in this post i’ll try to answer a legitimate question, relating to a comment due to my last statement that interfaces are poor contracts: why do we need contracts (in the sense of invariants, pre- and postconditions) when we’ve got unit tests right at hand, that could also test the stated conditions?
in fact, it’s very tempting to see unit tests as an all-embracing instrument to check and show that a class will behave correctly, making contracts kind of unnecessary.
i’ll try to show you that unit tests (as an instrument for test driven development) and contracts (as an instrument for Design By Contract) indeed share the same goals but aren’t competing techniques, rather than methods that could complement each other.
it’s not about verification – it’s about specification
first of all, we have to clarify a potential misconception: unit tests as an instrument in the sense of Test Driven Development (TDD) aren’t that much about verification of a correct implementation rather than about a specification of how a unit should behave. in fact, it’s the specification (not the verification), that will drive development. you can see the comeback of this core idea in the rise of Behaviour Driven Development (BDD) that mainly tries to find an adequate vocabulary to write down specifications (that of course can be verified automatically) in an easy natural way, refocussing on how a component should behave under certain conditions.
on the other side, Design by Contract is also about specifying the design, in terms of the behaviour of your components. a contract asserts truths about the design of your code in the form of runtime tests placed within the contracted units itself. these assertions will be checked as the thread of execution passes through the parts of those components, and will fire if they don’t hold.
that said, both methods share the same main incitement – the specification of a system that will drive its design.
don’t bear away
as said, the goal of bdd is to reconcentrate on the specification of a component. the other way around, it means that it might be easy to loose direction within tdd, risking to set focus on testing and verification. i have to admit, that i walked into this trap more than one time in the past. of course the whole vocabulary (test, testcase, testsuite, …) makes you believe that it’s about testing. it’s easy to become addicted to this idea, so even writing tests after implementation seems pretty ok in this sense. that’s no longer possible if you see tests as specification: you firstly have to have a specification on what a component is intented to do – you can’t start with an implemenation until that specification exist.
is the same true for Design by Contract? is the contract metaphor misleading? of course you could also define your contract afterwards, so again development of a component isn’t driven by it’s specification. but the contract metaphor uses a more appropriate vocabulary. with invariants, pre- and postconditions it’s clearly about the specification of the intended behaviour – the risk of seeing a contract as tool for verification might be much lesser.
collaboration includes clients and supplier
there’s a special area, where contracts are able to provide real benefits in regard to unit tests: oftentimes unit tests specify only the behaviour of a component as a supplier, that means the test asserts mostly the stated effects (the postconditions of a method). that’s perfectly legal: most of the prospective clients may be unknown to the designer of the component or doesn’t exist yet. since the client is responsible for the adherence of preconditions it even should not be the in the job of the supplier.
a contract on the other side also specifies the constraints under which it’s legal to call a method (the precoditions). since contracts act on runtime, those preconditions will also be regarded when it comes to collaboration between an arbitrary client and the supplier component. the following points show some aspects of this holistic view:
reliance on a proper context
most developers (including me) tend to insert guard clauses in their code despite having unit tests. they don’t seem to trust about the context in which the method get’s called (that’s of course a good idea if the context can’t be foreseen). but you’ll even find this fact in environments, where the whole context is controlled by the same team. that may be also because of the before mentioned lack of knowledge about all clients that may call your component, especially in teams with limited communication. another one might be the local (specification / test in a different file than the component) and the temporal (tests at design time – collaboration at runtime) ‘distance’ between the code and the test.
having contracts aside the code (as part of the public interface of a component) you can rely on at runtime, seems to increase trustability in the running context. This may be because of …
support of defensive programming
with Design by Contract, contrats automatically protect methods from inproper usage, like illegal arguments (in the simplest case) in the real hard world, checking potentially a wider range of collaborations than a test ever can do (tests cannot show the absence of defects, it can only show that defects are present). like Bertrand Meyer said: a test checks one (or a limited set of) cases. a contract describes the abstract specification for all cases.
that said, preconditions that will work at runtime, making guard clauses in a way redundant, because the method can rely that it’s called in a proper way. that’s a runtime feature you can’t achive with unit tests. they could only check or specify how a component should behave if it’s called in an inproper way (for example by expecting to throw an exception, which becomes a postcondition). but this one wouldn’t free you to write additional guard clauses.
clear reliable documentation
while tests can be seen as an additional artifact that specifies the expected behaviour of a component, contracts form part of the public (client-) view of a class – they are part of the public interface. personally, i’m lazy – i want to recognize the core behaviour of a building block by looking at its interface / signature, not studying an amount of code be it the building block itself or a test – but of course there are also other preferences that prefer a split between specification and implementation.
of course those contracts are more than documentation, since assertions are checked at runtime, thereby testing that the stated contracts are consistent with what the routines actually do.
integration ‘tests’ for free
this one follows directly from the initial point, when regarding collaboration between clients and suppliers: having contracts that can be turned on and off makes it very easy to test and retest parts of a program. it allows you for instance to continue to test class A while you are working on class B, in case there are any interactions you did not foresee. of course, this is kind of integration ‘testing’ – it’s not about the specification of the behaviour of a single component but more about collaboration between a set of components and most of time not in the scope of unit tests.
a contracts condition is an equivalent description of the algorithm used in a function, but written in an orthogonal manner, so thinking about such matters as pre-, postconditions and invariants help to make the concept of a class more explicit. it might encourage you to think about the conceptional model of a building block. that might lead to a clear design: obligations and benefits are shared between client and supplier and are clearly stated.
the limitations on the use of a method are clearly expressed and the consequences of calling it illegally are clear, so programmers are encouraged not to build routines that are too general, but to design classes with small single-purpose routines. while TDD guides you more towards loosely coupled building blocks (because it hurts when trying to test a tightly coupled component in isolation), DBC guides you more towards smaller building blocks with a clear resoponsibility (because it hurts to write invariants of classes with more than one responsibility or write pre- and postconditions for methods with more than one task).
contracts can take part in ‘real life’, that means under real runtime conditions, maybe during development and user acceptance test – they take effect under real circumstances while ‘real’ clients (no mocks) and real collaborators (maybe also contracted) interoperate with each other. so contracts also guarding the collaboration of muliple building blocks forcing to fulfill a collective adduced goal due to a specification. having clear sound messages that state the cause of a contract violation, helps locating where the fault lies semantically: it’s very easy to find the reason of a misbehaviour within a specific interaction, pointing directly to it’s origin (if a precondition is violated, than it’s the fault of the client, if an invariant or a postcondition is violated, than it’s the fault of the supplier)
for library users (where tests may not be accessible), contracts clearly explain what library classes do and what constraints there are on using them. they provide feedback to someone learning to use other peoples classes: sometimes you only know the interface to operate with – the implementing class is unknown. having contracts on that interface gives you a clear, sound concept of the limitations and relevant effects in programming against that interface in a proper way.
open close principle compliance
while tests usually test a single unit, DBC handles inheritance in a broader way, supporting also inheritance of contracts to subclasses and therefore forcing to adhere to the claimed behaviour, stated in contracts of a superclass or interface. this goes hand in hand with the Liskov Substitution Principle.
as you might have seen – Design by Contract is contributing some mind alluring features and ‘drivers’ when it comes to the specification of the intented behaviour of a component or even a whole system of interacting objects. as seen, contracts also regard the proper collaboration between clients and suppliers – a feature that’s mostly not in the scope of unit tests.
those new drivers fit very nicely with the strengths of unit tests (not all mentioned here), making them a perfect team for a ‘specification driven development’.