Unit testing has many benefits in agile software development during the whole life cycle of an application. Learn what makes a unit test successful and how it saves your effort in software development.
Unit testing has many benefits in agile software development during the whole life cycle of an application. In this blog I aim to clarify the purpose of unit testing from agile development point of view, with strong emphasis to code refactoring. After reading this blog I hope you have a better understanding of how unit testing saves effort during software development and maintenance, and with other test methodologies like integration and system testing.
An application consists of many small interoperable units, functions or objects, which form a stack of invocations. Applications are therefore multilayered.
In practice multilayered application means that a function implementing an interface calls several other interfaces which have their own implementations. Depending on application, there might be a lot of invocation layers, and each implementation of an interface should be unit tested.
Unit testing is about to prove that each unit invocation works as intended. Correct behavior of a unit can be ensured, since input and expected output are known to the unit test. This means that test material or test data must be provided right there in the unit test. Test material should consider both valid and invalid input for the unit’s invocation.
One of the principles behind the Agile Manifesto states that: “The best architectures, requirements and designs emerge from self-organizing teams”, sometimes interpreted as “an emergent architecture.” Unit testing is a great tool to test that decisions you make during development indeed result to the best architecture and design.
When creating a new layer of invokable unit you decide how the unit is used by declaring an interface and implementing a behavior behind that interface. By implementing a unit test, you ensure that the behavior of the unit is correct, but also the actual design is good enough to be integrated to the application. Therefore, unit tests should be applied early on when the application is still maturing.
If the unit can’t be unit tested it has some glitches in the design. Most likely the unit has dependencies which interact outside of the unit’s scope – remember that tested unit should not invoke anything outside of an implementation of the unit test. Quality of the unit can be enhanced by removing troublesome dependencies which in turn clarifies the actual behavior of the unit. There are several ways to provide dependencies to a unit which needs external interaction, but they are a matter of another blog.
Martin Fowler writes in his refactoring.com -page: “When a software system is successful, there is always a need to keep enhancing it, to fix problems and add new features.”
The application has a life cycle from the development to the maintenance of the application. While the application matures, it has more and more invokable units applied to it. Applying a unit to an existing application often requires changes to implementations of one or more units. Sometimes an implementation of the one or more units needs to be altered because implementation needs to be simplified or generalized in case of for example duplicates.
When it comes to refactoring, we shouldn’t forget Martin’s idea what refactoring is: “Refactoring isn't a special task that would show up in a project plan. Done well, it's a regular part of programming activity.”
Whenever you add to or fix an existing implementation, you should modify the implementation so that the original quality remains as it was, or even increases. These delicate changes to an existing implementation without altering the behavior of the invocation are what Martin Fowler calls refactoring. Keeping the implementation in good shape also allows rapid and focused changes which are in the core of the agile software development.
When applying changes to implementations of one or more invokable units, there is a chance of regression. Regression means that behavior of the unit is changed so that the result of an invocation is changed when invoked with the known input. Unit tests are one vital part of regression testing, the first line of defense to ensure that any modifications made to an underlying behavior do not alter the result of the invocation.
What if the behavior of an invocation, an interface of a unit, needs to be changed? In this case you need to implement new unit tests and modify the actual users of the interface. Unit tests are truly your best friend when you make changes which may alter the behavior of multiple invocations. Obviously, you capture invocations which have behavior changes when running the unit tests. You can also test your decisions which have led to behavior changes by implementing new unit tests.
If you find out that you have difficulties to create unit tests for the new behavior, you also know that you have glitches in the new design and can come up with the new plan. Then changing users of the altered invocations should be an easy task since refactoring an implementation using unit tests is robust and fast and ensure that integration is working correctly. You can apply this method when replacing a whole module with another module, since unit test let you know if any of the invocations change.
In the agile development we aim for continuous integration with quality. When a new feature is applied to an application it is subjected to a series of release tests, including integration and system tests. Creating an effective release pipeline for an application requires different tests to ensure the quality of the application. What matters is what is tested at each test cycle before releasing an application. Designing and developing a release pipeline requires as much technical expertise as programming an application.
When test developers need to implement tests for integration or system testing which would be easily tested by unit tests implemented by a software developer you should question if these tests do what is expected of them. Implementing unit tests reduces the actual work of the test developers and they can focus on developing the test environment and better and more extensive integration and system tests.
Scope of the unit tests are about handling the input data and getting a correct result from the valid input or a correct error from the invalid input. The input is therefore known, for example a known data set in a known format. The format can range from the simple input values to the more complex structured documents. As a comparison, integration tests are used to test that the parts or the whole stack of invocations in an application work as intended. Integration tests require work which consist of designing test environments, tests for use cases, virtualizations and much more which is usually done by test developers.
Unit tests are indeed an effective tool for software development. Here are the core points: