Contractual Test-Driven Development (DBC with TDD)


Test Driven Development (TDD) is at the heart of Extreme Programming and is used to add significant quality to the programming process. It drives the development and ensures you only build what is required for a particular test case, rather than spending time developing the system for what ‘might be’ needed in the future. Productivity is enhanced since the system is continuously regression tested. Better design is achieved since writing the tests first helps to validate existing design decisions and refine and improve them.

Design By Contract (DBC) was invented by Bertrand Meyer. It adds a significant quality aspect to the design and programming process. The technique enormously improves development productivity by ensuring that what is built conforms to the defined specification. It provides clients with a precise definition of the behaviour of a component. Integrating components becomes much less painful and code bloat is reduced by removing the amount of error checking code required.

The two techniques do have some overlap and also some areas of confliction. This article discusses effective ways to blend the two techniques to further increase quality, and thus productivity, in an agile development process.

Account Deposit with TDD

Below is an overview of the techniques.

In TDD we write a test before we actually write the code. The test sets up an initial condition (like creating an instance of something). We then run some code against it, and then assert that certain conditions are true. It works like this:

Step 1: Write the test: Set up an initial condition and write some test code against it with some assertions.

Lets say I want to write an account object and then be able to deposit money into my account. Yep, our good ole friend the account object has popped up again. My test code, written using NUnit 2.0 would look something like this:

Note that, in order to be able to test the Deposit method on the Account object I needed to have a Balance property. Thus, TDD has driven out a design decision. I still haven’t written the account object yet, which is why I have the blue wiggly lines indicating it will not compile.

Step 2: Get the test to compile: To get things compiling I create an account class that does nothing, but adheres to the interface defined in the test:

[I have also added the ‘using Domain;’ line to the test class to get it to compile.]

So, now when we run the test in NUnit we get a red light because the assertion fails.

Step 3: Code until the test passes: All we do now is complete the code until the test passes:

Now, when we run the test we get a green light and we know we have completed the code.

Granted, there are some gaps in the code at this point: what happens if I try to deposit a negative amount? Well, lets say that if we try to deposit a negative amount then an exception should be thrown. [We shouldn’t really be throwing expections for business rule failures which are not exceptional circumstances, but it will help illustrate some ideas.]. The test looks like this:

I’ve thrown an application expection here, but you could define expections that are more meaningful. This test fails, so we now write the code to pass the test:

The tests now pass. There are also some additional questions you could ask. For example, can I have an overdraft? Well, if we think in terms of TDD, the only requirement we have at the moment is to deposit an amount, and we have achieved that, so we move on. That is the essence of TDD with XP. Build only what you need now. If we need to enhance things later then we will do so. We will also refactor to change the design when required, with the comfort of a suite of tests that tell us if we have broken anything.

Lets now look at how things would turn out using DBC.

Account Deposit with DBC

DBC is all about 3 things:

preconditions : things that must be true before we invoke a method.
postconditions : things that must be true after a method is invoked.
Invariants: things that must be true both before and after a method is invoked.

For our account object withdraw method we could state the following DBC rules:

Account::Deposit(decimal depositAmount)

Pre : depositAmount > 0
Post : Account.Balance = Account.Balance@pre + depositAmount
Inv : Balance > 0

For the sake of this example, I’ve just invented a requirement here that says you have no overdraft facility. This then means I have an invariant which states that the balance must always be positive.

Note also the use of the ‘@pre’ symbol. This means the value of the property before the body of the method call was invoked.

Now, lets see what this looks like in code:

C#.Net does not have DBC support built in so I’ve had to write ‘@pre’ values to local variables at the start of the method body that can be used later in the method. This is a pain.

The DBC class has a bunch of methods that simply check the assertion passed and throw an exception with the passed message if the assertion fails. You might want to define different DBC exception types if you wish to distinguish between the varying types.

I’ve not written any code to roll back the transaction if the post or invariant checks failed. This is not required, since the DBC checks are compiled out for the release version. In DBC the client (calling code of this method) is responsible for ensuring that it adheres to the precondition before it calls the method. If the client illegally calls the supplier, then the supplier makes no promises about what will happen. The behaviour is essentially undefined. This removes the need to put lots of exception catching code in the client. If you wish to leave the DBC checks in at run time you can, but if one fails there is no much you can do about anyway. The system has a fault, and needs to be fixed.

Comparison of TDD and DBC

Both sets of code using TDD and DBC pass the tests.

During TDD we design for the specific (what we are building now) whereas in DBC we think more generally about the object itself regardless of who uses it. The focus is wider. The two methods conflict. With TDD we have a very narrow focus, which enables us to quickly build what is required now, whereas with DBC we have a danger to wander off into the ‘what if’ cases.

If the methods require precondition checks then TDD requires you to write a test for each condition which can result in an exponential explosion of tests when the number of preconditions increases. You also need tests for the edge cases. With DBC you don’t get that explosion at all since the precondition deals with the general case. The same argument can be used for invariant checking.

Because you are continuously testing TDD builds extra quality into the programming process by ensuring that breaks in the code do not occur as changes are introduced. DBC does not give you that comfort, although you may well have an automated test harness you can run. With TDD, the harness pops out as part of the process.

Using DBC precondition checks and the DBC method means that client code does not have to check for exceptions thrown due to preconditions failing. With the way I’ve used TDD it means the client code has to be aware of the exceptions that can be raised. Granted, this is a weak argument, since you could turn off the precondition checks in TDD for production code.

Both TDD and DBC force design decisions to ensure client code can check a methods preconditions before it is called. This is good design, which ensures that the calling code can change it’s behaviour if the supplier is not going to pass the preconditions. For example, rather than trying to withdraw a negative amount the user could be redirected to an area of the application that offers the ability to apply for a loan. This is more powerful than trying to capture an exception then change the behaviour based on that exception. Although the last statement is more to do with using exceptions for the right purpose than TDD and DBC driving the proper use of exceptions.

So, given the above arguments, and my experience of using the two techniques together I believe the techniques I describe below effectively combine TDD and DBC into what I loosely call Contractual Test Driven Development (CTDD).

Contractual Test Driven Development (CTDD)

The follow points below are guidelines for doing contractual test driven development.

  • Do not write tests for negative behaviour. Use a DBC precondition check instead which can be compiled out at run time.
  • Use post condition checks if you want, but don’t re-write the whole body of code. [ if you work with a DBC supported language, like Eiffel.Net, I would suggest using full DBC and derive generalised post conditions from your TDD assertions. This is just an idea though, I’ve not actually tried it in practice. It could reduce the amount of test code.]
  • Use Post conditions where you can without re writing the whole body, or have to then introduce a lot of code because your language does not support DBC.
  • Turn off DBC checks for production if performance is an issue.
  • Leave DBC checks in the code if the system is life critical.

Benefits of CTDD

The benefits from TDD are:
  • building only what is required now. Keep it simple and well focused.
  • a suite of automated tests for effective refactoring, continuous integration and regression testing.
  • Design decisions are driven from the tests.
The benefits from DBC are
  • the code quality is higher due to the precondition checks.
  • you do not need all the checking code in the production code which forces code bloat.
  • design decisions are driven from the preconditions.
  • cleaner code since there is no need for checking code in the method body.

CTDD In Practice

The CTDD technique was developed in practice, rather than theory, by an XP team that I am currently leading. We started with some “interesting ideas” about how it could be done, some of which were clearly flawed when it came to actually trying them out. The methods I describe herein are now practiced by the team wholeheartedly and we have found that adding DBC to the TDD equation has significantly increased the quality of the code, particularly the refactoring and debugging activities.

A sincere acknowledgement goes to Duncan Green, who helped develop and practice these ideas during the agile development at Freshfields Bruckhaus Deringer. Duncan is a superb technician and designer, and pragmatist who has a shared interest good design and high quality.

Closing Comments (A Dose of Reality)

Whilst I’ve coined the phrase CTDD here, I’m certainly not going to try and pretend that I’ve invented something new, by any stretch of the imagination. TDD, DBC and the use of assertions were invented years ago. What I’ve described in this article are observations of how, in practice, I’ve personally managed to combine the techniques.

Happy developing (not debugging!).

Dave Chaplin

All content © Byte-Vision Limited 1997 - 2004