Test Driven Development is an essential discipline to follow to ensure you are writing code that works all the time. But there are several things that can impede and even block you from being successful with it. Below I have some best practices that can help you with following TDD and making it a worthwhile effort.
Always do the next simplest test case
Even if you think you know exactly what the code is going to end up looking like for the functionality you’re implementing always try to keep yourself from jumping ahead in the testing. Try to think about what the next simplest test case is and implement that first. You want to build the functionality incrementally. If you go to fast and try to skip some of the simpler and more obvious test cases you can find yourself at a place where you have to write a large chunk of code all at once to get the desired functionality. Writing that big chunk of code all at once can be difficult and can lead to having a more complicated implementation than is necessary as you miss all the incremental refactoring that comes with implementing the simpler test cases first.
Use Descriptive Names for Your Test Cases
Making the code you write readable is hugely important as the code is likely going to be read 1000 times more than it’s going to be modified. This is even more true for you unit test code as it is the best documentation for what you were trying to do with your production code. One of the first steps in making your test code readable is having good and descriptive names for your test cases. The name of the test suite should specify the class or function under test and the individual test names should describe in easily readable text what functionality is being tested.
One Logical Test Per Test Case
This goes hand in hand with well named test cases. It can be difficult to write a good name for a test case if you’re trying to test many things in that one test. Tests are generally broken up into three sections: arrange, act, and assert. You set things up for what needs to be tested, you execute the code under test, and you assert the expected results. If you go through more than one arrange, act, assert sequence in your test then you should see if it can be broken up into separate tests.
Use Code Coverage Tools
Code coverage tools generate a report from a unit test run telling you what parts of the production code were touched during the test run. This can quickly tell you if you’ve missed any test cases. If you don’t have 100% test coverage then there are test cases that you’re missing. I’m not a proponent for doing unit testing on one line getter and setter methods, but those methods in a class that contain real logic should be tested with 100% code coverage. 100% code coverage doesn’t necessarily mean you’ve written all the tests you need for a particular method, but if you don’t have 100% code coverage then you know you haven’t.
Don’t Rely Solely On the Code Coverage Tools
Just because you have 100% code coverage that doesn’t necessarily mean that your testing is complete or correct. Your test cases may be getting each line of the production code to execute, but that doesn’t ensure that your test is validating expected outputs or calls to other methods correctly. You need to ensure that the logic implemented in your test for arranging, acting, and asserting is correct as well. This is one of the reasons why peer code reviews are so important.
Keep Your Tests Fast
Test Driven Development is all about feedback. It creates a very short feedback cycle so that for every little change you make to your code base you have immediate feedback that the change hasn’t broken anything. If it takes 5 minutes to run your unit tests then you’ve lost that feedback loop as no one is going to wait 5 minutes to make sure they didn’t break anything after each small code change. One easy approach to keep things fast is to only run the unit tests that are necessary (i.e. only the tests for the component or class that you’re working on rather than all of tests in your entire system). Most testing frameworks provide command line options for filtering which tests should be executed for each test run. In addition you should always make sure your tests run fast. The set of tests you run as you’re going through the red, green, refactor cycle should execute in just a few seconds. Things you do to keep your tests running fast are:
- Keep console output to a minimum. Each print statement slows things down and if you’re running hundreds or even thousands of tests each millisecond can count. Just let the test framework which tests are failing.
- Mock all system calls. You shouldn’t do any system calls that will slow things down or even potentially block. These are things like file system calls, timers, sockets, or database connections. All of these should be mocked out (both to keep the tests fast and to be able to control the behavior of these calls for different test scenarios).
Run Your Tests Multiple Times and In Random Order
Most unit testing frameworks now have command line options for running the tests many times and for running the tests in a randomized order. Doing this periodically helps ensure that your tests don’t have any dependencies between each other and also helps to identify any “flaky” tests that may fail intermittently. One of the most useful things about having a comprehensive suite of unit tests for your code base is being able to run that test suite whenever your baseline changes to ensure that those changes didn’t break anything. If you have flaky tests that are failing intermittently it can be hard to realize this benefit as you never know if a failure is because of a new code change or one of those flaky tests.
Test Driven Development can help you and your team go faster by always ensuring that your code is working and giving you the confidence to make changes to your code base. But like all things it must be done properly to ensure you realize all the benefits.