In the world of Software Development, there is often a big divide between developers, management and testers. Within management there can occasionally be an aversion to developers creating unit tests due to an inability to see the usefulness; a common perception being that the creation of tests add no worth or value to the product and are a drain on development resource. While there is some truth to this argument, this is mainly applicable when adding tests for the sake of adding tests. In this article, we are going to try to identify specific areas where correctly written tests can help maintain the stability of an application throughout its development cycle.
Test Driven Development (TDD) – What is it?
In our eyes, the most important principal of TDD is ‘every change to an application is to meet a proven requirement’. Projects of this sort usually consist of a shorter development cycle. The cycle is usually as follows:
- Create a failing test
- Write code to satisfy the failing test
- Run all tests to verify integrity
TDD also promotes continuous improvement and encourages refactoring of code that exists in the application. With TDD, any developer can safely refactor application code and be confident of the change if all the tests are still passing at the end.
Problems with TDD
As good as TDD sounds, it is not without problems. In the real world, it becomes very evident that each change that we need to make to an application does not necessarily have a set requirement or a set result. For TDD to be the best it can be we need to have a requirement to fulfill and an output to match. If there is no defined input and output for a requirement, it is impossible to cover the scenario, as you do not know what you are expecting given any parameters. For projects that do not have requirements outlined or confirmed in this way, the TDD approach would not be applicable. For fixes to existing projects that are without unit tests, again we would not recommend the use of TDD. If the project does not implement TDD and you want to use it, you may want to create a plan to upgrade the system but changing a project after completion would probably take longer as it may require much of the architecture and code to change.
Addressing some problems with TDD
TDD is ideal for projects that have not started and that have some requirements outlined before a project starts. TDD fits right in with an agile based approach, as requirements become clear and acceptance criteria added, tests must be authored to cover the various scenarios where appropriate. Existing projects will take much longer to bring in line with TDD and it is arguable that there is no worth to doing it. In these cases, developers will often fall into a trap, testing the implementation of code rather than the requirement. For an application overhaul or re-write, considering this scenario is crucial, as it is more likely to happen with the reuse of code and modules.
TDD can sometimes be seen in a negative light due to the requirement for extra up front design before implementation however, done correctly, TDD can lead to software releases containing fewer issues and a decrease in the time required to develop or improve the application. This is due to the inherent stability during development and upgrade processes provided by having appropriate tests in place. We know any changes to existing functionality are already under test to prevent issues, so new functionality will follow the same principals and will build on top of the existing testing platform created for the application. This can all translate to a bottom line: more efficient teams and lower support costs.
Practising TDD can be a big shift in developer mind-set as it requires a much more modular (i.e. more testable) approach to development, but a well-functioning team doing TDD does not actually increase the time it takes to complete a project over more traditional methods. Simply using a test-based approach will reduce the code that a developer will write and allows safer code refactoring. After all, “code is where bugs live”, so it makes sense that the less of it you have, the less chance there is to introduce bugs. With TDD, you not only have less code, but also have stringent tests to ensure that the code does what it should. Another positive commonly noted by anyone who uses a test driven approach is the decreased use of the debugger. This may not seem important but this itself can reduce development times, we are not launching applications and loading the debugger to make sure every line of code written actually does what we need it to. Debugging can lead to a situation where multiple screens are open just to navigate to the code under test whereas a unit test focuses on a single area, thus saving time.
Using traditional programming methods there is a cycle of Run, Test and Fix, which fits a debugger-heavy approach. With a test-driven approach, this becomes Test and Fix. We eliminate the need to always run the application and instead focus on smaller units to produce correct outputs. We save more time as we introduce a set point where we need to model a portion of the application before writing the code. Traditionally we would start writing code for an application and then change it further in to the project to match the implementation. This could introduce bugs in the system that we would again need to fix and test. Time consumption increases every time this happens. Using a test-based approach, we would know whether the implementation was broken before it hits the testing phase, saving both time and effort for developers and testers.
Appropriate Development Testing
We believe that there is no such thing as ‘one solution fits all’ and as such, here is the implementation that works for us. We started our own cycle based on TDD, which helped to improve our development times.
- Create a failing test based on the requirement – Unit test for code that is used for calculations or data manipulation, and integration tests for data presentation or output
- Satisfy the test by writing code to produce the correct output
- Verify all other tests pass and nothing has broken with the code change
- Refactor the code to make it meet standards with potential for optimisations
- Verify all other tests still pass
We use the above steps before we provide a release to our testing and UAT environments.
To satisfy customer requirements we used a mixture of Unit tests and Integration tests, we could not ensure accuracy and quality without unit tests and we could not ensure application requirements as a whole without integration tests.
For our project, we have unit tests in areas we felt would benefit from them, areas that manipulate data, calculate results or those that react when given different values. Using unit tests, we could ensure that the application code would behave the correct way in different scenarios and verify that it would never be possible to get into certain scenarios. All these tests add worth and ensure critical parts of our processing are resilient to change.
We also added Integration tests that use a development environment to verify the code change does not break the result from a user perspective. Creating integration tests requires us to focus on what would affect the customer and what we should be providing to the customer. These tests take the outputs and verify the page appears as it should, error messages are being displayed in the HTML as expected and the processing that was done by the application is correctly output to the user.
To our team, TDD is a crucial part of developing stable software. Having used a TDD based approach we have increased productivity, stability and developer confidence (as we can now make changes to the application and be confident that any impact on existing functionality will be obvious immediately through failing tests), whilst still being able to meet existing deadlines. For projects where requirements and information are available about how an application should work, a TDD based approach can ensure quality, reliability and decreased development time. For a successful implementation, we recommend a mixture of unit tests and integration tests, unit tests to cover critical parts where data in the application is changed or calculated. Integration tests to verify the application requirements meet customer expectations as outlined in the requirements and user stories.
Thank you very much for your time, we hope this has been a pleasant insight into how appropriate development testing can improve the quality and reliability of applications, while also reducing development times and support costs.