Why write tests? When to write them? When not to write them? Habr was and remains indifferent to these and other eternal questions, and more than one habrovchanin spent a sleepless night, coming up with convincing answers to them (so that opponents finally shut up).
I decided to take the enemy not by skill, but by number, and therefore here are 19 reasons why tests should be written (in chronological order). So tests
- They help develop the API of the class being created. Instead of inventing the interface of a class, you develop it in the process of writing a test.
- Helps to develop an application architecture. At a minimum, this is a low-level architecture — a way for classes to interact with each other. Experience shows that such an architecture, as a rule, is the most flexible and economical.
- Check if a specific piece of code is working right now.
- Check if a certain piece of code will work after the changes have been made.
- Document the functionality of individual classes.
- Document the behavior of the system (from the user's point of view).
- More has not yet come up, maybe someone will tell?
It is clear that the test of the test is different, and far from all tests are capable of bringing such great benefits. Let's try to figure it out.
Writing tests before the code or after?
Everything here, more or less, is understandable. When we write a test after the code, we lose the invaluable opportunity to allow tests to help us in writing code. In my opinion, this is the greatest value of the tests. However, it is also the most difficult to understand, because here it is required to shut up the internal Architect and let the tests run.
')
The second important point: according to the highly experienced, the tests postponed until later remain unwritten in most cases.
Verdict: we write tests after the code only in those cases when you need to quickly cut the money, not really worrying about the consequences.
Which piece of code to test?
There is no “either-or”, but rather a continuous spectrum: from a single-line method to a whole script that includes a sequence of user actions. The first extreme is called unit (unit), the second is acceptance testing, and somewhere in between there is integration (does anyone know a better word?) And functional testing, and everyone understands something different by these terms. In the people, unit testing is much more popular, and when it comes to Test Driven Development, unit tests are implied by default. First, they are much easier to write - in the sense that it requires less code, and secondly, this is due to the fact that developers like to write developer tests (more about them later).
Let us see how these and other tests relate to our list of good things:
Unit tests:
- They help develop the API of the class being created.
- Helps to develop an application architecture.
- Check if a specific piece of code is working right now.
- Check if a certain piece of code will work after the changes have been made.
- Document the functionality of individual classes.
As you can see, the benefits of a huge amount, lack only one (the most important) - checking the correctness of the entire system, or at least some meaningful piece of it. As a set of healthy neurons can produce a diseased brain, so a set of properly working classes does not guarantee the normal operation of the system. Even if we test the interaction of classes with each other, the modular test will only give us a guarantee that this class sends certain specific signals to its neighbors. But whether these signals of universal harmony are needed, the modular test will not tell us.
And here comes the integration test. It is harder to write, because it is necessary to create a context consisting of several parts, it is harder to maintain, because if the test has fallen, it is necessary to sort out for a long time what piece of code to correct in order to raise the test, and others did not fall. But now we can be sure that our system behaves correctly in certain specific conditions.
Does the integration test help design the application? It does not exist by itself, it checks a huge piece of code, and by itself this piece can be organized completely ugly. But if we start with the integration test, and then, in the process of refactoring, we will add unit tests to separate sections of the code, then this will bring truly countless benefits.
What should we test for?
Most developers will give an obvious and, frankly, quite
blunt, simple-minded answer to this question: we will test classes and their methods. No sooner said than done. For each class that our ingenious chump has invented, we start a test class. For example, if we have a MyClass, we ingeniously start the class MyClassTester. Further, since we already absolutely precisely know that we have the DoSomething method, we get the TestDoSomething method. And, most interestingly, we sleep well after that at night.
They say that in Visual Studio there is even a team that automatically generates this whole thing.
If we ask the customer, well, or even for example, for example a tester, then we will have another very obvious answer: we will test the behavior of the system. We have a specification, or there user stories, we take and rewrite them in executable form. And now this is work for the brain: it is in the process of writing such tests that this process, sacred for many people, called Test Driven Design, begins.
The first type of test is sometimes called developer, and the second is user test. There is a tendency to confuse this division into modular and integration tests. I declare with all responsibility that this is not so. Sometimes you can turn developer tests into user tests by simply renaming and rearranging test methods. A typical example is search. We can have one class responsible for selecting data for several parameters. In the case of developer tests, we have one test class with a bunch of tests. We divide it into classes by scenarios: search by name, by date, etc. We got custom tests. How to write and organize them is not entirely obvious, but the benefits of them are enormous. In addition to the following, we get a system in which there is no redundant testing. This means that we can change the implementation of the functional without fear that some tests will fail (namely, those that are testing not the features, but the way they are implemented).
So, developer tests:
- Do not help to develop an API of the class being created. After all, we have already invented all the classes and methods.
- They help develop the architecture of individual methods and interaction with other classes.
- Check if a specific piece of code is working right now.
- Check if a certain piece of code will work after the changes have been made. However, it is not clear whether it works as the system needs with new requirements.
- Document the functionality of individual classes. However, most often, in order to understand this, you have to go into the code of the method.
- Do not document system behavior.
Custom tests:
- They help develop the API of the class being created.
- Helps to develop an application architecture.
- Check if a specific piece of code is working right now.
- Check if a certain piece of code will work after the changes have been made.
- Do not document the functionality of individual classes.
- Document system behavior. At the same time, it is possible to organize these tests so competently that the behavior of the system will be clear from the name of the test (no need to dig into the code). There are even systems that automatically generate documentation based on test names.
That is, if I want to understand what this class is doing here, I will have to look at the code of the class itself. To understand the example of its use, I will look at the developer test code. But in order to understand how the system is doing this or that useful thing, I will look at the user test code.
Verdict: despite the fact that I have always fiercely ignored developer tests, I am ready to admit that in certain situations they can be useful. But I still choose custom.
Final thoughts
I did not invent anything conclusive, so I leave everything as it is.