⬆️ ⬇️

Selective unit testing or thin controllers again

In addition to the article recently mentioned on Habré that the full 100% code coverage of unit tests is almost always not cost-effective, since it’s just too lazy to write all this. ... it requires unreasonable expenses of working time and increases the cost of supporting the code, today I would like to present to the public reflections on this subject Steve Sanderson ( Steve Sanderson ), author of the books Pro ASP.NET MVC and Pro ASP.NET MVC V2 .



Introduction


I have been writing unit tests for 3 years now and I have been doing TDD professionally for a year now. And all this time I again and again notice such a thing: for some types of code, unit tests are written easily and naturally, significantly improving the quality of the code, while for others they require a lot of effort, they do not help in eliminating flaws, but rather the opposite - become only a barrier when trying to refactor or improve.



And this is not surprising, since practically all well-established approaches in different areas of life are in fact effective only in certain circumstances, and lose their advantages in other conditions. So here: many developers will agree that it is not always in the writing of unit tests that is understood.



So the purpose of writing this note:

  1. understand what actually determines the value of unit tests for this particular part of the code;
  2. show the inconsistency of the common opinion about the need for 100% coverage and the mandatory writing of tests before the implementation of each functional block.


Test Benefits


The whole list of advantages of having unit tests can be reduced to two main ones. They allow:

  1. design the code directly while writing it;
  2. make sure the implementation really works as intended.


But there are questions: why do we need an extra system of design and testing, does not the code itself carry information about the device and the behavior of the application? and if the tests do not provide fundamentally new information, how do they confirm the correctness of the designed system? what about the “principle of non-repeatability” ( DRY )?

')

For me personally, if at the first thought of a task, the code for its implementation does not become obvious, which means you have to sit and think about it for writing, then additional help (for example, in the form of unit tests), allowing you to make sure that everything will work correctly, would be very helpful. For example, if you develop a business rule system or analyze a complex hierarchical expression, you will not immediately be able to trace all possible branches of code behavior in your mind. In such cases, unit tests would be extremely valuable.



And vice versa: if the code is simple and obvious, and at first glance it is immediately clear what it does, then the benefits of the advantages that unit tests possess are reduced to zero. For example, you write a method that receives the current date and the amount of free disk space, and then transfers this data to another method. In this case, your code speaks for itself, additional unit tests simply have nothing to add.



So, the benefits of unit tests directly depend on the degree of code confusion.



Price testing


Among the factors affecting the cost of the product are the following:

  1. time spent writing tests;
  2. time spent on correcting and reworking tests after refactoring the code or making other changes to it;
  3. fear of making any improvements in the code because of the expectation that because of them some tests will be ridiculed and all of them will have to be rewritten.




Of course, the cost of supporting the test system can be reduced by following various recommendations , but it can still remain relatively large.



According to my observations, the total cost of unit testing of a certain piece of code is very strongly correlated with the number of dependencies existing in it on the remaining parts of the code. You will ask why?



Initial creation. If the method has no dependencies on other blocks and simply works as a normal function that takes one parameter, the unit test will simply be a list of matches between the input and output data. But if a method takes many parameters and interacts with many external services through class properties, you will have to make a bunch of fake (mock) objects. But the cost of this work is small compared to the next item.



Support. It has been established that the more dependencies exist in a code block, the more often this code is forced to undergo changes (this is exactly how the code instability is determined). And the reason is clear: for a given period of time, for each of these dependencies, there is a probability of changing its signature or behavior, which will result in the need to update the code and the corresponding tests.



Please note that these problems also apply to the situation when you are using IoC ( DI ), working with clean interfaces.



So, the cost of unit tests directly depends on the number of dependencies in the code section.



Graphic representation of the cost and benefits of tests


image



This intentionally simplified diagram shows 4 types of code:



Finally practice. So what about the ASP.NET MVC?


In ASP.NET MVC, at first glance, the logic of an application is easiest to place in controllers. And while continuing to push business rules into the controller, the latter becomes too cumbersome: it accumulates complex logic, and at the same time remains quite costly to test due to dependence on many objects. The horses, people, or rather the tasks of different application layers (the so-called “fat controller” antipattern) mixed up in a bunch.



In order to avoid such confusion, independent parts of the application logic must be factored (I apologize for the expression) into the model-level classes. Then from the rest you can separate the parts that are still not consistent with the true purpose of the clean controller, and stuff them by ActionFilters, your own ModelBinders and ActionResults.



The more we structure our controllers in this way, the simpler, cleaner, more beautiful they become, ultimately degenerating into pure water the coordinators who control the interactions between other layers of the application, and without any additional logic of their own, grow into a coherent system. In other words, the better the controllers are structured, the stronger they are to the lower right part of the diagram, depriving them of any sense of testing.



The purpose of the controllers is to be just a meeting place for various APIs of various services. The code for such a controller is easy to read and binds together many dependencies. I came to the conclusion that, from the point of view of economic benefits, my work is much more efficient if, instead of unit-testing controllers, I devote time to refactoring and writing integration tests.



Conclusion


In case someone could misinterpret my words: in fact, I am not against unit testing or TDD. My main points are:



All of the above is only a description of my observations, which may well not coincide with yours.



Original article



Perhaps it remains only to recall the words of Scott Bellver (Scott Bellware): "TDD is not about testing, it's all about design."

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



All Articles