TDD Example in Software Development (Part I)
Take a look at how test-driven development can ultimately save you time by using one of the methods presented here to turn your test green.
Join the DZone community and get the full member experience.
Join For FreeAfter thinking about what type of TDD article to write 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, and people get lost in the definition, especially on the part of "unit." People think that unit is a function or a class when 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 and is based on two very simple rules that will lead us on the right path:
Write code only when we have a unit test that fails
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 that follows a cycle of Red, Green, Refactor and follows three laws:
You can not write any production code until you have written a failing unit test.
You can not write more than one unit test that is sufficient to fail, and not compiling is failing.
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 even 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 generates a real bug and 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, as 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 confirming that it is correct as quickly as possible. This tends to lead to less testing, which at first you may think is positive since you go faster, but it is not 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, listening 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, we go straight to the part of implementation of what we have in our heads, and 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, triangulation is the most conservative technique to achieve the implementation we are looking for and he is right. First step 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; with our test in the 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, start triangulating.
After several internal debates in Apiumhub (by email, open spaces and informal talks at lunchtime), how to put your tests 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, they may be discovered throughout the development cycle, and 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 either 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
Published at DZone with permission of Oscar Galindo. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments