Wednesday, December 3, 2008

The need for speed

I say this a lot, but I love TDD. Besides the list of reasons about how it improves code or simplifies solutions, TDD is fast.

TDD is fun because it goes so quickly. Write a test, make it pass, refactor, run more tests.

I love the flow. I love adding a new interface and using it without worry about implementation. I love knowing after I make a new test, something in the app is different and it works. I love living in the tests and only running the application before checking in.

However, the moment the tests slow, the practices start to slip...
  1. Writing code and verifying it works by launching the app, then creating the tests
  2. Making the test pass before verifying it failed.
  3. Not running all the tests before check in.
  4. Not refactoring because it takes too long
  5. It stops you from working close to 5 because you don't want to wait for the build
  6. Not adding tests at all because you don't want to break the build and don't want to wait to find out
  7. It ruins all the fun.
These things all lead to poor test coverage or tests that don't actually do what they say. Yeah, on paper your coverage may look nice, but there's plenty of code you can delete w/o breaking tests. Coverage makes sure a code path is followed, it does not verify the path was actually tested.

So, how do we keep our tests running quickly?
  1. Mocking. Reduces the amount of time spent creating dependencies and their dependencies and the rabbit hole of dependencies that nobody can even see.
  2. Use a one to one ratio of test assemblies to application assemblies -- don't waste time waiting for things to build that you're not testing.
  3. Keep interfaces and implementations in separate assemblies. Dependents should only be recompiled if the contract changes, not the implementation. This allows you to complete the usage of a change, without worrying about the implementation. Sure, you can just resharper, but are you really going to add tests for a new method right now?? If you do, you break the rhythm, if you don't, you have to remember, somehow, that there is code that needs to be implemented somewhere that your tests won't pick up... you'll start thinking about the implementation or just forget and break the app without anyone knowing.
  4. Simplify. Keep tests short. Only create items in setup that are used in all tests. Avoid factories for creating concrete classes because they hide dependencies and make it too easy to use real implementations instead of mocks. Avoid repeating yourself in tests, don't assert the same thing twice, it doesn't help to have the same failure twice but it does slow everything down.
In general, follow the best practices of test writing! They're there for a reason. No point in experiencing the pain of the nice people who figured them out. Take advantage w/o having crappy tests, lots of pain and failure.