In this article, I wanna talk about my experience with Test Driven Development (TDD) and its implementation on software engineering projects.
Disclaimer: since it’s based on my experience, this article can’t be really correct so take this article a bit of grain of salt.
What is Test-Driven Development?
Well, basically test-driven development is we write the test code first before writing the actual implementation. The purpose of TDD is to make our code more robust and cleaner. The TDD itself has cyclic three phases:
- RED, in this phase, we create a test code for the implementation that will handle every possible scenario that could happen. We can also code the mock objects that are required for the testing in this phase.
- GREEN, in this phase, we create the implementation code that will pass the test created before.
- REFACTOR, after the implementation has passed the test, we can restructure the implementation to make it cleaner, more optimized, or more maintainable but still doesn’t break the correct logic.
Rethinking About The Test Metric
Some say that the good test is the one that fulfills 100% code coverage. But I don’t think that’s necessarily true. Code coverage only tells us the number of codes that are covered or called by the tests. Therefore, we can achieve this 100% coverage by creating assertion-free test code such that it executes all lines of implementation codes. That is why the code coverage itself doesn’t accurately represent how good or proper the test really is. Another problem is some codes can’t be tested because that code doesn’t contain our own implementation (for example code that’s generated from a certain script). We can overcome that problem by putting that kind of code into the coverage exception although that doesn't guarantee 100% coverage. Martin Fowler said that around 80–90s coverage should enough if we really make the proper tests. I can say that the tests that we made really do well if your code rarely creates bugs in the production and you don’t hesitate to change the code. So the main point is, create tests that cover all possible scenarios in all of ours implementation codes, put the untestable code as the exception for the coverage & don't stick too much on the code coverage target. Additionally don’t too stick too much on the pipeline badge result like if I made an implementation and push it to the repository don’t expect that the pipeline badge would also be green because then again the reality is, weird stuff or human error like typo can also happen in the progress.
TDD is Investment in Software Development
Test-Driven Development isn’t that easy. You have to invest your mind & your time in that matter like planning the scenario, learn libraries for the testing before writing the test. Weird stuff can also happen like the test on the local is passed but when I push it to the CI, the test failed instead due to the dependencies, mismatch between the mocking and the implementation, and other miscellaneous causes. TDD also actually slower than when we do the implementation first. Now the good thing is, TDD makes our code more cleaner since we plan the scenario first so the redundant code can be minimized. TDD can make our code first so we don’t have to fear when we have to change code especially when we use agile methodology. Therefore TDD makes our code more maintainable and robust. TDD also ensures the integration process runs more smoothly. Like investing, it takes a lot of time and knowledge but there is a huge payoff in the end.
How I Use Test Driven Development in Software Engineering Project
After that long talk about the theory and my thoughts on TDD let’s jump into the implementation. In this example, I applied TDD on a method on the user repository which fetches the user database entity based on the user’s ID. Anyway, the code is coded in Go.
In this phase, I created a negative and positive test for each that method.
As you see, those two test cases use mocking on the SQL. I’ll talk about the mocking later on. Here, on that positive test we simulate that when we put the ID that really exists, we get the user entity that really matched with the corresponding ID. In that example, I insert a user that has 1 as a user ID and makes sure that when there is a method that puts 1 as the argument that entity should be returned. While on the negative test, the test ensures that whenever there is a nonexistent user ID put on the method, the method should return an error instead. Here on that example, I put 10 as the method input and user entity with 10 as user ID doesn’t exist in the database, so there must be an error.
Here in this phase, we create an implementation that will pass the created tests.
In that phase, I created an implementation for the method. The method basically fetches user that has matching user id. That method returns the corresponding entity and error.
In this phase, we can refactor or restructure the code that looks neater or more optimized than before. Worry to break the logic? Don’t worry, we have test code on our back to ensure the change doesn’t break the correct logic.
In refactor phase, I added the comment on the implementation since the linter requires to put a comment on every method on implementation except the private method. Here the GetByID is a public method (in go, a public method is signified by uppercase on the first letter while a private method is signified by lowercase on the first letter). I don’t put much comment on it since the method is already self-explanatory. The change is on the comment so it absolutely doesn’t affect the logic of the implementation.
It takes a lot of time for getting used to the TDD paradigm. I’m still trying to get used to it until now. But that investment is surely worth spending on especially when we work on large or team projects since we can make our code more sustainable in the future. But if it’s just a fun project or some quick implementation, I don’t think you have to do TDD. TDD should be applied to a project that involves collaboration or a project where sustainability should be put into consideration.
That’s all folks about TDD from me. See you in the next post & have a good day!