Slow Tests Are the Symptom, Not the Cause
re-factor.com
re-factor.com
In an app designed like this the integration tests are the useful ones, because they're more contentful and test assumptions that are more likely to be wrong. The article flatly asserts that code written in this extremely decoupled style will require fewer integration tests, but I see no evidence for this. And you still need to figure out how to make them run fast.
My experience is that automated testing should test the system not the implementation.
The argument for having fewer integration tests goes like this: if it's hard to write isolated unit tests for some classes you end up testing many more execution paths in integrations tests.
But it doesn't help when your test is solving the same problem. If your test says "this object calls collaborator1 and collaborator2, then passes both results to collaborator3" then that's exactly the same design problem you'd have if you were to just write the object straight-up.
This is why people talk about BDD rather than simply TDD. It's not about having tests, it's about identifying the user-facing behaviour that you want, testing that, and then writing your objects to conform to it. This really does help your design, because it lets you start with the appropriate interface on the user side and work down from there, and the resulting object boundaries can be better than the ones you'd have thought of if you started with design. But if you restrict your test to a single class and mock its collaborators then that implies you've already decided what your class boundaries are; in effect you've already done the most important part of the design.
>The argument for having fewer integration tests goes like this: if it's hard to write isolated unit tests for some classes you end up testing many more execution paths in integrations tests.
True enough - if there are things you can't test in a unit test then you need to test them in an integration test. So if you have a complex piece of logic that you want to run several tests of, it's best if you can test that piece of logic in isolation - this is exactly what unit tests are good at.
But you still need to test all your integration pathways. All too often a class doesn't contain any logic as such, particularly when you design the way the article suggests - it's just an integration between its collaborators. For such a class there's no point in unit testing, because the only thing the class actually does is integration.
And in my personal experience, you CAN'T refactor because the test is so tightly aligned with the production code. EG: moving a variable declaration up or down one will break a test even though it didn't change anything.
Benefits like fast, easy testing, easier maintenance, swappable external things like databases, queues, external api's are apparently not enough of a benefit to change away from rails.
I wrote a pattern (http://obvious.retromocha.com) that makes pluggable systems easy based off of ideas from Uncle Bob, Alistair Cockburn, and Kent Beck which I think makes writing pluggable systems a lot easier, but as with all similar structures like Hexagonal Architecture and the like, Rails devs seem to not see the value in it until their app is a complete mess, at which point they would rather add more rails code than fix the underlying problems.
For a lot of developers, now that rails is popular, rails IS the architecture and things that go off the rails are either a waste of energy or too difficult.
Rails is what Kent Beck calls a connected architecture and as his incredibly scientific graph describes, the cost of change spikes dramatically over time compared to a modular architecture: http://www.threeriversinstitute.org/blog/wp-content/uploads/...
Until developers adopt modular or at least more service oriented designs, nobody should be surprised by slow tests, features taking too long to ship, the cost to ship code to rise dramatically over time. It's mostly our own fault.
http://www.smashcompany.com/technology/an-architecture-of-sm...
We have been using this approach for a year now, with REST on top, and it so far has worked beautifully. The entities map naturally to REST resources and DTOs makes nice request and response classes.
Armed with CQRS (now I sound enterprise-y), the data storage is easy to work with using the same DTOs (the data storage and its interface do not know anything about the entities either). On the DB implementation level, we work on the DTOs, which are trivial to store to the database (even without ORM, although it does remove much of the boilerplate). Only one class - the entity repository - is needed to convert the DTOs to entities, and this only happens on create, update, and delete. Reads (due to CQRS) go directly to DB without instantiating any entities - neat. We also use 'unit of work' -pattern to abstract the notion of transaction on create, update, and delete operations, which simplifies the interactor code, hiding the nature of the data storage (making it a swappable in this respect too).
But to stay on topic, our tests are fast too as the interactors can be tested by injecting an in-memory database as a data storage to test the business logic, and the interactors have no dependencies on any frameworks, web or otherwise. Testing the interfaces makes BDD feel natural and also supports clear separation of the REST layer (the communication channel) and the interactors (business logic).
* http://www.infoq.com/news/2012/02/programmer-anarchy
* http://www.youtube.com/watch?v=uk-CF7klLdA
Edit: would help if I actually added some links
In a language like Scala Obvious is literally just a way you would organize your program, not so much a library. It already has everything else you need, even things like immutability (if you're into that kind of thing). I just wish it compiled faster.
Obviouscasts is built on Obvious. I built a cool pluggable newsletter tool that you could trivially plug in Mailchimp, Mailgun, Sendgrid, or just your boring SMTP stuff to it. It was a heroku deployable newsletter so that you didn't have to pay monthly fees to a company like AWeber, Mailchimp, etc. just to collect emails for a list that won't be used very often.
I'm working on building out some new things in go with Obvious architecture behind them.
At my old job, some of the Obvious structure ended up powering the ruby services behind StBaldricks.org, which does $30+ million in donations and millions of page views a year.
I honestly don't know of many more examples beyond that, but it's a very small project and it was open sourced in January.
I like the idea of an object or collection of objects having a generic interface that can be mapped to all sorts of things such as a commandline or an HTTP POST etc... I am also at a job where we are struggling with the ol' "monolithic codebase" and we're investigating solutions such as this architecture.
This feels like how Spring must have been born. Trying to think of an alternative, and I think I would be happy with the fat controller example originally given. It's simple enough and straight forward.
Perhaps it's just bad examples all the way around...
Tests for these "director" style methods must by nature assert that all the right messages were forwarded to the right objects under the right circumstances, so yes, you do end up with tests that look a lot like what they're testing.
Agreed that ideally you are only testing ins and outs though, but I think this case was exceptional.
As for the testing issue, we have a controller-like object there, and a controller’s job is to coordinate sending messages between collaborators so I don't think a unit test for this object should test anything than these interactions. This also might be a consequence of using a simple example.
I see a lot of unit tests like the ones described in this article and have some doubts about their effectiveness for doing much more than spotting syntax errors. Integration testing is still important if the goal is avoiding defects getting out to production.
Edit: I love tests and they should be fast so that you can run them every time before pushing your changes but as I said they shouldn't dictate how you design and write your application.
> ... your tests shouldn't dictate how you will design and write your application
I know that's not your whole quote and a lot of context is lost in that "...", but I think that's what he's replying to.
Cause slow tests as a symptom pretty much means that if you're not an excellent programmer then tough sh*t, testing will be slow and painful, which ultimately drives people away from those good practices.
Also, I find ironic that Rails (as a framework that is supposed to empower people and make them efficient) is, according to many comments in this thread and "tech pundits" conducive to the kind of tight coupling and dependency that will lead to bad design choices and slow tests.
But they need to spend half hour compiling before they can run their tests.
[tongue in cheek obviously]
1: Added a users resource beneath the mailing list, ie
resources :users
resources :mailing_lists do
resources :users
end
add_foo is almost always a sign that a new nested controller should be made, and there is almost always a need for a delete as well - in this case users should be able to remove themselves from a mailing list.2: Put the add method on the mailing list and not on the user, because that is where I'd expect the least logic to be.
because:
3: I'd put the actual mailing list mailer logic in a separate class for the configuration of it.
http://blog.codeship.io/2013/08/21/faster-test-suite-boot-ti...
http://blog.codeship.io/2012/11/15/speeding-up-our-test-suit...
Perhaps I'm showing my lack of knowledge or experience with capybara, or integration testing, but that's one aspect I wish there were some better tools / approaches. Having to dig through css/xpath elements to click on or perform interaction is still a major PITA. If anybody has some suggestions to share, I'd love to learn more!
Were we to improve our unit tests to run even faster, as the post suggests, the difference in execution time will become much more pronounced. several orders of magnitude.
I think feature/UI testing is still an interesting space for better tools, but of course the problem is very difficult to solve.