My original best practices regarding testing, discussed here, was mostly based on the experience I had working on projects for University. These were all small projects with most of them not using the file system or a database. The experience I have gained since I started working (about 1,5 years ago now) have learned me that in real-life software my best practices are useless. A couple of reasons why my ways to test software failed me:
- Testing a high level component takes much longer due to all the integration.
- Software that uses the filesystem or a database are a pain to set up.
- Systems consisting of multiple servers or services often need to be fully deployed.
Tests will become slow and less reliable. Moreover if something in the lower layers fails all of you tests fail, or in case if you use a dependency system for your tests a lot of your tests won’t run hiding potential other failures.
I have investigated this, looked into the idea of unit testing and I have fallen in love with it. The benefits of it are pretty simple:
- Your test can only fail if the unit under test is at fault.
- Tests will run fast, as they only test a small part.
- There is no heavy setup of servers, databases, …
Creating unit tests requires you to use mocks, stubs and fakes. An often heard argument against unit testing is that the extra work to create and maintain these test doubles does not justify the advantages. An end-to-end test does not care about changes internally, but such a change will lead to some rework in test doubles.
I consider this a pretty void argument, first the extra work to create the test doubles is compensated by not having to deal with setting everything up. I have had my share of messing around with writing setups in my tests and they tend to take up a lot of time. Maintaining the test doubles is indeed an extra cost, but at the same time you can wonder how often these internal changes happen, and should they happen that often? After all the unit has exposed some API, changing this API may affect other code you are not aware of. In this case the unit test acts as a guardian, it defines and protects the contract of the unit.
Unit testing allows for something more as well, all of a sudden you are able to validate the calls that are made. Instead of relying on input-output tests you verify that only intended methods are called and how often. A test like this gives a bigger insight in the unit, and can reveal coding errors. Cutting the dependencies of a unit with all the rest can seem a lot of work, but if this is the case then perhaps your units are not designed well, they are too tightly coupled.
So as it turns out, unit tests are not just there to make your tests go faster or more reliable. They allow you to look at your code in a different way, a way that is hidden by the end-to-end tests and it will warn you of possible dangerous actions. The information that can be extracted from using unit tests far outweighs the cost. I am still learning in this aspect, and I am curious to see what wondrous things lay in my way.