The most fundamental thing a developer can do to verify that the code they wrote is working properly is to write a unit test for it. In this article I’m going to talk a little bit about what a unit test is and one of the best disciplines a professional software engineer can follow for writing unit tests: Test Driven Development (or TDD).
What are Unit Tests?
So what is a unit test? It seems to be an often confused term as I have many times heard co-workers and managers discussing “unit tests” that were really integration or end to end regression tests. Unit tests are small, specific bits of code that are meant to test individual functions in isolation. They are often organized into suites of tests that verify all the functions in a class. They should verify that a particular function gives the proper outputs based on given known inputs. This should be done for both positive and negative test cases. If necessary unit tests can also verify that the code calls the correct underlying libraries with the correct inputs and in the correct order.
Running Unit Tests
Unit tests are not tests that are written to be run in the target environment and test the inputs and outputs of your actual release builds. Unit tests are compiled into a specific test component that will execute in your development environment. It should have minimal dependencies so that it compiles and executes quickly. The process of compiling and executing the unit tests for an individual components should happen in seconds (if not fractions of a second). Things like disk IO, databases, and network connections should be “mocked out” (i.e. replaced with stubs of varying functionality).
So What is Test Driven Development?
TDD enforces the writing of unit tests by requiring that the developer write the unit tests for the production code before writing the production code. It’s a bizarre notion when you first read it. But this doesn’t mean that TDD wants you to write ALL the tests and then write ALL the production code. Instead it’s a cyclic process where:
- You write a failing unit test.
- You write some production code to make the test pass.
- You refactor to make the code clean.
And that’s it at it’s core. If you know those three rules then you know the essentials of what TDD is. But actually following the practice can require some more explanation. Which tests should you write first? How much of the production code should any one test verify? How do I start? I think Robert Martin’s (Uncle Bob’s) rules for TDD from his book Clean Code explain it best:
- You may not write production code until you have written a failing unit test.
- You may not write more of a unit test than is sufficient to fail (and not compiling is failing).
- You may not write more production code than is sufficient to pass the currently failing test.
If you follow these rules you will be stuck in a small tight loop of only a few minutes long where you write a small, failing unit test to test a little bit of functionality. Then you write a little bit of production code where you make that test pass. Then you refactor both the unit test and the production code to make them clean (removing duplication, following good design and naming conventions, etc).
How Do I Start?
What test should you write first? The simplest test. This is the simplest bit of functionality that you can think of that needs to be implemented. If the first thing you need to do is instantiate an instance of a new class in the production code then write a test that verifies you can instantiate the class. And when you’ve made that test pass you write the next simplest test. And the next and the next. In that process you incrementally add functionality until the production code you are writing meets all of its requirements. At that point you will have automated tests that tell you the production code is working and will tell you in the future if anything ever happens that breaks it. This way you can move forward to the next piece of the production code with confidence that your unit tests will catch any errors you may introduce as you go.