 |
Home > Detail: Testing the Contract Metaphor


| A StickyMinds.com Original |
 | |  |  Testing the Contract Metaphor
 By Kevlin Henney

  
 Summary: A contract represents a service agreement between two parties, the bounded provision of service by one party to the other. This metaphor also applies to how we can think about the relationship between unit tests and code. A contractual mindset encourages test names and partitioning based on clear propositions, backed up with executable examples. |  |  |

|
|
 | Continuing a theme from my previous Better Software magazine
columns "Programming with GUTs" (July/August 2008) and "GUT Instinct" (May/June 2009), how you partition and name your tests will affect how you think about their role and vice versa. What role do unit tests play? Do they check that the code is right? Or do they define what it means to be right? This is a subtle but important distinction. Unless you already know what right is, trying to test code for correctness is not particularly meaningful.
The common focus on tests' being used to uncover defects means that their complementary role in specifying the code is often overlooked. Indeed, for unit testing, there is a case to be made that the quest for defects is actually a distraction rather than an attraction. [1]
Defining what we expect or require from a piece of code can be likened to a contract. Writing tests in a more contractual style changes their naming, their partitioning, and their readability, as well as their role in development.
Test Functionality, Not Functions
A common, but misguided, style for writing unit tests is to align test methods with methods under test, one for one. By attempting to focus on individual methods in isolation from one another, however, this procedural style diverts attention away from actual object usage. To use an object to achieve an end and to confirm an outcome involves a sequence of method calls, from the initial creation through to the final queries.
The RecentlyUsedList C# class from "Programming with GUTs" offered four accessible features: default construction, size query using a Count property, string addition using the Add method, and indexing using the subscript operator. Using NUnit, procedural test naming is shown in listing 1a--not particularly enlightening, and perhaps also a little peculiar: The compiler-generated constructor is sufficient in this case, but the name implies that this is something the programmer must necessarily define. A test style that reflects propositions about the behavior gives a completely different quality of description, as can be seen in listing 1b.
This reads like a list of specific behaviors rather than merely echoing the method names. Without even reading the body of the test methods, we already understand what is expected. The names should be specific and refer to measurable outcomes, rather than list vague objectives. A counterexample would be the test names in listing 1c, which, although not incorrect, are vague and do not really tell us how a recently used list behaves.
Naming is not merely an act of labeling: Adopting a naming style based on propositions about usage and behavior changes the role of the tests and influences the partitioning of test cases.
Required and Ritual Naming Conventions
Test names are influenced by technical constraints and non-technical preferences. Groovy, with its out-of-the-box support for JUnit 3, was used in "GUT Instinct" to define code and tests for an isLeapYear predicate. JUnit 3 requires that test method names begin with test. This is not necessarily unreasonable, but on its own it is not enough to encourage--and can even discourage--names that reflect specification. In such cases it is more useful to consider testThat to be the prefix, as this naturally invites a proposition to follow it, as can be seen in listing 2a.
With frameworks, such as JUnit 4 and NUnit 2, that use metadata to denote test methods, naming is a little less constrained. Nonetheless, some programmers find ritual phrasing to be helpful, such as prefixing with testThat or requireThat or, in the case of behavior-driven development (see the StickyNotes for more information), using should somewhere in the test name, as shown in listing 2b.
The use of should can be helpful if specification-like naming is not the norm, but be aware that it can at times look a little indecisive and uncommitted. The word should is often used to qualify things as probable, suggested, or desirable but not actually definite, obligatory, or necessary. If you can do without ritual words, a direct, propositional naming style is less noisy and less ambivalent, as can be seen in listing 2c.
As Strunk and White note, "Make definite assertions. Avoid tame, colorless, hesitating, noncommittal
language." [2]
The Contract Metaphor
Such a definitive and assertive approach sounds contractual,
and with good reason. The tests contribute to the specification of the code, defining the expected interface that can be relied on. As Butler Lampson noted, "an interface is a contract to deliver a certain amount of service; clients of the interface depend on the contract, which is usually documented in the interface specification." [3]
The metaphor of contracts is quite a rich one, although it is often interpreted narrowly as no more than programming by contract. [4] Programming by contract frames the behavior of individual operations in terms of preconditions—which must be met by the caller if the operation is to execute successfully--and postconditions--which must be met by the operation following execution.
Programming by contract is simply one application of the contract metaphor, one that addresses some kinds of behavior but not others. In practice, the contract metaphor should be taken more broadly. There are many other approaches that are also contractual in nature, even if the word contract does not appear in their names. They vary in terms of effectiveness, effort, formality, executability, enforceability, scope, and so on. The style of testing we have been discussing is one such approach.
Returning to leap years, here is a textual definition of the contract:
Given year, which is an int, isLeapYear returns true if year is divisible by 4 but not by 100, with the exception of years also divisible by 400, otherwise it returns false. For example, isLeapYear(2008) and isLeapYear(2000) both return true, whereas isLeapYear(2009) and isLeapYear(1900) both return false.
Looking at the test names in listing 2c, we can see the contract teased out into its constituent parts. The examples form the basis of the assertions within each test method.
Contract and Converge
Would an approach more like programming by contract be as effective in expressing the contract for isLeapYear as the test-driven approach? This would entail checking the postcondition within the body of the method rather than driving the method from outside. Assertions would still be used but now to express an invariant rule rather than a specific outcome. This would look something like listing 3a.
The limitation with this approach becomes obvious when we elaborate the calculation, as shown in listing 3b. The implementation is identical to the desired outcome. For code that is declarative and functional in nature, this is not uncommon: The implementation of a pure function is often the same as the specification of its result.
Given that the logic itself is actually the challenge, duplicating it does little to clarify the specification or to catch defects. Indeed, not only is this approach unlikely to catch any defects, it is likely to propagate them. Making the logic more verbose is unlikely to help; it merely spreads it over more lines of code. It is the essential complexity of the rule for leap years that is the issue, not the accidental complexity of the code. [5]
None of this implies that isLeapYear is not bound by a contract, just that the specific technique of programming by contract has little to offer in terms of expressing and checking this particular contract. The test-driven approach turns out to be far more effective for realizing the contract in code: The contract is decomposed into descriptive clauses (the names of the test methods) with illustrative examples attached (the body of each test method).
While there is often a tacit recognition that testing and specification are related, it is one thing to say that you can derive tests from a specification and quite another to say that the tests form part of the specification. {end}
References
[1] Feathers, Michael. "The Flawed Theory Behind Unit Testing."
[2] Strunk Jr., William and E.B. White. The Elements of Style, 3rd edition. Allyn & Bacon, 1995.
[3] Lampson, Butler W. "Hints for Computer System Design." ACM Operating Systems Review, October 1983.
[4] Meyer, Bertrand. Object-Oriented Software Construction, 1st edition. Prentice Hall, 1988.
[5] Henney, Kevlin. "The Accidental Complexity of Logic." Better Software magazine. May, 2008.
Has naming influenced the style of your unit tests? Does a specification-oriented approach change the quality of your tests?
Join the conversation below or start a new one in the Member Comments section.
About the Author Kevlin Henney is an independent consultant and trainer based in the UK. He provides consultancy and training in programming techniques, software architecture, and development process. Kevlin is co-author of two recent books on patterns, A Pattern Language for Distributed Computing and On Patterns and Pattern Languages.
Back to Top 
|