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 :)
What is TDD?
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.
The Three Laws of TDD
Based on the book Clean Code: A Handbook of Agile Software Craftmanship by Robert C.Martin, TDD has these three laws:
You may not write production code until you have written a failing unit test.
You may not write more of a unit test than is sufficient to fail, and not compiling is failing.
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.
Why TDD? What are its benefits?
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:
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.
How to implement TDD?
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.
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.
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.
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.
TDD in PPL 2021
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
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
And finally, we pass the test (again)!!
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
Glenda Emanuella Sutanto