5 Reasons You Should Have More Unit Tests
The test pyramid is a valuable visual in agile. First defined by Mike Cohn in his book Succeeding with Agile, it describes the importance of focusing testing (and automation) below the user interface layer of an application. In particular, it argues that unit tests should make up the majority of tests.
While agile teams recite this principle, it is often not clear why it is so important. I can think of at least five reasons unit tests should make up the majority of tests written for an application.
Less rework
In a rapid, iterative development process like agile, time is of the essence and rework is the enemy of progress. By testing each unit of code as it is created, we identify and correct implementation defects quickly and reduce the amount of rework necessary later. Unit testing as we implement code supports a “fail fast” mentality that seeks to find and fix problems as quickly as possible.
Easier debugging
If unit testing is not performed, implementation bugs must be identified by your testers. Because testers are typically testing at the story, API, or user interface layer, the implementation bugs they find are more difficult to pinpoint. This means longer debugging cycles, as the developer not only has to recall what their code does but walk through perhaps thousands of lines of code to determine what is broken. Testing at the unit level narrows the scope of where a problem resides, greatly speeding up finding and fixing defects.
Fearless refactoring
Maintainable code relies on the ability to constantly refactor code to keep it clean as changes are made. The ability to refactor code without the fear of introducing new bugs is called fearless refactoring, and it’s only possible if your team has a strong regression test suite that regularly verifies that code changes don’t break other code. A regression test suite should include automated tests that check the code at a variety of layers, including at the unit level. By creating unit tests for every unit of code we create, a significant amount of automated testing can run as part of your continuous integration process, easing the fear of refactoring.
Business-focused testing
Concentrating some testing effort on units of code will increase the amount of effective testing done against business requirements and stories. When unit testing is not performed by developers, implementation defects are left to be found by testers at a higher layer. This means testers have to spend precious time identifying and documenting defects that aren’t business-focused, and that’s a waste of their time. By catching as many implementation defects as possible during unit testing, testers can better spend their time looking for ways the application doesn’t meet the needs of its users.
Whole-team quality
An important concept in agile is that the entire team is responsible for quality. While quality means more than performing testing, getting everyone on the team involved in the testing process emphasizes its importance. In addition, it reduces the temptation for software developers to implement untestable code and still throw it over the wall to testers, as was often done in traditional software development approaches.
I agree with the "shift left" idea of getting quality information sooner rather than later; that's very important. But, I really only agree with the value of unit tests in cases where there is a direct connection to functional requirements, rather than a unit test that just verifies that the unit is implemented a certain way (which I think we've all seen). Refactoring the implementation might mean rewriting the unit test, too, in that case.
This article seems to assume that all testing of the SUT other than unit testing is through the GUI, and makes the valid point that finding a bug through the GUI may be hard to characterize or find root cause because a GUI drops so much information from the implementation just to work (and other reasons, like race conditions in the GUI). A better approach, assuming a good architecture that allows this, is to do bottom-up testing. Many issues can and do happen with integration between components of the SUT and between the SUT and external services, and these can be high-risk, so it's important to get those early.