How does unit testing simplify parameter checking?

We have an agreement to check all parameters of constructors and public functions / methods. For mandatory parameters of a reference type, we mainly check for non-empty ones and that the main check is in the constructors, where we establish the required type dependencies.

The number one reason why we do this is to catch this error earlier and not get an exception with a null reference for several hours after the line, not knowing where and when the wrong parameter was entered. When we begin the transition to more and more TDDs, some team members find that checking is unnecessary.

Uncle Bob, who is a vocal supporter of TDD, strongly advises against performing parameter checks. His main argument seems to be "I have a set of unit tests that guarantee that everything will work."

But for my whole life, I just can’t see how unit tests can prevent our developers from calling these methods with bad parameters in production code.

Please, the module testers are there, if you could explain it to me rationally with concrete examples, I would be more than happy to take advantage of this parameter check!

+6
source share
5 answers

My answer is: "This is not possible." It basically sounds like I disagree with Uncle Bob on this (by the way).

It is too easy to imagine a situation where you tested your library code for non-zero arguments, and you checked your calling code for a path that, as it turned out, provides an empty argument for the library without you realizing it, but also does not cause any problems for this particular path . You can have 100% coverage and actually a pretty good set of tests, and still don't notice the problem.

Everything is good? No, of course, this is not so, because you are breaking the contract with the library (do not give me a non-zero value) without knowing it. You can be sure that the only situations in which you provide a null argument are those where it does not matter? I don’t think so, especially if you did not even suspect that the argument was null.

In my opinion, public APIs should test their arguments regardless of whether the calling code and the API itself are being tested. Problems with calling the code should be uncovered and exposed as early as possible.

+7
source

This is a question that I have been asking myself for centuries, and still have not received a satisfactory answer.

But I believe that when it comes to checking arguments, you need to distinguish between two cases:

  • Are you checking the argument to catch logical programming errors?

    if (foo == null) throw new ArgumentNullException("foo"); 

    probably an example of this.

  • Are you checking the argument because it is some kind of external input (provided by the user or read from the configuration file or from the database) that may be invalid and must be rejected?

     if (customerDateOfBirth == new DateTime(1900, 1, 1)) throw …; 

    there may be this type of argument checking.

(If you advertise an API consumed by someone outside of your team, example 2 also applies as well.)

I suspect that methodologies such as unit testing, contract design, and to some extent “early failure,” focus mainly on verifying the first type of argument. That is, they are trying to detect logical programming errors, and not invalid input.

If so, then I dare to say that it doesn’t really matter which error detection method you follow; Each has its advantages and disadvantages. & dagger; As a last resort (for example, when you have absolute confidence in your ability to write code without errors), you can even completely reject these checks.

However, whatever method you choose to detect logical errors in your code, you still need to check user input, etc., so you need to distinguish between two types of argument checks.


& dagger;) An amateurish, incomplete attempt to compare the relative strengths and weaknesses of Design by Contract, unit testing and the “failed early”:

(Although you did not ask for it ... I just mentioned a few key differences.)

Failure before (for example, checking for an explicit argument when starting a method):

  • Writing basic checks like null protection is easy to write
  • can mix logical error protection and external input validation with the same syntax
  • does not allow you to check the interaction of methods.
  • does not encourage you to strictly define (and therefore think) your method contracts

Unit Testing:

  • Allows you to test code in isolation, without running a real application, so error detection can be faster
  • If a logical error occurs, you do not need to trace the stack to find the reason, because each unit test indicates a specific “usage example” of your code.
  • allows you to test not only individual methods, for example. even the interaction between several objects (think stubs and layouts)
  • convenient tests (for example, protection against null ) is more work than when approaching with an error (if you strictly adhere to the Arrange-Act-Assert pattern)

Design by contract:

  • forces you to explicitly specify the contract of your classes (although this is also possible with unit tests, otherwise otherwise).
  • makes it easy to formulate class invariants (internal conditions that must always be satisfied)
  • not supported by many programming languages ​​/ structures, as other approaches
+3
source

It all depends on the type of application you are developing.

  • Most of my time I write applications that do not expose public APIs, in this case the application should be deterministic in the sense that all parameters must and will be different from zero. In short, you should perform input validation at your system boundaries to prevent these invalid entries from entering your application, which may end in null links, etc. In this type of application, you have full control over the input verification of your application right where you purchase them.

  • If you are writing public APIs, it is not recommended that you check for null references. Just take a look at all the methods of the MSDN class that can throw exceptions, all this happens inside the API as preliminary checks, you can read the recommendations on developing the C # Framework for more information.

In my opinion, whether it is an open (or not) API application, the prerequisites for your methods are always good (these contracts are documentation for your peers who will work on your code in the future)

+2
source

I fight Uncle Bob in almost everything, but it's not this one. I vote for "fail quickly and unsuccessfully."

+2
source

This has nothing to do with TDD.

For public APIs, yes, we need to do argument checks as quickly as possible.

All tests of constructor arguments seem completely unnecessary to me, because they are NOT consumed by anyone outside the command. Why did we have zero checks? We did not trust the code calling these methods.

So what are public APIs? Any public methods? If so, then there is no such thing as internal APIs, then I think. So why use the word public? Why just say that all public methods should do zero / border checks.

I think that the main cause of the problem is the lack of trust in our own code and team members, and, apparently, we will solve the problem wrong.

0
source

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


All Articles