Applying Test-Driven Development

Rayhan Arwindra
Pilar 2020
Published in
5 min readOct 8, 2020
Source: https://marsner.com/blog/why-test-driven-development-tdd/

I get it, we all do. Testing is not exactly the most fun thing we can do in development.

But you know what’s even less fun than testing?

That’s right, debugging.

If only there was a way to anticipate bugs and errors in our code before it actually happens. That would save us so much time and stress that we can use in other parts of our life.

That’s where Test-Driven Development comes in to save the day. Test-Driven Development or TDD is basically the act of writing tests first before you write any code or functions in your program.

TDD in practice is actually a cycle, where we first write tests that fail, then we write the minimum amount of functionality to make that test pass, then finally we refactor the program to improve it without changing its external behaviour.

This cycle is usually denoted by three words: RED, GREEN, REFACTOR.

Let’s discuss them one by one.

RED

Red is the first of the three cycles of TDD. In red, we need to write the tests necessary in order to achieve the goal of the current feature or backlog.

Allow me to emphasize that, the tests can pass if and only if all the user stories required for the current backlog has been accomplished. If the tests pass before that, then we have not yet attained the desired goal or acceptance criteria for that particular feature.

So how do we write good tests that not only accomplish the user story, but is also clean and readable for you and others working with you?

Thankfully, from the book Clean Code by Bob Martin¹, we have the F.I.R.S.T principle for testing:

  • Fast: Your tests should be fast enough that it doesn’t deter you or other programmers to initialize the testing procedure.
  • Independent: Each test should be independent, it should not depend on tests that have happened previously. If the tests require data, then that data has to be set up at the very beginning of that test, not the one before.
  • Repeatable: Tests should be able to run in any environment, whether it is at your local machine, other people’s machines, or at the git repository. If the tests fail, it is because the functionality of the program is not correct, nothing else.
  • Self-Validating: The tests should tell you whether or not it succeeds. It should not require the developers to manually check the output of the test in order to dictate whether or not the test is successful. We can do this by returning a boolean, or by using the assert method available in most programming languages or test frameworks.
  • Timely: Tests should be written just before the programmer writes the code to make it pass, not after. This is especially true if we are following TDD (which we are!).

After writing the tests, making sure that it validates the acceptance criteria of our user story, and also following the F.I.R.S.T principle, we can then commit our code.

The commit message must begin with [RED], indicating that we are in the Red phase of our TDD cycle and the tests are meant to fail.

GREEN

After writing our tests and committing it to our repository, we now need to begin the implementation of our code. In this phase, our goal is to make all tests that previously failed in the Red section pass.

That’s the only goal we have at this stage, to write the bare minimum code necessary to pass our tests and accomplish the user story.

Once you are done, commit your code with the commit message starting with [GREEN], indicating that the tests you made should pass by now.

REFACTOR

Once we have written the code, this is the opportunity to refactor it. Remember that at the Green step in the TDD cycle, we are tasked to write the minimum implementation in order to pass the tests we wrote at Red.

So what can we do at this stage? Well, we can start by getting rid of unused variables and imports, renaming unclear variables and functions, and cleaning up our code.

But how do we clean up our code?

Thankfully, there’s a great article here written by yours truly. Be sure to have a read at that after you have finished reading this one!

Code Coverage

Code coverage is a quantitative measurement of how many lines or block of code is executed while your tests are running. In other words, it measures how much of your code has been tested.

Code coverage is measured in percentile format, from 0–100%. This means that if your code coverage is 85%, then there are 15% of your code which has not been tested yet.

Your goal should always be to achieve 100%, although reality might differ from that. Aiming to achieve 100% code coverage will ensure that your code is well tested and free from bugs.

TDD in Action

Now that we got the theory out of the way, let’s take a look at an example of TDD implementation in our project.

Here, a developer (me!) writes a test for a new feature at the Red stage of the TDD cycle. The pipeline then (surprise, surprise) fails, because the implementation of the tests has not been written yet.

Then, the developer codes the implementation of the feature, and the pipeline passes. This is because the tests written in the previous commit have passed, indicating that the goal of fulfilling the user story has been accomplished.

Finally, this developer refactors the app by getting rid of code that ended up not being used in the end.

Conclusion

Is TDD perfect?

No.

Making tests for some developers are just not enjoyable. Also sometimes developers struggle with the testing library, figuring out which code in the testing library tests what kind of functionality.

But is it the best practice?

As of now, yes. Especially if we wish to maintain the quality of code of our application. Testing our code before implementing the functionality of it ensures that the code is free of bugs, and fulfills the acceptance criteria for the backlog.

[1]: Clean Code by Bob Martin

--

--