What if there was a quick and easy way to double the power of unit tests, even hands-on tests?
Precursor: The Vienna Development Method
Many years ago at university in the Software Engineering class, the professor discussed the Vienna Development Method (VDM). This was a method to formally specify software and prove the integrity of a design.
Among other rules, VDM specified that, per type of entity (i.e., per class) in the design, you can specify an object invariant. This is a set of conditions which must be true at any time inside every instance of the class. For example, you may specify that in an Account class, the Balance property must always be at least as high as the MinimumBalance property.
Another rule specified that, per method (or "procedure"), you need to specify preconditions and postconditions:
- A precondition is a condition which must always be true when the method starts executing.
- A postcondition is a condition that must always be true at the end of the method execution.
Typical examples of preconditions are input parameter validations (such as amount > 0, amount <= MaxAmount, etc.), the fact that the object must already be initialized, etc.
Typical postconditions are: state is updated, the value returned from a function is not null or inside a given range, etc.
VDM, being a formal method after all, used a mathematical expression language to specify these conditions and invariants. You could then use mathematical methods to "prove" that these conditions were met, and that therefore the program was correct. Now, it's a far cry to assume that, if pre– and postconditions and invariants are indeed always met, the program must be correct. Still, I thought VDM was a valuable specification and development tool.
Much to my chagrin, in the following years I discovered that virtually nobody in the industry had ever heard of VDM or was even interested in applying it! (This was one of my first exposures to the sad, sad state of professional software development, but that's another story).
I did manage to sneak in pre– and postconditions in C and C++ code by using the assert() function, and later in C# using Debug.Assert(). It did the job, but better stuff was to come.
VDM Comes Alive
Fast-forward to 2007. Microsoft Research (MSR) creates Spec#, a superset of C# that adds code contracts to the .NET Framework 3.5. The MSR team is not the first to take VDM's principles and put them in code; the Eiffel language and the Java Modeling Language (JML) already exist.
Now, in 2009/2010, they are making this cool stuff available to any CLR-based language under the name "Code Contracts".
Here's a simple example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| ///
/// Gets the total value of the order, before rebates
/// and taxes.
///
public decimal TotalValue
{
get
{
Contract.Requires(this.Lines != null);
Contract.Ensures(Contract.Result() >= 0);
decimal result = Lines
.Where(x => x != null)
.Sum(x => x.NumberOfItems * x.PricePerItem);
Contract.Assume(result >= 0);
return result;
}
} |
Contract.Requires specifies a precondition.
Contract.Ensures specifies a postcondition. Notice that the postcondition(s) are placed together with the preconditions, which keeps things quite clear and readable, much more so than JML, in my opinion.
Now, you don't use code contracts just for declaring specifications. At runtime, and – here's the kicker – even at compile time, the Code Contracts system can use a static checker to verify the validity of the conditions and invariants. This is, therefore, not just a good help to translate specifications to code, but also to prove that these specs are met. Code Contracts are a great way to complement unit tests – in fact, all conditions are evaluated during your unit tests too, reinforcing their value.
Contract.Assume, as in the example above, specifies an assumption; this is like an assertion, but weaker. At runtime there is no difference between an assumption and an assertion, but at compile-time the static checker will try to prove any assertion and generate a warning if it cannot. Since in the case above the static checker cannot prove that the result will be positive, we instruct the checker to assume that it is so. At runtime, the condition will still be checked and a contract exception will be thrown if it is not met.
Here's another simple example, this time of a contract invariant method:
1
2
3
4
5
6
| [ContractInvariantMethod]
void ObjectInvariant()
{
Contract.Invariant(Lines != null);
Contract.Invariant(TotalValue >= 0);
} |
In the coming days, I will post more examples and techniques for making the most of code contracts in .NET.