No developer of sound mind and sober memory when developing complex applications (> 100K LOC, for example) will deny the need to use testing in general and unit testing (unit tests) in particular. This is as true as the fact that every developer will try to eliminate meaningless work from the creative process of creating an application. Where is the border that separates the need from meaninglessness, if we are talking about unit testing in the context of complex applications? I set out a couple of my thoughts on this subject under the cut.
Unit testing, or unit testing (unit testing) is a programming process that allows you to check for the correctness of individual modules of the program source code.
The idea is to write tests for each non-trivial function or method. This allows you to quickly check whether the next code change did not lead to regression, that is, to the appearance of errors in the already tested program areas, and also facilitates the detection and elimination of such errors.
Everything is clear. There are 5 lines of code:
class Calculator { public function add($a, $b) { return $a + $b; } }
There is a unit test for it ( already 10 lines, but this is normal for a unit test, when the number of lines in the test exceeds the number of lines of the code under test ):
class CalculatorTests extends PHPUnit_Framework_TestCase { private $calculator; protected function setUp() { $this->calculator = new Calculator(); } public function testAdd() { $result = $this->calculator->add(1, 2); $this->assertEquals(3, $result); } }
The test allows you to check the logic of the code and detect an error in case something or someone violated this logic. Since unit tests check the code separately from the entire application, they are very simple and very fast, and are able to evaluate the “health” of a significant part of the code in a very short time.
Unit tests by themselves do not guarantee the correct functioning of the entire application, but are the first, basic stage in the list of tests:
(The picture is taken from the Internet solely for its triangular form and the layer-by-layer enumeration of some types of testing; percentage figures and other details are insignificant in the context of the above; )
" ... write tests for each non-trivial function or method. "
With the code that implements the logic according to a given specification, everything is clear. And what to do with the code, where there is no such logic? For example, with accessors in DTO-like classes?
The network mind regards such cases as trivial, despite the non-zero probability of having an error in the code like:
public function getDateCreated() { return $this->_dateUpdated; }
The likelihood of such an error greatly increases with the massive use of the Find & Replace advanced technology code, and the desire to apply progressive technology increases with the growth of the project and more complete immersion in the details of the subject area.
A compromise between meaninglessness and necessity may be to accessors when preparing data for testing other, less trivial classes (such as services) that use DTO-like objects, or check the result after asserts on return:
$in = new InDto(); $in->setId(4); $out = $service->callMethod($in); $this->assertEquals('success', $out->getStatus());
Although in this case the principle of isolation of the code under test from the rest of the application code is violated. Well, he is a compromise in order to choose the third one from two very good options, not very bad.
All object-oriented developers sooner or later came across the abbreviation SOLID ( who didn’t come across - it's about time ), in which the first letter " S " corresponds to SRP - "the class should have only one duty ". The methodical and consistent application of this principle leads, on the one hand, to simplifying the code of a particular class, and on the other, to an increase in the number of classes and the connections between them. To overcome the growth problem, a modular approach , a multilevel architecture, and inversion of control are used successfully. In the net balance, we have a solid profit in the form of " simplification of the code of a separate class ", right up to such implementations of individual methods:
public function execute($in) { $order = $in->getOrder(); $this->_service->saveOrder($order); $this->_otherSrv->accountOrder($order); }
Testing such a code again balances on the edge between meaninglessness and necessity - in fact, the test boils down to creating caps / mocks and checking that the corresponding methods will be called in the appropriate order. Exactly the same effect can be achieved much faster if you make a control copy of the source code file and report all the deviations of the current code from the control copy when testing.
A colleague, Dimitar Ginev, recommends that, in such cases, the code be divided into two categories of classes ( orchestrator and decision makers ) and only the code of the second category should be covered with tests.
A wonderful metric for assessing the quality of a code is% of the code coverage of tests. This percentage can be calculated both for a separate source file and for the entire code base of a project (for example, coverage with Magento 2.1.1 unit tests). Code coverage allows you to visually assess problem areas in developing source code and should strive for 100% coverage of meaningful code. Moreover, the more complex the application being developed, the more significant code in it, and 100% coverage begins to have more significance. Unit tests are very good candidates for using their results in calculating this metric again because of its independence (from each other and from the rest of the code that is not being tested at the moment) and the speed of execution.
Coverage of all the code in the project can be increased to 100% in two ways:
@codeCoverageIgnore
in PHPUnit);The first method implies that tests should also be created for each trivial function or method , which increases meaninglessness. The second method is fraught with the omission of coverage tests of non-trivial code, which adversely affects the need.
Since the community agrees that there is no need to test the trivial functionality, it is quite obvious that the simpler the code or the more ingenious developers are, the fewer reasons to create tests in general and unit tests in particular. And vice versa, the more complicated the code or the more mediocre the developers, the more reasons there are. Ie, if you are alone developing a project for 100K lines of code, then you can easily do without tests in general, but as soon as another developer connects to the project (not as ingenious as you), then the need to create tests increases dramatically. And if this developer is also a junior, then tests become vital, since even your genius can save the enthusiasm with which junior introduces errors into your favorite code.
If at the initial stage of development it is quite possible to exclude the trivial code (accessories and orchestrator) from unit testing, then the more the project becomes and the more people work on the project, the less trivial code remains in it. In the limiting case, when the code is publicly available (i.e., a pull request may come from some bored parking guard who decides to be a programmer that night), every line of code, even the most trivial, should be covered with unit tests.
Source: https://habr.com/ru/post/310826/
All Articles