Sometimes, even if you are generally applying good Test Driven Development practices, you can find yourself slipping into what I call Integration-Test Driven Development, or ITDD. Essentially, this is when you use a high level integration or functional test to know if your low-level code changes are having the desired effect. So you need to build several modules of your app, or even your whole app, to see if your code is correct.
This is a Bad Thing.
Integration Tests have their uses, but driving fast development cycles is not where they work best. In fact, ITDD is bad for two big reasons. Firstly, it slows down (sometimes insidiously) the feedback cycle, which results in a big hit to your development pace - you get bogged down. Secondly, you find yourself using integration testing to trouble-shoot low level coding issues, which is imprecise and inefficient.
We often find ourselves falling into the ITDD trap when under pressure to deliver results, fast. The problem is, it's like buying expensive goods using a credit card with very high interest. Maybe your last-minute demo will work (or it might just as likely crash and burn), but you are definitely making life harder for yourself down the line. Under pressure, it sometimes feels natural to drop the good habits you have painfully acquired, and just hack. However, counter-intuitive as it may seem when you are under pressure to deliver, it is actually those good habits that allow you to progress at a fast, sustainable rate without being bogged down by slow feedback cycles and a continuous stream of obscure, difficult-to-diagose and hard-to-fix bugs.
Writing a unit test is formulating a theory about how your app behaves, and getting instant feedback to confirm (or infirm) your theory. TDD/BDD driven unit tests are a great way to elaborate a clean low-level design for your code - they are also a great way to isolate and identify bugs. On the other hand, fixing bugs using integration tests, by observing log messages, generated reports, or manual testing is more like taking pot-shots at a distant, fast moving bird. It may work eventually, but it is not the most efficient of approaches.
When you find yourself doing ITDD, the bugs have taken the initiative. They are fighting a guerilla war against your code, striking in places you least expect. It is time to pause the advance and regroup. Take a deep breath, step away from the keyboard, and think about what it is exactly that you are trying to achieve. Then think about how you can break this down into smaller chunks and verify your app's behavior at a unit test level.
There are a few common Red Flags that can help you spot ITDD. These include:
- building the whole app to test a minor change or bug-fix - again, and again, and again
- Relying on debug messages or println to understand your app's behaviour
- Falling into the "just one more fix and it should work" trap
Of course this is often simply because the code you are working with Is hard to test. Still, hard does not mean impossible - if you can refactor your code to make it easier to test, you will save yourself a lot of time and effort in the long term. Here are a few general tips:
- Make sure dependencies are visible, so that they can be mocked and/or observed when required
- Stub out dependent objects with your own home-rolled test classes if it makes it easier to verify expected behavior.
- Try to figure out exactly what you expect your app to do, then design a way to prove that it does it. In my experience, observing the outcome or state (stubbing) is easier and more reliable than observing interactions between your classes (mocking), but sometimes you do need both.
So if you value your time and peace of mind, try to avoid the ITDD trap. I'm not being overly dogmatic here - just reflecting on what seems to work best in practice.
John is a well-known international consultant, trainer and speaker in open source and agile Java development and testing practices. He specializes in helping development teams improve their game with techniques such as Test-Driven Development (including BDD and ATDD), Automated Acceptance Tests, Continuous Integration, Build Automation, and Clean Coding practices. In the coming months, he will be running include online workshops on Test-Driven Development and Automated Web Testing for European audiences on May 31-June 3, running a full 3-day TDD/BDD/ATDD workshop in Sydney (June 20-22) and Wellington (date to be announced), and talking at the No Fluff Just Stuff ÜberConf in Denver in July.