Many programmers who once tried to work in the style of TDD, face numerous challenges and throw testing. They note that unit testing complicates the design process, making it too slow and more difficult because of the increasing volume of test code that is too difficult to maintain. Communicating with developers, we noticed that they all face similar problems and make the same mistakes. But as a rule, all they blame it tests.
These critical remarks about TDD gave me an excuse to conduct additional research materials on the Web, on TDD, but also do a little analysis of our way to the use of TDD in the everyday practice of programming experience and some familiar to us developers. In this article I will tell you about errors that many beginners TDD-practice permit, of disadvantages that may be associated with testing and how to avoid these disadvantages.
Let’s start with the fact that most developers misunderstand the idea of TDD. The most typical misunderstandings about TDD: «We are using in their work or SimpleTest PhpUnit, it means that we work in the style of TDD.» This is not necessary. People who write (or try to write a) unit tests for your classes – not necessarily deal with TDD.
Once in a TDD-mailing, I stumbled upon some kind of classification of patterns of use of unit tests by developers:
Only the first option can be considered a full TDD. The second option – the so-called method of test-first development, ie TDD without the first D – driven. The third variant is no relation to the TDD itself has not, and should be applied in parallel in the form of acceptance tests.
TDD – this is an iterative process, continuous, parallel to writing tests, and working code with a mandatory phase of refactoring.
Too many developers are starting immediately with a set of unit tests for the new class, and only then write only the class itself. As a result, they spend half a day to ensure that these tests worked. They have not only to debug production code, but also tests, and any change in the structure forces them to rewrite large portions of the test code – it’s boring, uninteresting and time consuming. This is not to TDD, or even test-first. When you’re doing TDD, you should strive to minimize the size of the context in which you work. This applies both to the test, and the working code. We usually write tests are very small steps and not try to imagine how it will look the final code. This allows very early to see the green line and dispense with debug session at startup tests. Even so it is clear how it will look all the other tests and myself working class code, we prefer to write a short test, then a small part of production code. Then you can move quickly and confidently. Of course, everyone can independently determine that for him the "small steps", but the general recommendation is this – try to reduce the intervals between tests are run. With the active development, we usually run tests every 2-3 minutes and even more often. This allows you to check written code immediately, without losing the context, that is, remembering all the details of just changing the code.
Consider the "active" and "acceptance" means the use of tests (see above). We began the practice of TDD is to "active" way to use your unit tests, drawing a UML-diagrams, and then tried using the tests to realize their ideas in the code. But gradually we have abandoned routine use of this practice. The fact is that, if developed by focusing on test-ability, it is possible to test the code, then the resulting implementation will always be different from planned, and time spent in the detailed study design, it is a pity. Now we try not to spend more than 10-15 minutes to design the session, quite a couple of small sketches on the blackboard, which will give an indicative picture, and you can start coding. By the way, I do not want to say that TDD replaces analysis phase of the project. Architectural decisions that affect the whole project, you still need to take. In the end, TDD does not replace analytical skills developer, his ability to foresee the development of the project. But TDD really can get out of the deadlock situation of architectural (design deadlock), when in general it is not clear how the implementation should look like.
"Acceptance" way to use the test distorts the meaning of the term "module". Typically, in this case, the developer writes tests for classes of higher levels (elevations). In this case, the notion of module (unit) becomes too large, so the test becomes useless when searching for errors. Such a test often increases in size and complicated, it becomes harder to read and understand. Such criteria as test-ability is no longer playing the title role, so the impact tests for design will be significantly less than in the traditional TDD.
Mandatory condition for the successful implementation of TDD is the phase of refactoring. After receiving the production code and the green band should be required to critically look at the code and make at least some steps to improve readability and understandability of code. Ideally, if after refactoring code would not have demanded the inline comments. It is also important as often as possible while running tests, so as not to collide with a red stripe. In the book of Martin Fowler’s "Refactoring: Improving the design of existing code" technique of refactoring is described in great detail.
Development through testing did not learn a couple of weeks. According to our experience and expertise of some other developers on the original study of techniques of testing goes somewhere 3-4 months. Another half a year to the restructuring of the consciousness of the developer. Total – about a year, sometimes longer. Read a book on TDD – it’s 60% code and 35% – no comments to them and 5% theory. TDD – a set of best practices (best practices), the best way proven in software development. Many of them have to try for yourself to understand their importance. TDD has become the everyday reality of your workflow. In this case, you definitely have to face difficulties. Impossible as soon as possible to quickly learn how to write good tests, create the optimal number of tests, to move straight steps (test-code-test-code-refactor), right to hold the insulation tests, give the correct names of the methods and variables, etc. That is why it is desirable that the team was a man (in XP it’s called the coach – coach), who has experience in testing, especially when introducing unit testing into the finished project. It is the existence of such a person will help get rid of many problems associated with the introduction of TDD, for example, with the odor test code. The coach may at times reduce the implementation time TDD to any team. Many times we caught ourselves on the fact that incorrectly use the tests properly spend large refactorings, we lose the rhythm of development, etc. Only after 2 years of very intense practice TDD we can say that TDD is harmoniously blended with our workflow and we can reap the benefits of its use.
Many developers (and including us) have noted that implementing TDD is best or on a new project (can be academic), or from individual classes. We do not recommend starting the introduction of TDD on real commercial projects, especially on large and complex legacy. No legacy code (legacy code) to implement TDD is much simpler, since in this case it is possible to fully control the situation and take all sorts of solutions. Our (and not just ours) experience suggests that in the first months of the introduction of TDD there is a strong desire to alter one and tighter job several times, since a change of outlook developer – he begins to realize the real advantages of a design. Therefore, freedom of action is a very important criterion of success at an early stage.
Ideally, if at the initial stage of implementation of TDD you’re not limited to the duration. The first time the introduction of TDD is a significant reduction in speed – somewhere in the 2-3 times, so many developers are frustrated and write code without tests. Once you learn the most advanced testing techniques, the rate to rise again and will be even higher than before.
Another point – since the very beginning of the introduction of unit tests, we worked in pairs. Working in a pair of generally very useful thing because it increases the intensity of training can not be lazy, emboldened in taking sometimes very important architectural decisions and improves the quality of software code.
Begin implementation of unit testing is better in small steps and direction from the bottom up. That is, first test library classes lower levels. However, the developer must first get used to the tests, learn how to read them, use them as documentation to the code, consider the classes in terms of opportunities to test them. In this case, the developer will have to deal with small modules (unit), but it’s really important at the initial stage. Only much later can move on to more complicated cases, such as tests of interaction, to an expanded use of mock objects to the regression tests.
If you have any serious problems with the tests, look at the list of most frequently appearing smell test code – maybe you have stumbled upon one or several of them. This will help understand the causes of their failures and do not quit the job. List of odor test code is available under TDD site phpclub.ru
TDD and the PLO to go hand in hand. Therefore, TDD is very poorly suited for developers who do not have to use the advanced techniques of the PLO, do not understand the PLO, and also do not have enough programming experience at all. When properly selected system architecture tests become the strongest obstacle in the development of any project, since they may increase the cost of change and support at times. Lost mobility, increase time – everyone suffers: the developers, managers, and clients.
Once on the site forum sitepoint.com I came across a very interesting statement by Marcus Baker (creator of the package for testing of SimpleTest) about object-oriented programming. The essence of this statement boils down to the fact that most developers are beginning to use the PLO, not understanding until the end (or even not understanding) in its essence. Beginner (in terms of experience with the PLO) programmer, got acquainted with the concepts (or even just the syntax), the PLO, once created several classes, each of which will pull the whole package and then immediately begins to design a large system, imagined himself as steep an architect. Of course, it does not work. After that, these developers have read many books on patterns and get stuck even more, because they do not understand why these patterns are needed and where to apply them correctly. Classes are becoming a burden and many of them go back to procedural programming, because they do not find anything good in the classroom. Experienced developers, according to Marcus, the PLO used to design not at all! Concepts such as code re-use is outdated. Classes need love for their flexibility, not for reuse. To understand this need very much time and perseverance, or the presence of an experienced mentor.
We began the practice of TDD in autumn 2003, with the typical situation described by Marcus Baker. We had our homegrown CMS, where each class was godlike, everywhere there are such buzzwords as Factory, Singleton, Fasade etc. But it was a terrible system. And in this situation, we started implementing TDD. Guess what we have come in six months? Almost the same! Only now a terrible code (though if we did not think so!) Add more tests and clumsy. Naively, we thought that writing tests – this is the TDD. As a result, we received an encrusted and slow-moving system. After that, we realized that without a radical restructuring of our knowledge of the PLO succeed, we should not expect, and we really learned a lot from scratch, opening up many new things, such as design patterns.
Any advanced TDD-practitioner (yes, in principle, any programmer) must be well understood that such a relationship between the individual classes and subsystems, as these reduce the dependence, which can be used in this technique, which is a client-oriented API, etc. You must seek to understand the essence of the most basic Pater, for example, decorator or strategy, in which cases they should be used. I personally would very much help in terms of the PLO by R. Martin, "Rapid development programs» (Agile software development). Incidentally, when we first started the practice of TDD, it seemed to us that we are very well versed in terms of OOP, but it turned out that this is not true. Incidentally, this is seen not only among us. People who have programming experience 5 or more years, after only 3-4 months of practice TDD recognize that the tests forced them to look at many things differently. Writing tests is very clearly demonstrate the feasibility of many OOP techniques that many people know so, but this knowledge they are usually dead weight.
My current view is that to begin the practice of TDD is on real commercial projects without much programming experience – this is a very serious risk.
With testing may be due to many problems if used incorrectly. At the same time, in my opinion, TDD – a great stimulus to the development of the programmer. Tests reveal many of the problems of architecture, allow you to make the working code is better, encourage professional development the developer can make less mistakes, hold refactorings, etc. As you progress, we must learn to provide a clear interface, use patterns, where appropriate, to create classes with distinct zones of responsibility and reduce the dependence between different components. But removing these benefits can only be with proper training, and require a lot of time to produce tangible effect. And if you do decide to try their hand at TDD – get ready for what you have to learn a lot.
Tests – this is not a thing in itself. But developers often go to extremes – a situation where tests are beginning to pay attention. Developers write too many tests, check out everything. And this one develops into a development-oriented tests (testing oriented development anti-pattern). James Greenwood describes this anti-pattern: "When the inexperienced or misguided programmers continue to write the tests, although this makes no sense from a practical or financial standpoint. In this case, they think that this is the TDD and adding to the system more and more tests, they increase its value. " I think that comment on this definition makes no sense.
Try to describe another case where the tests become an end in itself and hinder the development of the project. Sometimes it is associated with the smell of "Extraordinary Hope to mock objects, but sometimes the reason lies elsewhere. We are faced with this problem for a long time, but the effects are felt to this day. Its essence is this: we had a code that is harder to test because it was not designed to meet the test-ability. But without understanding the essence TDD, we instead look for reasons in the design of the system, doing its best to just write the tests. The result is a complete chaos with full or partial mock objects, methods, types doParentSomething (), incomprehensible and immense fixtures. That is, we are the fruits of unnecessary code, which also had to maintain. This test is an additional decay of the project, since they were very fragile, although in theory they should on the contrary increase its flexibility and quality. Only much later we realized that the tests clearly showed us that we have big problems c design of working code. A good working code should not have sophisticated tests! This indicates that the architecture has serious drawbacks. After proper application techniques to reduce dependencies between classes, tests much simpler, and their support is now not difficult.
Error associated with the writing too many tests before working code we mentioned earlier. No real advantage in this test case is also not given, but become a thing in itself.
TDD – is primarily a design activity, ie activities aimed at creating the system design. Tests serve as examples of the real use of the code and that is what one of their core values. Thoroughly inspect the working code in a Test – it’s too expensive. The tests should help in the design, make it more predictable and manageable, rather than time-consuming and tedious. In the end, they should allow us to make more money, do your job faster and better.
Developing a test for me was something like a silver bullet solution to all problems. I was ready with glowing eyes to prove that the tests – a panacea that everyone needs more practice to start writing tests. Now it’s a little insight. Now I realize that this is just a tool and how this tool is used depends on the person who is an instrument in his hands. In someone else’s hands it can bring many benefits, but in others – a lot of damage. If you like anything that promises a TDD, then be prepared for the victims: it is the loss of development speed at first, and harsh discipline and self-control, intensive training, etc. All those sacrifices and then be compensated. Many did not survive and return to the old style of work. But, in our opinion, and according to some familiar TDD-practitioners, as soon as the developer felt the dependence on the "Green Line", knew what full control over the code, that is, became infected with tests (test-infected), it is already never give up testing. While none of our friends does not regret the time spent on studying and experimenting with TDD. I hope this article helps someone to accelerate the introduction of TDD and avoid the mistakes that we assumed.