component

2016-10-28T17:33:27.000142Z

@seancorfield @roberto So in your response to @tetriscodes , are you saying I would probably not ever test a database? Could you review what I'm about to add to a current project, and give me a little guidance?: ---------------- I have a Postgres Component, and that just manages a connection, but then I have a "Boundary" (ala duct) to that component that establishes my internal "api" for accessing, ex, my users db (`find-user`, create-user, ...) So, the path I'm going down today wouldve been to create a test-db, and some fixtures+setup+breakdown code in my tests for testing this User Boundary. And I want the testing of the test-db to use migrations + rollbacks I have setup for my dev+prod. databases. What would be a good practice here?

seancorfield 2016-10-28T17:38:15.000145Z

Well, my basic question would be: where’s your business logic?

2016-10-28T17:38:29.000146Z

in an endpoint

seancorfield 2016-10-28T17:38:30.000147Z

What exactly do you want your tests to do for you?

2016-10-28T17:39:13.000148Z

and i would like to make sure that my sql queries, and the yesql that brought them in, all work as expected against my current migration, or schema

2016-10-28T17:39:35.000149Z

the endpoint is just a compojure route

roberto 2016-10-28T17:40:25.000151Z

testing your business logic should take care of that, right? If the sql queries are not working, then your business logic will also break?

seancorfield 2016-10-28T17:40:28.000152Z

So you’d be testing a stateful function? Could you break that down into a query, a pure function, and an update — and then test the pure function?

2016-10-28T17:41:56.000153Z

@roberto I could test just the business logic, but then that would depend on my sql queries, and I'd prefer to test it independently of my database, component, or boundary. (right?)

seancorfield 2016-10-28T17:42:27.000154Z

We use TDD/BDD to support design of APIs — making sure they’re a good experience to call, that they have the right error handling. None of that requires a DB behind it as you can exercise it with canned data. Then there’s the actual business logic, which again you can write tests for.

roberto 2016-10-28T17:42:36.000155Z

honestly, I don’t know what you mean by boundary, it seems to be a duct specific thing. But a component should only have 2 responsibilites: starting and stopping

2016-10-28T17:43:06.000156Z

@seancorfield And yes, the state would be the postgres database, and so I could set something up to run a migration against an empty test-db, run my queries, and then roll it back

seancorfield 2016-10-28T17:44:06.000157Z

Consider this code:

(let [data (run-some-query args)
      result (perform-the-business-logic data args)]
  (perform-any-updates result data args)
  result)
Now you can write tests for each part separately.

seancorfield 2016-10-28T17:44:25.000158Z

More importantly, you can isolate tests of the business logic from any database.

2016-10-28T17:44:28.000159Z

and a "Boundary" is an idea that duct + /u/weavejester champions, which is basically, you can never just do (db/query ...), but instead you make a boundary of the specific fns your allowed to call on that component

roberto 2016-10-28T17:44:59.000160Z

so, it is just an “object” that wraps around a component?

2016-10-28T17:45:14.000161Z

@roberto yes, basically (to my understanding)

roberto 2016-10-28T17:45:53.000162Z

yeah, then you just need to test that “boundary”, the component gets exercised automatically when you are using the functions in that boundary

2016-10-28T17:46:23.000163Z

@seancorfield I haven't been involved at this "systems" level of development before, so honestly, while your example code makes sense, should I even be testing sql queries against a db? I'm embarassed to ask, but, do people do that?

2016-10-28T17:46:43.000164Z

(like, migrate in a fresh test-db and thebn break it down after, on each test)

seancorfield 2016-10-28T17:46:43.000165Z

In general, no, I wouldn’t.

roberto 2016-10-28T17:46:47.000166Z

i don’t test sql queries. I test the function that use that query

roberto 2016-10-28T17:46:54.000167Z

testing every query is double the work

roberto 2016-10-28T17:46:58.000168Z

and double the code

roberto 2016-10-28T17:47:09.000169Z

and makes it harder to then change something

seancorfield 2016-10-28T17:47:15.000170Z

That said, I will verify that a complex HoneySQL expression produces the right SQL statement — but I’ll tend to do that in the REPL.

2016-10-28T17:47:32.000171Z

checking by hand, but not automated in a test?

roberto 2016-10-28T17:47:33.000172Z

you can add tests that you will delete afterwards if you are doing TDD, to just guide you. But I wouldn’t leave tests for ‘queries’ in the code base.

seancorfield 2016-10-28T17:47:42.000173Z

I’ll also have a DBA eyeball a particularly complex query for performance issues, and maybe have them run it against production.

2016-10-28T17:48:30.000174Z

so, then, if I want to test the business logic, it sounds like I need to test it not as a pure function, but in the context of a db?

seancorfield 2016-10-28T17:49:07.000175Z

When we first started doing Clojure, we tended to mix SQL queries, SQL updates, and business logic all together — horrible to test. Now we’re being more careful to separate out the queries from the business logic and then run the updates at the end, depending on the results. That makes the business logic much easier to test — as a pure function.

roberto 2016-10-28T17:49:39.000177Z

you can test logic without the db. But I would also test the integration points. The places where the business logic is passed in the data from the database.

roberto 2016-10-28T17:49:55.000178Z

That integration point is where you would exercise the database component.

2016-10-28T17:50:11.000179Z

So if I have a fn that does logic, and an effectful call, I should split it out, and test the logic, and not test that the effectful call is behaving?

roberto 2016-10-28T17:50:22.000180Z

i think testing that integration point offers more value than writing individual tests for each query

2016-10-28T17:50:29.000181Z

(or in what @roberto just said, test pure, and test integrated?)

roberto 2016-10-28T17:50:51.000183Z

yeah, that is the general approach I take

roberto 2016-10-28T17:51:34.000184Z

i’m not much of a test purist, so I’m quite liberal as to what I test. I find that trying to test every function I write adds more cost to maintenance in the long run.

2016-10-28T17:51:36.000185Z

so if I have an integration test that, say, updates a user in the db, would you then check that the rows in the db look as expected? Or just check the return value of the fn?

roberto 2016-10-28T17:51:58.000186Z

the return value

roberto 2016-10-28T17:52:21.000187Z

because if will fail if something goes wrong. so you don’t need to do another query to the database.

roberto 2016-10-28T17:52:50.000188Z

if the sql statement is wrong, for example, it will throw an exception, etc...

2016-10-28T17:53:44.000189Z

ok. so the gist I'm getting is, tests are expensive, so don't do too many, but also don't do too few 😉

roberto 2016-10-28T17:53:56.000190Z

hehehehe, all code is expensive 🙂

roberto 2016-10-28T17:54:17.000191Z

basically consider that every test you write, you have to also maintain

roberto 2016-10-28T17:54:58.000192Z

so I try to only keep those that offer the most value. At times I write tests that offer no value, but only serve as some sort of guidance while I’m testing things out. But those get deleted.

roberto 2016-10-28T17:55:11.000193Z

With clojure I do that less and less because I have a good repl, so I try things out there

roberto 2016-10-28T17:56:15.000194Z

I used to write tests for every property I added to a data model when I was doing java or some other oo language. It quickly became too much.

2016-10-28T17:56:44.000195Z

@seancorfield @roberto thanks, you just cut out a chunk of my work today, and gave me some piece peace of mind 🙂

seancorfield 2016-10-28T17:58:33.000196Z

I tend not to test queries or updates on their own. I’ll write more extensive tests for the pure business logic (and if you can find properties and do generative testing, even better), and then I’ll write integration tests (of the whole endpoint) sometimes. Or automated “user acceptance” tests which exercise the API itself.

seancorfield 2016-10-28T17:59:15.000199Z

For example, in our API test suite, we typically have tests like

(expect some-result (api-post “endpoint” (auth-token “username” “password”) :some “args”)

seancorfield 2016-10-28T17:59:43.000200Z

And the auth-token function walks through the whole OAuth2 login / token exchange process.

seancorfield 2016-10-28T18:00:12.000201Z

So that test makes four POSTs altogether, exercising all sorts of stuff.

seancorfield 2016-10-28T18:00:53.000202Z

We can also use that to get a token and then test three or four API calls as a sequence, just like an end-user (client) application would.

seancorfield 2016-10-28T18:01:58.000203Z

In addition, we’ll have more extensive example-based tests for pure business logic functions where possible, or integration-level tests for older code that still has the queries / updates mixed in.

2016-10-28T18:02:36.000204Z

that is beautiful, and that makes a lot of sense, I think I'll try to gravitate toward more general tests - #PickTheRightLevelOfAbstraction 😉 so for those tests, will you even run the api server and test over HTTP calls against your api?

seancorfield 2016-10-28T18:08:15.000205Z

We have tests we can run on the code directly, and tests we need the API server up for.

seancorfield 2016-10-28T18:08:31.000206Z

Technically the API, Authorization, and Login Servers — they’re three separate processes.

👍 1
tetriscodes 2016-10-28T19:18:06.000207Z

Interesting points above.

tetriscodes 2016-10-28T19:18:26.000208Z

I’ve settled on testing my system instead of my components individually.

tetriscodes 2016-10-28T19:19:37.000209Z

I started writing very small and many tests on the components and then the light came on that I’d be writing tests to simulate system access via http/mqtt.

tetriscodes 2016-10-28T19:20:49.000210Z

ie, I’m getting more confidence testing my components after dependency injection than the sum of the parts/tests.

tetriscodes 2016-10-28T19:21:13.000211Z

I am building the components up and testing them on the fly as I develop with the repel.

tetriscodes 2016-10-28T19:21:36.000212Z

(def configcomponent (com.stuartsierra.component/start (spacon.components.config/make-config-component)))

👍 1
seancorfield 2016-10-28T22:41:53.000213Z

Perhaps what’s different for us is that we have test suites that can run independently for a number of our “subsystems”, so we have a low-level “system” component and a test suite for that stuff, and an “environment” component and a test suite for that stuff, and so on.