📜 ⬆️ ⬇️

Testing the trivial code

Even if the code is trivial, you still need to test it.
A couple of days ago, Robert Martin published the post “Pragmatism of TDD” , ( where the translation is - comment of the translator ) where he told that absolutely all code is not being tested. Among the exceptional situations where TDD should not be applied, Uncle Bob mentions writing GUI code, and I see the point in such statements, but among the exceptions there are a couple, in my opinion, illogical .
Robert Martin claims he doesn’t develop through testing:

In essence, these statements boil down to the only rule that “trivial” code, such as getters and setters (properties for .NET programmers), should not be developed through testing.
There are several issues with this TDD application rule that I would like to discuss:

Let me consider each of these points in more detail.

Causal relationship


The whole point of TDD is that tests direct implementation. A test is a cause , and an implementation is a consequence . If you follow this principle, then how on planet Earth, you can decide not to write a test because the expected implementation will be a trivial complexity? You still do not even know. It is logically impossible.
Robert Martin himself proposed the transformation transformation order (TPP), and one of the thoughts is that when you begin to direct the development of new code through tests, you should try to do it in small formalized steps. The first step is likely to lead you to a trivial implementation, such as returning a constant.
From the point of view of TPP, the only difference between the trivial and non-trivial code is how far you have come to the direction of implementation through testing. Thus, if “triviality” is all you need, the only true way is to write a single test that will demonstrate that the trivial behavior works as expected. Thus, according to TPP, you are done with the task.

Encapsulation.


Robert Martin’s exception about getters and setters baffles in particular. If you consider this or that getter / setter (or .NET property) to be trivial, why did you implement it at all? Why not, then, implement an open field in the classroom?
There are excellent reasons to avoid implementing open class fields, and all of them are related to encapsulation. The data must be implemented through getters and setters, because it will give the opportunity to change their implementation in the future.
What exactly do we call the process of changing the implementation without changing the behavior?
We call this refactoring . How can we know that by changing the implementation code we will not change the behavior?
As Martin Fowler argues in the book Refactoring , you must have a good set of tests as a safety net, otherwise you won’t know if you broke something or not.
A good code lives and develops for a long time. What was trivial in the beginning can change over a long period of time and you cannot predict whether trivial members will remain trivial for several years. It is important to be sure that the trivial behavior remains correct with the addition of complexity to it. The regression test suite is designed to solve this problem, but only if you actually write tests for trivial features.
Robert Martin cites as an argument that the getters and setters are tested indirectly through other tests, but despite the fact that this may be true at the stage of member declaration, it is not a fact that this will be true for a long time. After months, these tests can be removed, leaving the trivial member entered uncovered by tests.
You can look at it this way: you can follow or not follow TPP with TDD, but for trivial members, the time gap between the first and second transformations can be measured in months, not minutes.
')

TDD study


I think that being a pragmatist is good, but the “rule” that says that you don’t have to develop a “trivial” code through testing is simply terrible advice for newbies . If you give someone who studies TDD a way to follow which you can avoid this very TDD, then this someone, facing any difficulties, will go this way every time. If you already provide such a workaround, you should at least make the conditions explicit and measurable.
A vague condition that sounds like “you can predict that the implementation will be trivial,” is absolutely unmeasurable. You may think that you know what an implementation will look like, but by allowing you to direct the implementation through tests, you will often be surprised. This is the way of thinking that TDD directs us - what you originally thought will work, will not work.

Root Cause Analysis


Do I insist that you have to apply TDD to all getters and setters? Yes, I really insist.
You could say that such an approach would take too much time. Robert Martin ironically remarked on this:
"The only way to move fast is to move right."

And yet, let's see what the application of TPP to properties looks like (java-programmers can continue to read. Properties in C # are just syntactic sugar for getters and setters).
Let's say I have a DateViewModel class and I want it to have an int property representing the year. The first test will be:
[Fact] public void GetYearReturnsAssignedValue() { var sut = new DateViewModel(); sut.Year = 2013; Assert.Equal(2013, sut.Year); } 

Without taking anything for granted and doubting everything, the correct implementation will be the following:
 public int Year { get { return 2013; } set { } } 


Following the TPP, this code will look like this. Well, let's write one more test:
 [Fact] public void GetYearReturnsAssignedValue2() { var sut = new DateViewModel(); sut.Year = 2010; Assert.Equal(2010, sut.Year); } 

Together, these two tests encourage me to implement the property correctly:
 public int Year { get; set; } 

Despite the fact that these two tests can be refactored to one parameterized test , there is still a lot of work done. After all, we have to write two tests not only for one property, but also to do this for everyone!
“Do the exact same thing?” You ask. What are you, just what?
Oh, well, you're a programmer. What do programmers do when they have to do the same thing over and over again?
Well, if you put up with conflicting rules that allow you to avoid hurting your work, this is the wrong answer. If the work hurts you - do it more often.
Programmers automate repetitive actions. You can also do this with property testing. Here's how I did the same thing using AutoFixture :
 [Theory, AutoWebData] public void YearIsWritable(WritablePropertyAssertion a) { a.Verify(Reflect<DateViewModel>.GetProperty<int>(sut => sut.Year)); } 

This is a declarative way to test the same behavior as in the previous two tests, but one line long.
This is where the root cause analysis is appropriate. One gets the feeling that the cost / benefit ratio regarding the use of TDD to getters and setters is very high. However, I think that Robert Martin stopped there because he considered the costs fixed and the benefits too small. However, while the benefits may seem insignificant, the costs do not have to remain fixedly high. Reduce costs and cost / benefit ratio improve. That is why you should always lead the development of getters and setters through testing.

From the translator: I plan to release a translation of a review of AutoFixture next time, or to review it myself. IMHO, a very interesting tool.

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


All Articles