Unpopular aspects of testing
Each developer knows that writing tests is right, some developers know this from their own experience, the other from books of those programmers who know this from personal experience and know how to sell their books. I am not able to write books, and experience is not enough, but I can share some aspects of using unit tests. Below, I will try to mention less simple truths, but more original thoughts. All thoughts are not tied to any language or framework, so this article can be useful to a wide audience of developers.
The most difficult thing in writing tests is to start, because at the beginning you will have to spend time learning the framework for testing and writing the tests themselves, but the useful impact of testing will come later. Also, the difficulty is that it is very easy to choose the wrong time for testing and get disappointed in it, for example, the idea of ​​covering an already running system with unit tests instead of integration ones is almost always failing (the exception is the case when unit tests are written after creating the system, but at the same time when correcting errors, then the task of the tests is to reproduce the situation in which there was an error and check that it is not there now). A developer who is just starting to work with testing should remember that tests should be created simultaneously with the code. After this truism comes the original:
Tests - breeding entry points
It is hard to believe that there is a developer who started thinking about testing, but has never worked on it before. Most likely, the tests that he wrote were written at the entry point to the program (in C, C #, Java are variations on the main procedure), run one or more times until they work out without crashes, and are deleted. It’s convenient for this developer to think that tests are the same entry points into the program, but there are only a lot of them and it’s easy to create them. Therefore, a working test can not be deleted. In addition, you can get rid of the main entry point until the last moment (that is, if you are developing a console program, then replace it with dll) and use only tests for this purpose. Next comes the thought, the consequence of which is TDD:
')
A test that never fell - a worthless test
This is easy to prove: consider two twin programmers, one wrote a working program right away without using testing, and the other wrote it just the same on the go, but with test coverage. As a result, we have two identical programs, but more time is spent on the second one, and therefore it costs more. If we introduce the condition that the tests are not useless, then it follows that a good test should at least once fill up the program. Interestingly, the concept of TDD (tests are written before the code that they are testing is written) derives from this statement - if the code is written after the tests, this will ensure that the test is full.
Tests - REPL in statically typed languages
Read, Eval, Print, Loop - this is what the REPL stands for. Probably, in all dynamic languages ​​it exists, for example, in Ruby it is irb, in general, REPL is a console that immediately executes the code that was entered there. The REPL is useful when you need to quickly check something, for example, in ascending or descending order by the standard method Sort (). Other uses include loading the written code into memory and quickly testing it. Often, a REPL implementation has one good feature — the history of typed commands can be saved to the source file, as well as the result of their execution. This allows you to write code in the following way: I will try to make a connection to the google server — a damn mistake, and if so, great; what else is needed, and parsing the result - this regex should work, and I will test it on this data set, what? an error?, and if you do not run the greedy version - it works - a victory, then it remains to keep the context, edit it and the program is ready.
Unfortunately, statically typed languages ​​are not friendly with the REPL, but it can be replaced with unit tests. If I’m not sure about the behavior of the library function, I’m writing a test. I also noticed that when a significant part of the system is already written, I start writing code in tests, and when the new module is ready, I refactor, isolating the functionality from the test into a separate module, and the test parent turns into a test, testing this module. Thus, tests perform the same role as REPL.
Tests increase code inertia
This is a controversial property of tests, with poor design tests resist code changes. The controversial point is that bad architects scold tests for disturbing them, and good architects praise them for telling them where their mistakes are.
Tests - typing in dynamic languages
For me, the tests were discovered by a person who actively promoted Ruby, and I tried to object to it, protecting languages ​​with static typing, referring to the help of the compiler. His answer was - why the compiler check is needed if the code is covered with tests. This example illustrates the fact that the testing culture is developed more by programmers in dynamic languages. Another proof of this is that google generates approximately the same number of pages for requests “java tdd” and “ruby tdd”, but for requests “java” and “ruby” the difference is significant.
Test - theorem, code - proof, testing - formal verification of proof
Partly, this statement is a consequence of the previous one and is connected with the Curry-Howard isomorphism, but I have not yet gone into this area of ​​mathematics and programming, so this statement is based more on intuition than on strict theory. I will try to explain this statement with an example. Let the program be an emulation of the forestry fire service: there is a patrol that patrols a route 3 times a day, there are fires that appear randomly in different places, if a fire falls into the patrol area, it is considered detected. The following statement can be considered a theorem (an invariant of the program): at the end of the day, those and only those fires that are outside the patrol's area of ​​operation or those that arose after the start of the last patrol will remain undetected fires. But on the other hand, this is the words described test, verifying the work of the program. I think after this example, this aspect of testing becomes clear.
Collective responsibility for the code, but personal responsibility for the tests
Working in a team, I often had to either be indignant, or to hear outrage in my address about the following content: “What the hell did you touch my code ?!”. In fact, the meaning of this phrase is not that someone is offended because its code was changed, but by changing the code, the author of the change implicitly changed the invariant that the author of the code meant. This situation is easy to avoid if you use testing - all invariants that are in the code will be embodied in the code of tests. In this case, only the cries “What the hell did you touch my test ?!” would be heard, which you can get rid of by forbidding the company's charter to change other people's tests. This approach will add flexibility, since it will not be necessary to coordinate changes to the code with its author. Bearing in mind the previous statement, we can say that this approach has been practiced for centuries - there are nominal theorems, for example, the Pythagorean theorem, which have a lot of non-nominal proofs.