David Heinemeier Hansson (DHH) wrote an article which started a discussion about test-driven development (TDD). In this blog post, I would like to put forward a counter argument, namely that TDD is still alive.
Technical knowledge often works like literature - we are free to reinterpret it and, in doing so, think outside the box. With time our experience gets broader and broader, and we can look back and find new truths in old knowledge. The first approach might be unsuccessful, but perhaps the second leads us to a better understanding of TDD, and a greater ability to use it...
Enough. No more. My name is Mateusz, and I write software test-first. I want to apologize that I needed so much time to understand it. I'm grateful for what TDD has done to open my eyes to automated testing, but we need something more - “long live testing”.
The process of testing allows the developer to think about code design - which is why we need TDD. There’s an obvious need to write unit and integration tests, which form the base of the testing pyramid, shown below:
Let’s think on a high level about Test-Driven-Development. It lets me look at design from a bird’s eye view and use a black-box technique, so I’m not worried about what happens inside a function. A developer will receive a basic skeleton of the functionality which a future class must have, and pass tests proving it possesses, after an implementation. As mentioned, developers can choose between two approaches for testing their code: black-box testing and a white-box technique which relies on testing function flow, i.e. of all statements inside a function and their variants.
Now, let’s zoom in a little bit on the nature of each kind of test. A unit test relies upon checking only one class and lets you be as pedantic as you want. It’s worth checking how methods are working and to treat them as a black box - i.e. you pass some values in, some computation happens, and you get something out at the end.
An integration test lets you examine cooperation and relationships between two or more classes or libraries in a system or application. In my opinion, TDD encompasses these two approaches and uses them as its foundation. In addition, a client often performs acceptance tests, which should show that the application works correctly, contains all ordered elements, and makes sure that it fulfills the requirements of the specification. TDD is suitable for writing acceptance tests as well as unit and integration tests.
While I disagree with DHH, his blog post contains some interesting food for thought, such as
Maybe it was necessary to use test-first as the counterintuitive ram for breaking down the industry's sorry lack of automated, regression testing. DDH
What does automated, regression testing mean? To me, regression testing means that the developer validates the existing functionality of a system, or part thereof, to check if modifications to the code could cause a threat. This kind of testing should prove that changes to code do not damage any functionality. So, it allows us to look back and invoke current tests with previous ones. We find out what is broken and what should be fixed. Frankly, I do not know what DHH means by “automated regression testing” because tests written with the TDD technique could also be used for regression testing.
Step one is admitting there's a problem. I think we've taken that now. Step two is to rebalance the testing spectrum from unit to system. The current fanatical TDD experience leads to a primary focus on the unit tests, because those are the tests capable of driving the code design (the original justification for test-first). DDH
Dear DHH, what is wrong with focusing on unit tests? They are the cheapest kind of tests which a developer or QA is able to write. And, as you can see above, they lie at the base of the testing pyramid and should constitute a majority of all written tests. The pyramid is an excellent illustration of the spectrum between unit and system tests and shows why a revolution is not necessary here.
Less emphasis on unit tests, because we're no longer doing test-first as a design practice, and more emphasis on, yes, slow, system tests. DDH
Perhaps the test-first approach doesn’t seem like a good fit for a particular design. Maybe we should find a new language which allows us to express it better? Test-first was an answer to the thick piles of documentation that accompanied a project, and test-first is more suitable for agile projects where requirements need to be able to change on a daily basis without wasting money. What about test-wise thinking when a team is working on analysis and documentation? It improves the software as a whole, and hopefully leaves you with less documentation.
There is only one point on which I agree with DHH - “long live testing”. We need well-tested, bug-free software. Also, we need to increase awareness of testing, as well as writing, code. Bugs accumulate, and, if they are not found as soon as possible, they increase the costs of application maintenance.
The faster they’re detected, the less it costs to fix their consequences in next sprints or when the end user receives the application. Remember - don’t let bugs propagate.