Home
Courses/Training
Articles
Clients
Approach
Technology
Associates
Contact Us
Links

Quality By Design - Part 2

Abstract

"I'm going to cure the world of software bugs and make everybody's lives much easier." [Chaplin 2001] Bugs in software are bad. They cost more and more to fix as they are discovered later in the project life-cycle. Often bugs that were fixed previously arise again after changes to the software. Testing is often cut short due to pressing deadlines and software is released with bugs still present. Developers often do not exhaustively test their own code each time they make changes.

This article, in three parts, explains how some simple design and development techniques can be used to eliminate all those bug concerns and massively accelerate your productivity. The three techniques described are Automated Unit Testing, used heavily in Extreme Programming(XP); Design By Contract, and Response Matrices.

By using these techniques you can quite quickly double your productivity, and in some cases increase productivity four or even five fold.

Introduction

In Part 1 I explained how to build Automated Unit Test Harnesses using simple assertions together with the benefits of implementing Automated Unit Test Harnesses.

In this part, Part 2, I will show how you can use Design By Contract techniques to make your software more robust and easier to integrate.

Finally, in Part 3 , I’ll explain how you can utilise Response Matrices with Design By Contract to drive out exhaustive sets of Unit Tests for your classes.

Design By Contract

Once you get your head around Design By Contract (DBC) and it clicks with you, you will realise how simple and effective the technique is. I will summarise DBC here and refer you to other sources for more exhaustive explanations.

DBC uses the concept of a contractual Client-Supplier relationship. The Client is the bit of code calling a method, and the method is the Supplier. There is a contract between them that says ‘If the Client adheres to a certain set of rules about how it calls the method then the Supplier will guarantee some expected behaviour (a return value say, or a change of state). We use preconditions and postconditions to specify the contract. If the Client satisfies the preconditions, then the supplier will promise to satisfy the postconditions. An example,

Let’s say I have a class called Account, whereby you can withdraw and deposit, but you are not allowed to go over you overdraft limit. If you attempt to go over your limit an exception should be raised, since this is an error.

<:get><:get>

The conditions for the WithDraw method can be stated as follows:

Account::Withdraw(idblAmount)
-----------------------------
Pre:
idblAmount > 0
idblAmount < = Balance + mdblOverDraftLimit

Post:
mdblBalance = mdblBalance@pre + idblAmount
[The @pre indicates the value of the property before the method was called.]

[Aside: The conditions written above are actually called 'Constraints'. The standard way of writing these constraints is using the Object Constraint Language (OCL). This is part of the UML specification. It is designed to be simple to understand and use. You can pick up the basics within a day. Alternatively, you could write your constraints in what ever language you want. The rule is that they must be boolean expressions and evaluate to True or False.]

There are a number of ways of enforcing this contractual relationship:

  1. We can leave it to the Client to ensure they have met the conditions and do not put any checks in the Supplier. This way does have advantages over others, but is not within the scope of this paper.
  2. We put checks in the Supplier to ensure that the Client adhered to the conditions. This is Defensive Programming which is safer, but not as flexible than the first option. When you first do use DBC I recommend you do it this way.

Thus, using the second approach we would have code as follows:

‘** Pre-condition checks **’

If idblAmount < = 0 Then
      Err.Raise ERR_AMT_INVALID, Err.Source, “Amt must be > 0”
End If

If idblAmount > Balance + mdblOverDraftLimit Then
      Err.Raise ERR_OD_VIOLATED, Err.Source, “Amt exceeds od limit”
End If

mdblBalanceBefore = mdblBalance

‘** Method body **’

mdblBalance = mdblBalance – idblAmount

‘** Post condition checks **’
If mdblBalance <> mdblBalanceBefore + idblAmount Then
      Err.Raise ERR_POST_FAILED, Err.Source, “Account:WithDraw….”
End If

Okay, so for this very small example it does appear that there is a lot of code that essentially doesn’t do much. But, for larger components with complex interfaces there could be many pre and post conditions. If you have an object that can take many states then you need whole sets of preconditions depending on the accepting state of the object. With clearly defined preconditions and postconditions you can check that the Client is using you correctly and throw an error if they are not. You can also provide a meaningful message as to why things do not work, rather than just throwing something like 'An error occurred', or 'Amount not allowed'. Your error descriptions should state what rule failed, why it failed, and how it can be overcome. E.g. 'the amount cannot exceed zero balance plus overdraft. You entered an amount of X, when available funds was Y. Please enter a smaller amount.'

Something to avoid is optional parameters. These things are bad news. With one optional parameter you need two sets of preconditions. With two optional parameters you might need four sets and so on.

Thinking in advance what the preconditions are for a particular method gives you an indication, before you have even written the code, how complex the behaviour could be. If you have many preconditions you may want to re-visit your design to make it simpler. Simpler designs are easier to understand, easier to code, have easier contracts to enforce, and are much easier to test. In Part 3 when we look at Response Matrices, we will see how simple designs make testing much easier.

Class Invariants

Something I've not mentioned yet is the class invariant. This is part of DBC in addition to preconditions and postconditions. An invariant is something that must always be true before and after an interface is called. They can also be checked after the method body to ensure that the state of the object is correct. In this case the invariants would be:

msngBalance > = 0 – msngOverdraftLimit
msngOverdraftLimit > = 0

I have purposely not mentioned them in this paper, since you can show using Set Theory (which all this derives from by the way) that the preconditions and postconditions imply the invariant anyway, so the invariant tests are superfluous.

Invariants are very useful for determining the pre and post conditions for the class interfaces. You will find it easier to start by writing the invariant for a class, and then move onto the pre and post conditions.

Ensuring the Supplier Is Testable

You should make sure that the class you write is actually testable. This means that any properties that are included as part of a precondition are exposed to the client via a public property method. By exposing these properties we can then write Automated Unit Test (from Part 1). Everything is starting to come together now!

So. Lets take a look at what the Automated Unit Tests would look like.

Testing The Class Using Automated Unit Tests

There are three tests for the withdraw method. In Part 3 you will see how these tests were derived exhaustively.

  1. If I pass an amount less than zero and an amount less than balance plus overdraft it should raise an error = ERR_AMT_INVALID
  2. If I pass an amount greater than zero, but greater than balance plus overdraft it should raise an error = ERR_OD_VIOLATED.
  3. If I pass an amount greater than zero, and less than balance plus overdraft, the balance afterwards should be equal to its previous balance minus the amount.

Dim objCAccount As CAccount

Set objCAccount = New CAccount
[Assume at this stage that the overdraft is set to 1000, and the balance is 300]

'/* Test 1. */'
On Error Resume Next
ObjCAccount.WithDraw(-100)
Debug.Assert Err.Number = ERR_AMT_INVALID

'/* Test 2. */'
On Error Resume Next
ObjCAccount.WithDraw(2000)
Debug.Assert Err.Number = ERR_OD_VIOLATED

'/* Test 3. */'
On Error Resume Next
dblBalanceBefore = objCAccount.Balance
ObjCAccount.WithDraw(500)
Debug.Assert Err.Number = 0
Debug.Assert Err.Number = (objCAccount.Balance = dblBalanceBefore - 500)

Starting to Use Design By Contract

For new comers to this topic who wish to try out some of these techniques I would suggest the following track:

  1. Choose a fairly small component to build, or set of classes that only have a few interfaces. Choose classes whose objects have a single state. 
  2. Implement Design By Contract on those classes.
  3. Once you’ve mastered single state objects, move onto ones with multiple states.

What's in Part 3?

In Part 3 I will show you how to use Response Matrices with Design By Contract to drive out exhaustive sets of Unit Tests for your classes.

All content © Byte-Vision Limited 1997 - 2004