Testing: how to focus on behavior instead of implementation without losing speed?

I think all three opinions are still completely valid. Ryan and I were struggling with the maintainability of mocking, while David felt the maintenance tradeoff was worth it for the increase in speed.

But these tradeoffs are symptoms of a deeper problem, which David alluded to in 2007: ActiveRecord. The design of ActiveRecord encourages you to create god objects that do too much, know too much about the rest of the system, and have too much surface area. This leads to tests that have too much to test, know too much about the rest of the system, and are either too slow or brittle.

So what’s the solution? Separate as much of your application from the framework as possible. Write lots of small classes that model your domain and don’t inherit from anything. Each object should have limited surface area (no more than a few methods) and explicit dependencies passed in through the constructor.

With this approach, I’ve only been writing two types of tests: isolated unit tests, and full-stack system tests. In the isolation tests, I mock or stub everything that is not the object under test. These tests are insanely fast and often don’t even require loading the whole Rails environment. The full stack tests exercise the whole system. They are painfully slow and give useless feedback when they fail. I write as few as necessary, but enough to give me confidence that all my well-tested objects integrate well.

Unfortunately, I can’t point you to an example project that does this well (yet). I talk a little about it in my presentation on Why Our Code Smells, watch Corey Haines’ presentation on Fast Rails Tests, and I highly recommend reading Growing Object Oriented Software Guided by Tests.

Leave a Comment