To test first or to code first?
That is the question.
This article is written as an individual review assignment for PPL CS UI 2021.
Most people associate TDD (Test-Driven Development) as a boolean, either they do TDD or they don’t.
Although 0 and 1 are booleans, there still exists an infinite amount of real numbers that lies between 0 and 1. I believe this also applies to TDD, where there must exist a spectrum between “no TDD” and “TDD at everything”. As a developer, we should find that equilibrium state, where both testing and implementation can achieve their peak potentials at a given time.
Do you ever look at a chunk of code and wonder what is going on inside the programmer’s head?
Code implementation needs tests, as much as tests need code implementation.
Testing increases our confidence in the correctness of our code, omits our fear of refactoring, and ultimately provides documentation of our code not only for our current team, but also for the future developers that inherit our work. It is undeniable that testing plays a significant role in the software development process.
When it comes to TDD, the argument is not about whether we write tests or not; it is about when to do so.
We have been mentioning TDD for quite some time, but what exactly is TDD? In the simplest words, Test-Driven Development (TDD) is the art of making tests before its implementation (or test-first approach), following the cycle of RED — GREEN — REFACTOR:
- [RED]: Writing tests containing expected input and output of a yet-to-exist code implementation
- [GREEN]: Writing the code with intended functionality based on the previous tests
- [REFACTOR]: Re-writing the code to improve code quality or change the source code without affecting its functionality
- [CHORES]: Updating files other than the source code in the project (a rather optional category)
Test First vs Code First
The other way besides TDD is the code-first approach, or I dare say, the normal way to develop a software. As the name suggests, code-first approach is the art where developers will implement the code and later decide how to cover it with tests.
Both approaches are the opposite counterpart of the other. The benefits of TDD are the drawbacks of code-first, while the tradeoffs of TDD are the key selling points of code-first approach.
Benefits of TDD
- “Automated” validation for the whole team — If any functionality changes unexpectedly either from firstly implementing the code or refactoring, the tests will notify you in a blink of an eye.
Whereas if we code first, we will have to simulate all the flows within the code ourselves, verify that every expected input and output is fulfilled, and modify the implementation again if any manual test does not match the intended functionality. This obviously works, however it can be a vicious cycle that is human error-prone.
- Code modularity — Early testing forces developers to think in terms of isolated parts of code that later can be integrated into bigger parts. Not only that it promotes clean code along with its advantages, it also equips us with the ability to envision and plan code structure with independent, interchangeable modules.
- Domino effect to increase code coverage — TDD gives us the motivation to increase our code coverage psychologically. With a set quantity of the minimum 100% feature coverage and having the tests there to point it out, writing code that has not fulfilled that criteria seems rather buggy and incomplete.
Tradeoffs of TDD
- Slower development speed — Writing tests takes time, sometimes a lot of time. If we have yet to familiarize ourselves with the framework used or the business domain, OR our requirements still remain vague and ambiguous, tests can be a huge hindrance in development process.
If we can’t even visualize the implementation from top to bottom, how much more the tests? Not to mention, if there is a need to re-design the domain modelling, the implementation should be changed, and the tests written are in vain.
- Lack of flexibility — With TDD, developers are trapped in a system where tests should be released first despite of any urgency in rapid feature development. Time is the essence, and code-first approach allows developers to postpone the phase of writing tests and gain short-term benefits.
Noting the benefits and tradeoffs of both sides, I believe there is no silver bullet solution to software development.
And TDD should not be considered as such.
However, there is an equilibrium state where we can apply both approaches and reap both of their benefits throughout our software development process.
Improvisation is as important as planning.
In the early stage, developers are usually still improvising.
We are still getting a sense on what our project’s business domain is about, learning the framework to be used, and gathering requirements from clients to recognize the product we are building. Here, code-first approach shines as it encourages explorations on the right things and boosts the performance of deliverables.
Once we are equipped with enough information on the product, framework, and requirements, planning is the way to go.
TDD shines after early stages as it improves the team’s overall productivity to implement more modular components, ensures quality code, and eventually promotes faster speed of development as the project grows larger.
(Proper) Usage of TDD and Code First
So, to answer the question,
Write code implementation before tests when we are still experimenting, as we need to do the right thing first.
Write tests before code implementation when we already know what we are going to build, as now we need to do things right.