📜 ⬆️ ⬇️

Why study TDD is difficult and what to do about it. Part 1

From the translator: it so happened that in the Russian-language Internet there is little information about TDD and basically the mechanical actions of the developer are described. The main thing - the idea - is given very little attention. This article is an attempt to fill this gap. It is important to note that it is not for those who do not have time for tests , and especially not for those who do not realize the importance of loosely coupled architecture. The article ( original ) is addressed to those who are doing or are going to take the first steps in TDD.

Recently, I was offered a TDD-training, which included working in tandem with a very intelligent developer, poorly familiar with this technique. I noticed something interesting: many of the questions he asked were strikingly similar to those that I had myself at the very beginning of the study of TDD. It was also clear that a large number of questions stemmed from incorrect initial assumptions. The fact that we both made similar assumptions made me think about the reasons for this.

In this article we will try to understand what exactly prevents to master TDD to the level of use in everyday tasks. Then we will look at how and why TDD works, in order to provide newcomers with the opportunity to critically evaluate the information they already have and avoid incorrect assumptions that may slow down the learning process. Finally, we will look at how to use new knowledge to answer common questions that hinder the actual use of TDD.

Why is it so difficult?


I think there are several reasons. Firstly, the hype around TDD itself is quite harmful to the learning process. Depending on where the information about this technique came from, it is easy to create the impression that it is the only correct way of development, and that thanks to just following simple steps, the code begins to shine clean and filled with beautiful and obvious abstractions. However, this is not the case for everything except the simplest examples.
')
Unrealistic expectations, in turn, lead to focusing on the algorithm itself, without understanding the idea behind it, and the subsequent inefficient use of TDD. And this acts very negatively on the person whose process, which worked well during the implementation of the stack, led to failure when trying to output something very simple to a web page.

TDD is not a substitute for thinking and does not eliminate the need for design skills. In fact, TDD does not do anything on its own - the developer does everything, and this technique is only as good as the developer himself. A more realistic view is that TDD is simply a tool that can be used to facilitate work. Of course, this technique can be very useful, but the ultimate goal is to complete the task *, and not always to start creating the smallest piece of code with a non-working test.

* A poor-quality code is not considered a completed task .

Another reason for the difficulty in studying TDD is, I believe, the fact that this technique alone teaches how to use it. The usual approach to learning something new is to simply follow the rules until you gain enough experience to understand when to apply these rules and when to break. Ultimately, there comes a vision of the idea behind these rules, and that their use mainly depends on the context. From here comes the typical answer "and it depends on the situation" ("it depends" - approx. Transl.) .

So the TDD rules are extremely simple . In fact, they are so simple that they do not allow you to simply apply them to achieve the level of skill required for truly successful use of TDD. On the contrary, you need to be able to build the right abstractions, which requires in-depth knowledge of object-oriented design, patterns, the principles of SOLID and DRY, and the like. Until the gaps in this knowledge are filled, it will be impossible to apply TDD. In general, identifying the lack of knowledge in some areas, TDD does not cope well with its completion.

Understand the technique, then learn how to apply it.


I think a good way to make learning TDD easier is to focus on how this technique works. Of course, it is worth starting with the simplest examples of developing classes like Stack or StringCalculator , in order to assimilate the process “red stripe - green stripe - refactoring”. But after it is necessary to stop and think about what, in fact, this process does.

We start by writing a failed test. What for? Existing tests pass normally, which means there are no known problems in the code. Having created a test to identify the missing functionality, we clearly identify the problem that we are going to solve. Fixing the task and, consequently, its possible solutions, we make the process of working on it easier. We also guarantee that the resulting code will be covered with tests for correct implementation and protected from regression (although this can be achieved without TDD using any sufficiently deep automatic test).

But the most important thing here is creating the outline of a future architecture. It is simply done not on the drawing board (which has its advantages), but directly in the code, which allows you to immediately understand how easy it is to use and test this code. Let's see what questions arise when creating the very first, non-performing test:

When creating a code, all the questions listed should sometime be asked, and the strength of TDD is in providing a convenient tool for this. Instead of a process to resolve questions one by one with the prevention of potential conflicts as they arise, TDD allows you to ask the abstract question “How do I write the next test?” And in the process of solving it answer a number of specific questions. That is why TDD is not better than a programmer - you still have to look for answers to the same key questions, which requires appropriate knowledge and experience. TDD just makes search easier.

What exactly does the abstract question mentioned above do? As with any good abstraction, it makes it possible to consider many small components as one unit, the creation of which can simultaneously apply all available technical and other skills. In addition, finding the answer to this single question facilitates another property of TDD: providing fast and accurate feedback to the code.

The test creation process provides a comprehensive assessment of the module being developed. Preparing to use too cumbersome? Perhaps we have too many auxiliary classes (or they violate the Law of Demeter ) and we can try to hide them behind an additional abstraction. It turns out a lot of usage scenarios or fixtures? The code under test probably has too many responsibilities. Is it difficult to isolate the tested behavior or it is not clear how to check it? Probably, we have the wrong API or the abstraction itself, and it should be allocated somehow differently. With TDD, these problems become apparent instantly. Their solution requires design skills such as those required by other development methods. But creating a test at the very beginning provides an excellent opportunity to respond to errors and test the design of the module before its implementation. The cheapest time to correct a code is before writing it.

Then we write the simplest code that satisfies the test. Ironically, this is the least interesting part of TDD. We have already completed all the hard work of identifying the desired behavior, and now it remains only to implement it. Is implementation too complicated? Then you have to go back and change the test - we just learned using TDD that we tried to make a big step in developing the module. Is the implementation trivial and has obvious flaws? Great, now we know what the next test will be.

And here we are at the refactoring step. The newly created code satisfies the test, but we were focused on a very small part of the application and now is the time to look at the whole picture . If the solution was implemented "in the forehead", then you can get rid of duplication or select a separate method so that the code better describes what it does. Even more important is the identification of high-level duplication - repetitions not just of code sections, but of similar behavior, which can be separated into an abstraction or brought to a structural level.

More specifically about what to do with all this - in the sequel.

Source: https://habr.com/ru/post/191010/


All Articles