What does TDD mean to us?
Global companies rarely get to write software from scratch, reappraising existing code, methods and assumptions before deciding whether or not to apply them. Access Worldpay provided such an opportunity and the team seized the chance to refresh their approach by embedding test-driven development into their work. Two years on, we asked two engineers for their views.
Most software engineers recognize test-driven development (TDD) as best-in-class methodology, and within smaller businesses, it has become standard practice. However, large organizations can struggle to embrace it, partly because of roadblocks caused by legacy software.
Another issue is the “change in mindset” that TDD demands, says senior developer Ignas Traskevicius. Engineers need to concentrate not on the precise code needed to satisfy one specific task but, instead, focus on the larger goal the software aims to achieve. That sounds straightforward but, he says, it represents a “complete inversion” of traditional approaches to development that requires engineers to “write an automated test for code that is not yet there… to think up with the most convenient interfaces for this component, even in the abstract, and then use them within the test although such interfaces may also not yet exist.”
Such mental gymnastics are difficult but, continues Ignas, they deliver significant benefits. These include “better abstractions and leaner interfaces, with cohesive code brought together.” Moreover, TDD-led components are less coupled to others, so changes made in one component are less likely to affect others. This all results in software with robust architecture and maintainable code, which becomes more important as the software becomes increasingly complex over time.
The intellectual rigor involved is precisely why TDD has become fundamental to Access Worldpay’s approach – so much so that the maxim “If it’s not tested, it’s broken” is one of the team’s guiding principles. But exactly what impact has TDD had in practice?
Code to the test
“The important difference TDD has over other forms of tested software is the ‘driven’,” says Alastair Donlon, principal developer. “You start from the ‘test’ first – it drives everything else.” Ignas adds: “You have to design the test in the abstract, without thinking about how it will affect implementation. Only once the test is complete can you start working on the production code to satisfy it.”
This process pushes engineers to start by breaking down the code to the point where they can control its dependencies. As a result, every individual piece of software is written with sound architecture and clear code which engineers can continue to support, even when combined into much larger packages.
By contrast, says Alastair, “a test-afterward method creates software with intertwined dependencies that make it harder to test the code in isolation. It becomes very difficult to move past that, to write a test for that code and to use it in other places.” Ignas goes further: “Applications that aren’t covered by TDD or some form of automated test are broken – if not immediately, then sooner or later.”
Tests breed success
Since it was introduced, TDD has worked well for the Access Worldpay team. “Wherever TDD is used, I see successful software,” says Alastair. “We’ve never seen any types of bugs or issues going into production and applications are more robust and easier to develop.”
Of course, any software development, whether or not TDD is involved, faces the possibility of under-testing. This is the chance that one or more test cases, if overly limited, may fail to address some functions and so leave them unproven.
The Access Worldpay team tackle this issue by using more tests to triangulate their code. A simple example is an addition. Engineers would start by writing a test to give a specific, expected and minimal solution (e.g. 2+2=4) and then hard-code the response to be that solution. They would then triangulate this code by designing another test for the same code. The second test would have slightly different inputs, expected to produce slightly different outputs (e.g. 3+3=6).
To make the code pass the second test, they make very simple changes that may, initially, be architecturally unsound. Once the test has been passed, however, the engineers can refactor the code to ripple throughout the software, and then test again. “This looks better from an architectural point of view and you still have the tests showing you that you haven’t broken the code,” says Alastair. Further triangulation testing can then identify other functions that remain unconfirmed.
First principles last
Ultimately, the team has found that using TDD encourages them to clarify their thinking and go back to first principles, says Ignas. “The test is the very first component – it gives us the chance to put software that hasn’t yet been written through its paces,” he says. “It allows us to think about the interface and to consider the most convenient and comfortable way to use each component. Indirectly, TDD drives the user experience.”
- General articles on the TDD cycle and rule
- More specific information on triangulation
- An overview of some research on TDD practices