Test-Driven Development: What and Why?

Source: codica.com

This is written to fulfil individual review criteria for PPL Fasilkom UI 2021.

Test-Driven Development (TDD) is a practice that is exceptionally mainstream in the world of software development. It is a practice of developing software by writing tests and then writing the minimum code to pass those tests. But, what exactly is TDD? Why is it a best practice? Read more to find out :)

As I mentioned earlier, TDD is a software development practice that focuses on creating the tests first before writing the implementation codes. It is an iterative approach that combines programming, creating unit tests, and refactoring.

Based on the book Clean Code: A Handbook of Agile Software Craftmanship by Robert C.Martin, TDD has these three laws:

First Law
You may not write production code until you have written a failing unit test.

Second Law
You may not write more of a unit test than is sufficient to fail, and not compiling is failing.

Third Law
You may not write more production code than is sufficient to pass the currently failing test.

Hence, TDD is not just about writing the tests before the code. That rule is just the tip of the iceberg. We have to consider those laws to be able to practice TDD in the best way.

I have been wondering about this as well. I wonder why we have to do TDD and why it’s a best practice. I’ve always been told to implement TDD since I was in my 3rd semester, and I didn’t get the idea back then. However, after doing my PPL project, I start to gain new insights into TDD. Here are some of the many reasons to practice TDD:

Iterative feedback
As we have to write the test first, our implementation code is required to pass the test. If we write, even if it’s a minor bug, we will immediately get feedback that our code has an error. This keeps the code’s maintainability because we don’t have to wait until the whole software is finished to get feedback. So that with every slightest change to the code, we can immediately know about it. It helps developers to maintain their code, especially when the project is big or is getting bigger. Imagine having all those feedbacks at the end of the project, and it turns out that it has a lot of errors; that’s the worst nightmare ever.

Higher code quality
Unit test means testing the smallest part of the code. Hence, a unit test will always be small. Small that is intended here is it covers a specific functionality. After writing the unit test, then we can write the implementation code. By looking at this pattern, the implementation code that will be written will likely be modular because we always write them incrementally by functionality. Sure, having our code modular is a good thing because it makes our code is easy to maintain and reusable. Making it reusable can prevent duplication in our code. Thus, the code will be so much cleaner. So, in conclusion, the code quality will be much higher.

Identify functionality issues
TDD makes it easy to spot functionality issues even as the software is being built, allowing them to be resolved more quickly. When we need to develop an entirely usable application, TDD will ensure that our application accomplishes its purpose.

Safe to refactor
When we refactor code, there is a risk that it will break our code, making us afraid to do refactoring. With the automated tests created, we don’t need to be scared to refactor because we can catch those bugs before publication. When using automated tests, proper warnings will be given if breaks are found in our code.

Now that you know what TDD exactly is and why we should implement it, maybe you wonder how to start to implement TDD. Is there any “guide” that we can follow to implement TDD? This section is for you!

To start implementing TDD, these are the “steps” that we have to follow.

  1. RED
    In this step, we have to write the unit test based on what we expect our implementation code will do and return. Based on its name, it’s likely that the test will fail at this step because no implementation codes are made yet.
  2. GREEN
    In this step, we have to write the implementation code based on the test we’ve already made in the previous step. We have to write the minimum amount of code to pass the test. It’s expected that the implementation code will pass the test.
  3. REFACTOR
    After making our working code from the previous step, we can refactor the code here if necessary. The purpose of refactoring is to make the code cleaner and more effective, and efficient. Always remember that refactoring can’t bring up breaks to our code. If so, we have been refactoring the code wrong.

Repeat those steps until all features are implemented.

In PPL 2021, we are also required to practice TDD in our software development activity. These are some of the examples.

Supposed I want to create an entity named DietelaProgram with its GET API. So firstly, I have to write the test first.

Here, I wanted to make sure that the DietelaProgram GET API works perfectly. First, I tried to send a GET request to the desired endpoint. After that, I also tried to get all the DietelaProgram instances in the database then create a serializer from them to get the same response format as when we get it by hitting the endpoint. Eventually, I compared them by using assertEqual function.

After getting that “red”, I have to write the implementation code.

To pass the test, I have to make the model and the endpoint. Using DjangoRest, I can create both of them by using Django models and ReadOnlyModelViewSet. Of course, I have to set other things as well such as serializer and URL to make it works.

Suddenly, I want to add a new field for DietelaProgram entity named unique_code and use it for each instance’s string representation. Even if it’s just a small change, I have to make the test first.

Then, I have to make the implementation code that passes the test. Thus, I just have to add the unique_code field on the DietelaProgram models.

And finally, we pass the test (again)!!

Another example.

Supposed I want to have this formula that takes the client’s total vegetable and fruit score as a parameter and returns a corresponding response (where there is 2 possibility of responses) based on the parameter. But we have this restriction that the total score can’t be less than 0 or more than 8.

As usual, we have to make the test first.

We learn something new here. The unit tests that we made have to cover all possible conditions. If we want to have restriction on something, it’s better to have tests for both the valid and invalid condition.

Then, we have to write the implementation code.

Now that we pass the tests, we ensure that our code is working as expected. But suddenly we thought of making the error message more informative. Thus, we have to refactor the code.

And we’re done! We have implemented TDD successfully! We can see that it’s very straightforward and simple to practice TDD right? :)

That’s all for now! I hope this post will make you interested in implementing TDD!

Have a nice day :D

Author:
Glenda Emanuella Sutanto
PPL-D 2021

an aspiring software engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store