In this series of blog posts I will try to give the reader a good understanding of unit testing and the most important things that are associated with it. Nowadays unit testing is a very common practice that every software developer should know about. In this series of articles you will read about the purpose of unit testing, test-first (TDD), building testable software architectures and of course the unit testing itself. My samples are written in C# 4.0 using ASP.NET MVC3, Ninject, Moq and MSTest, but I will provide links and information about other frameworks as well. The principles of unit testing are pretty much all the same on other platforms, so even if you are a Java or PHP programmer you can follow this guide.
- What is unit testing?
- What is test-first?
- How to build a testable architecture?
- How do unit tests work?
- Step-by-step walkthrough
- Conclusion & comments
What is unit testing?
Unit testing is just one of many different kind of tests that you can use to test your software. What it means is that you write some code that executes an isolated piece of code (hence 'unit') and checks if it performs the expected operations and/or returns the expected data. This usually implies that every bit of code you write also has a bit of code that executes your code and validates the result. You'll usually create new objects every time you execute a test, because tests should be able to run independently. This means that you create a new 'object under test' (the object you are testing) and supply it with new data for every test method, even if they are executed against the same method. One method can have different unit test methods for different scenarios as you will see in the examples later in this series, but one test method shouldn't be testing multiple methods because that would defeat the purpose of a 'unit' test. These kind of tests are called integration tests.
In order to write unit tests you'll need a unit testing framework, which is just a little library of 'assert' methods that can be used to validate results. Furthermore the framework usually provides ways to run these tests. You can also use mocking frameworks to create stubs/mock objects that replace the real-world objects. Last but not least I would suggest using a dependency injection container to do the dependency injection for you (I will talk about that later).
Nowadays unit testing is one of the most common testing strategies. It can be supplemented with other kinds of tests like code inspections/reviews, pair programming, etc. I highly recommend doing these kind of tests too because unit testing doesn't capture every kind of error (in fact: the defect-detection rate for unit testing is only about 30% according to research done by Capers Jones). For example: if there is an error in your unit test, you won't be testing your code thoroughly enough, if there is an error in your specifications, you are testing the wrong things. Other types of tests such as code inspections can hugely increase the defect-detection rate if you combine them with unit testing. You can read more about this in Code Complete 2, page 480.
What are the benefits?
There are a lot of benefits to unit testing. The first, obvious, benefit is that you now have means to easily validate that your code does the right things without having to set up a complete testing environment with a full-blown database, web services, etc. The second benefit is that it is probably much faster to write a couple of methods that do some magic and validate the result than having to start your application all the time, setting up scenarios and manually checking if all the right operations are triggered (e.g. with a debugger). The third argument for unit testing would be that you really reserve some time to think about what a method should (and should not!) do, which is nice because you may come up with new scenarios that you didn't think about before. The most important argument for unit testing however is regression testing. This means that, whenever you refactored or otherwise changed a piece of code, you can validate if everything is still working by just running your unit tests again. In large applications it is virtually impossible to manually check every scenario for every feature again after you've changed a bit of code. Unit tests give you the opportunity to do this with just one (of maybe a few) clicks.
To sum it up:
- Validate that you code performs the right actions in a very easy way.
- It's faster than manually going through every scenario in your application.
- It gives you a second time to think about your software.
- It enables you to continuously validate that your software is still working as intended after changing anything, anywhere, in your application (important!).
What are the disadvantages?
One of the most important disadvantages of unit testing is the fact that having unit tests that run 100% correctly never guarantees that your software is correct. There are errors that unit tests can't spot (such as GUI stuff), there can be errors in the specification, you might have missed some scenarios, your code coverage may not be 100%, etc. Another very important point is that it's quite a burden to refactor your unit tests every time you refactor the application, because it is quite a bit of extra work. I think that the amount of test code is roughly the same as the amount of normal code.
To sum it up:
- Having unit tests that run 100% correctly absolutely doesn't mean that your software is correct.
- It is a burden to maintain the unit tests when you change the application.
- It's always faster not to write any tests code, but that would be very unwise.
Next: What is test-first?