📜 ⬆️ ⬇️

Contracts vs Unit Tests

DISCLAIMER: This note implies that the reader has basic knowledge of unit tests, which the author of these lines does not doubt, as well as basic knowledge of contract design, which can be replenished from here .

At one of the presentations on the design of the contract, one of my colleagues asked a very reasonable question about the relationship of contracts and unit tests. Postconditions in a class contract, like unit tests talk about class guarantees to its customers, and since unit tests are a more powerful mechanism in this matter (complex postconditions to express in the form of contracts are not always easy, and sometimes impossible), the question arises postconditions.

So, let's briefly consider exactly where the intersection of contracts and unit tests are located, and try to answer the question: are the postconditions redundant in the presence of unit tests?
')

Contracts

The problem with any code is that it is not self-sufficient. Of course, looking at the code you can immediately see the non-optimality of the solution, find banal nonsense or incorrect use of the programming language idioms, but it is very difficult to answer the main question: does this code do what it was written for or not?

The problem is that the code itself is not correct or incorrect, the concept of correctness is applicable only in conjunction: code - specification. (You can read more about this in the article: Design by contract. Software correctness .)

Usually this problem is solved by duplicating information in comments, which very quickly become obsolete and begin to contradict the code itself; the specification may even be formal and be located on a wiki page; it can be expressed in the form of statements, and also unit tests can be used for these purposes.

We will look at the latter method later, but for now let's move on to the penultimate one: the use of contracts.


Contracts are designed to specify specifications directly in the code in the form of statements: preconditions, postconditions, invariants and statements. Now we are not interested in all the subtleties of these issues, and at the moment it will be enough for us to consider only the preconditions and postconditions.

The preconditions of a class B method speak about a client's contract: what clients of class B must perform in order for class B to start performing its duties . The postconditions of class B methods speak of a class B contract in front of its clients: which guarantees that they will perform class B if their clients have fulfilled their part of the contract .

An example of a contract is the IList interface's Add method: if the client of the IList interface transmits a non-zero object (precondition), it will be added to this collection and the number of elements (Count) will increase by 1 (post-condition).

Unit tests


Unit tests are a very popular technique today, with skillful use of which the developer can get a number of buns, ranging from simplifying the refactoring process, to improving the design of the application by increasing modularity and weakening connectivity .

Another property of unit tests is that they essentially describe the intended behavior of classes or modules, which may well be considered as their specification. Tests are a great starting point for a new team member who wants to understand the work of a particular class, because with their help he will see the way to use the class, the necessary input data and the expected results.

It is the last aspect of unit tests that so strongly resembles the preconditions and post-conditions of a class contract. In fact, there are certainly similarities, but there are important differences.
Contracts are declarative : they describe a class guarantee to its high-level customers, but they do not say how these guarantees are provided. Unit tests are imperative : they describe the many steps that a class or method must perform in order to get the necessary results.

Contracts describe class guarantees to his clients, and unit tests ensure the fulfillment of these guarantees.

The “falling” unit test, like the violation of a postcondition, is a bug in the class code, which means that the clients of our classes should never face a violation of the precondition. The class developer cannot guarantee the fulfillment of preconditions by his clients, but he can try to fulfill his part of the contract and ensure the fulfillment of the postconditions. The exception arising from the violation of a postcondition is an insurance for the client, which he should never use. If a method for some reason cannot do its work, then it should clearly signal to its customers with the appropriate type of exception.

Contracts, unlike unit tests, are always available to clients, and with their help, the client can understand much more quickly what to expect from a class or method. Even when it comes to in-house development, contracts are the preferred way to describe code intentions, while unit tests will be guaranteed to perform.

A practical example. Interface Contracts


The need for additional libraries for contract programming is also related to the fact that the type systems of most modern languages ​​do not express the programmer’s intentions as clearly as we would like. One can only imagine how much less the number of argument checks would be if we had non-nullable reference types at our disposal, as in the Eiffel language or in functional languages.

The type system is the first and most important means of conveying intentions, but it is quite difficult to express your intentions, especially when it comes to interfaces. The interface models a certain abstraction, and each of its methods has a certain semantics, which can be understood using its name, a set of arguments and documentation. Other no less important sources of interface specification can be contracts, and, in some ways, unit tests.
Let's look at the Add method of the IList interface, which were once participants in one of the notes :

//    [ContractClass(typeof(IListContract<>))] public interface IList<T> { /// <summary> /// Adds an item to the ICollection<T>. /// </summary> void Add(T item); int Count { get; } //     } 


Looking only at the name of the method, its parameters, as well as documentation, it is impossible to clearly answer the question of which post-condition of the method, i.e. what the calling code can expect and what the class implementing this contract should provide. Whether it is necessary to add an element and only one, or not. Looking at a contract is obvious, but without it it is very difficult to understand it.

 [ContractClassFor(typeof(IList<>))] internal abstract class IListContract<T> : IList<T> { void IList<T>.Add(T item) { Contract.Ensures(this.Count == (Contract.OldValue<int>(this.Count) + 1), "Count == Contract.OldValue(Count) + 1"); } } 


Of course, unit tests of an interface provider can help in this matter, but the problem here is that there may be dozens of interface implementations from different manufacturers, which makes unit tests not the best source of specification.

Interface contracts are particularly useful because the basic way of understanding what a particular class method serves for does not work for interfaces. To understand the purpose (semantics) of a class method, we use reverse engineering, but this process is complicated for interfaces, because for this we need to analyze all possible implementations.

Contracts, on the other hand, provide additional information that the interface client can use, and, just as importantly, the class that implements this interface.

Conclusion


Contract and unit tests are not competing with each other, even though both can be used as a specification expression. Unit tests have a lot of worries, in the real system there will be quite a lot of them and it is possible to bite out of them the elements of the specification, but not so simply. Any tool should be used for its intended purpose, and our two heroes are no exception.

Contracts - describe the abstraction and say nothing about how it works. Unit tests, in turn, ensure that the implementation fits this description and that the guarantees described in the contract are always fulfilled .

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


All Articles