Test-driven development

Test-driven development (TDD) is part of agile philosophy, and it appears to solve the common developer's problem that shows when an application is evolving and growing and the code is getting sick. The developers fix the problems to make it run but every single line that we add can be a new bug and it can even break other functions.

TDD is a learning technique that helps the developer to learn about the domain problem of the application they will build, doing it in an iterative, incremental, and constructivist way:

  • Iterative because the technique always repeats the same process to get a value
  • Incremental because for each iteration, we have more unit tests to be used
  • Constructivist because it is possible to test everything we are developing during the process straightaway so that we can get immediate feedback

Also, when we finish developing each unit test or iteration, we can forget it because it will be kept throughout the entire development process, helping us to remember the domain problem through the unit test. This is a good approach for forgetful developers.

It is very important to understand that TDD includes four things: analysis, design, development, and testing. In other words, doing TDD is understanding the domain problem and correctly analyzing the problem, designing the application well, developing well, and testing it. It needs to be clear; TDD is not just about implementing unit tests, but the whole process of the software development.

TDD perfectly matches projects based on microservices, because using microservices in a large project is dividing it into little microservices, our functionalities are like an aggrupation of little projects connected by a communication channel. The project size is independent of using TDD because, in this technique, you divide each functionality into little examples and, to do this, it does not matter whether the project is big or small, and even less when our project is divided by microservices. Also, microservices are still better than a monolithic project because the functionalities for the unit tests are organized in microservices and it will help the developers to know where they can begin to use TDD.

How to do TDD?

Doing TDD is not difficult, we just need to follow some steps and repeat them by improving our code and checking that we did not break anything:

  1. Write the unit test: It needs to be the simplest and clearest test possible, and once it is done it has to fail; this is mandatory, if it does not fail, it means that there is something we are not doing properly.
  2. Run the tests: If it has errors (it fails), this is the moment to develop the minimum code to pass the test; just do what is necessary, do not code additional things. Once you develop the minimum code to pass the test, run the test again; if it passes go to the next step, if not fix it and run the test again.
  3. Improve the test: If you think it is possible to improve the code you wrote, do it and run the tests again. If you think it is perfect, write a new unit test.

The following image illustrates the mantra of TDD--RED, GREEN, REFACTOR:

How to do TDD?

To do TDD, it is necessary to write the tests before implementing the function; if the implementation is started and then the tests are written, it is not TDD, it is just testing.

If we create the unit tests after we start developing our application, we are doing the classic testing and we are not taking advantage of the TDD benefits. Having your unit tests in place will help you ensure that your abstract idea of the domain problem is correct throughout the developing process.

Obviously, doing testing is always better than not doing it, but doing TDD is still better than doing just classic testing.

Why should I use TDD?

TDD is the answer to questions such as "where shall I begin?", "how can I do it?", "how can I write code that can be modified without breaking anything?", and "how can I know what I have to implement?".

The goal is not to write many unit tests without sense, but to design TDD properly following the requirements. In TDD, we do not to think about implementing functions, but about good examples of the functions related to the domain problem in order to remove the ambiguity created by the domain problem.

In other words, we should reproduce a specific function or case of use in X examples by doing TDD until we get the necessary examples to describe the function or task without ambiguity or misinterpretations.

Tip

TDD can be the best way to document your application.

Using other methodologies of software development, we start thinking about how the architecture will be, what pattern will be used, how the communication between microservices will be, but what happens if once we have planned all this, we realize that it is not necessary? How much time will pass until we realize that? How much effort and money will we spend?

TDD defines the architecture of our application by creating little examples in many iterations until we realize what is the architecture. The examples will slowly show us the steps to follow in order to define what the best structure, pattern, or tools to use are, so that we avoid spending resources during the first stages of our application.

This does not mean that we are working without an architecture. Obviously, we have to know whether our application will be a website or a mobile app and use a proper framework (you can research which framework fits your needs in Chapter 2, Development Environment), and also know what the interoperability in the application will be; in our case, it will be an application based on microservices. So, it will give us support to start creating the first unit tests. TDD will be our guideline to develop an application and it will produce an architecture without ambiguity from the unit testing.

TDD is not cure-all; in other words, it does not give the same results to a senior and junior developer, but it is useful for the whole team. Let's look at some advantages of using TDD:

  • Code reuse: This creates every functionality with only the necessary code to pass the tests in the second stage (Green). It allows you to see whether there are more functions using the same code structure or parts of a specific function; so, it helps you to reuse the code you wrote earlier.
  • Teamwork is easier: It allows you to be confident with your team colleagues. Some architects or senior developers do not trust developers with poor experience, and they need to check their code before committing the changes, creating a bottleneck at that point, so TDD helps us to trust the developers with less experience.
  • Increases the communication: It increases the communication between team colleagues. The communication is more fluent, so the team share their knowledge about the project reflected on the unit tests.
  • Avoid overdesign: Do not overdesign the application in the first stages. As we said before, doing TDD allows you to have an overview of the application little by little, avoiding creating useless structures or patterns in your project that you will maybe trash in the future stages.
  • Unit tests are the best documentation: The best way to give a good point of view of a specific functionality is by reading its unit test, which helps us understand how it works instead of human words.
  • Allows to discover more use cases in the design stage: In every test you have to create, you will understand how the functionality should work better and all the possible stages that a functionality can have.
  • Increases the feeling of a job well done: In every commit of your code, you will have the feeling that it was done properly because the rest of the unit tests pass without errors, so you will not be worried about breaking other functionalities.
  • Increases the software quality: During the refactoring step, we spend our efforts in making the code more efficient and maintainable, verifying that the whole project still works properly after the changes.

TDD algorithm

The technical concepts and steps to follow the TDD algorithm are easy and clear, and the proper way to make it happen improves by practicing it. As we saw before, there are only three steps: red, green, and refactor.

Red - writing the unit tests

It is possible to write a test even when the code is not written, you just need to think about whether it is possible to write a specification before implementing it. So, in the first step, you should consider that the unit test you start writing is not like a unit test but like an example or specification of the functionality.

In TDD, this example or specification is not fixed; in other words, the unit test can be modified in the future. Before beginning to write the first unit test, it is necessary to think about how the software under test (SUT) will be, and just a little about how it will work. We need to think about how it will be the SUT code and how we will check that it works the way we want it to.

The way that TDD works drives us to firstly design what is more comfortable and clear if it fits the requirements.

Green - make the code work

Once the example is written, we have to code the minimum to make it pass the test; in other words, set the unit test to green. It does not matter if the code is ugly and not optimized, it will be our task in the next steps and iterations.

In this step, the important thing is only to write the necessary code for the requirements, without unnecessary things. It does not mean writing without thinking about the functionality, but thinking about it to be efficient. It looks easy, but you will realize that you will write extra code the first time.

If you concentrate on this step, you will think of new questions about the SUT behavior with different entries. However, you should be strong and avoid writing extra code about other functionalities related to the current one. As a rule of thumb, instead of coding the new features, take notes so you can convert them into functionalities in future iterations.

Refactor - eliminate redundancy

Refactoring is not the same as rewriting code. You should be able to change the design without changing the behavior.

In this step, you should remove the duplicity in your code and check whether the code matches the principles of the good practices, thinking about the efficiency, clarity, and the future maintainability of the code. This part depends on the experience of each developer.

Tip

The key to good refactoring is doing it in small steps.

To refactor a functionality, the best way to do it is to change a little part and execute all the available tests, and if they pass, continue with another little part until you are happy with the obtained result.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.130.201