Table of Contents
After a while thinking about what type of TDD article to write about and, since there are many of them and very good theoretical ones (written by influencers in the world of software development), I have chosen to develop a mini-project while explaining the key points of the development of the application, basically giving you a TDD example.
The application will be a notes manager with users where we can see how we test each layer of our application using TDD.
I would like to highlight that the purpose of the article is to see how TDD will help us develop clean code that works, showing you how TDD will provide us with a continuous design space.
Theory
First of all, let’s go over the basics to make sure all of us are on the same page in terms of theory. The first and most important thing is that TDD and unit testing are different things. TDD is a development process based on getting feedback as quickly as possible when designing our application while unit testing is a tool to prove that a “unit” works as expected.
The definition of unit testing is somewhat ambiguous, people get lost in the definition, especially on the part of “unit“. People think that unit is a function or a class when it it is not, a unit refers to a functionality/use case. This functionality may involve communication between several methods/entities.
TDD, however, is not so ambiguous, it is based on two very simple rules that following them will lead us on the right path:
1. Write code only when we have a unit test that fails
2.Refactoring / Eliminating duplication
TDD
The TDD mechanism is easy, write a test that proves the functionality that we want to implement works as expected (a use case), then write the minimum possible code to put the test in green. Once we have it in green, remove duplication and refactor. Easy.
TDD is a rapid feedback mechanism, the cycle in which one develops RED · GREEN · REFACTOR and the three laws on which it is based:
1.You can not write any production code until you have written a failing unit test.
2.You can not write more than one unit test that is sufficient to fail, and not compiling is failing.
3.You can not write more production code than is sufficient to pass the currently failing unit test.
We all know that having a bug in our code and not finding it is painful but, more painful is when you have a bug in a test. The test gives you green, you trust the test and the code you see seems to be good, but when you change production code that works generating a real bug, you do not understand why, where and how … the loss of time and the level of disappointment when this happens is brutal. Within the TDD cycle, when we see the test in red and turn it to green we control that our test really does what it should do avoiding this type of problem.
Green Bar Patterns
There are certain patterns to help us put our test in green in the fastest possible way
1. Fake It
Return a constant and gradually change them by variables. With this technique, we will take many small steps and get the test green as quickly as possible
TDD Example:
expect(suma(1,2)).toEqual(3);
function suma(a,b) {return 3}
Once we have it in green we go to the Refactor phase, in this case, we are going to remove duplication. The duplication, in this case, is not code but data, we have repeated data that at first glance is not seen, it will be better with this small change:
expect(suma(1,2)).toEqual(3);
function suma(a,b) {return 1+2}
We have put 3 but what we wanted to do is 1 + 2, which are the same numbers that we have passed as parameters to our function, now we see the duplication, we are going to remove it:
expect(suma(1,2)).toEqual(3);
function suma(a,b) {return a+b}
Done! Later we will explain you how to get the same implementation with a somewhat more conservative technique.
2. Obvious Implementation
The technique is based on implementing what we believe we know and confirm that it is correct as quickly as possible. This tends to lead to less test, which at first you may think is positive since you go faster but it is not very true as in the Refactor phase, if you have not tested all the specifications of the SUT, you can break something without noticing it.
With the obvious implementation what we are looking for (or rather, what we are getting) is speeding up the cycle by skipping one of the very important steps and is to listen to our test. When we see a test and it is red, we are forced to ask ourselves how we should implement it, we doubt our solution but we have a mechanism that tells us if we are doing it right or wrong. With the obvious implementation, this step, we skip it, we go straight to the part of implementation of what we have in our heads, we could write the test after implementing the algorithm and we would have the same result.
It is advisable to apply this technique when the implementation is not only obvious but it is trivial, where there are less specifications and we run less risk of failure, even so, we must always be very careful.
In short, how would you implement simple operations? Simply implement them.
TDD Example:
expect(suma(1,2)).toEqual(3);
function suma(a,b) {return a+b}
3. Triangulate
As Kent Beck says in his book TDD By Example, triangular is the most conservative technique to achieve the implementation we are looking for and he is right. First is the same as Fake It, we write a test and put it in green returning a constant. The next thing would be to do the refactor but we are not going to do it now, having our test in green, what we are going to do is write another test that makes it red:
TDD Example:
expect(suma(1,2)).toEqual(3);
expect(suma(3,4)).toEqual(7);
function suma(a,b) {return 3}
Now we would have two roads:
- Develop the implementation to have the two tests in green
- Continue returning constants with a simpler implementation than the real one
> function suma(a,b) {if(a===1) return 3 else return 7}
Once we have our implementation, we can think of eliminating the tests we have used to get to our implementation, we may have created redundant tests but that is something that depends on each case and each person.
What technique to use?
Well, this is something personal and subjective, something that with experience evolves. Beck says in the book that what we have to achieve is a rapid development rythm, red / green / continuous refactor, if you know what you have to develop, use Obvious implementation. If you do not know, use Fake It and if you get stuck in the design, end up triangulating.
After several internal debates in Apiumhub (by email, openspaces and informal talks at lunchtime), how to put it in green is something personal (although fake it / triangulate are usually the most used techniques) but if we come to a clear conclusion, we shouldn’t leave any specification without a test, something that with obvious implementation is very easy to skip.
Feedback: TDD example
TDD is a process where we can get feedback about our design in a fast way (I’ll repeat it as many times as necessary), the feedback will be given to us by automated tests, so how many tests do we need to be sure that our code works? One may think that the confidence at the time of developing is the factor to take into account to know when to stop doing tests, and, although it has its importance, it is something very subjective. How can we turn that subjectivity into something objective? As we have already mentioned, a test must cover a specification, if we have a test for all the specifications we have in mind, we turn the subjectivity of personal confidence into trust on the business level.
Let’s highlight that TDD was born as a process within an agile methodology (Extreme Programming), the requirements may change, new ones may appear, etc, the requirements are discovered throughout the development cycle, even once it has been uploaded to production, new requirements may be discovered.
When we find a case (which are very few) in which [we think we know] the implementation perfectly, it is trivial and we have blind trust, we dedicate some time to write the tests to cover the specifications. But there is no excuse that it takes you extra time because or you have experience with TDD or you do not have it. If you have experience, you will directly write tests and if you do not have it, practicing is the only way to get to understand TDD well. You can read a lot, find TDD example and case study, have a positive opinion about TDD but the experience is the only thing that will make you different from others.
Image source: XP Explained
If you would like to know more about TDD or get another TDD example, I highly recommend you to subscribe to our monthly newsletter by clicking here.
And if you found this article with TDD example interesting, you might like…
Scala generics I: Scala type bounds
Scala generics II: covariance and contravariance
Scala generics III: Generalized type constraints
F-bound over a generic type in Scala
Microservices vs Monolithic architecture
iOS Objective-C app: sucessful case study
Mobile app development trends of the year
Banco Falabella wearable case study
Viper architecture advantages for iOS apps
Be more functional in Java ith Vavr
Author
-
Software developer with over 16 years experience working as Fullstack Developer & Backend Developer.
View all posts