views:

1193

answers:

6

I'm working on improving our group's development process, and I'm considering how best to implement Design By Contract with Test-Driven Development. It seems the two techniques have a lot of overlap, and I was wondering if anyone had some insight on the following (related) questions:

  • Isn't it against the DRY principle to have TDD and DbC unless you're using some kind of code generator to generate the unit tests based on contracts? Otherwise, you have to maintain the contract in two places (the test and the contract itself), or am I missing something?
  • To what extent does TDD make DbC redundant? If I write tests well enough, aren't they equivalent to writing a contract? Do I only get added benefit if I enforce the contract at run time as well as through the tests?
  • Is it significantly easier/more flexible to only use TDD rather than TDD with DbC?

The main point of these questions is this more general question: If we're already doing TDD properly, will we get a significant benefit for the overhead if we also use DbC?

A couple of details, though I think the question is largely language-agnostic:

  • Our team is very small, <10 programmers.
  • We mostly use Perl.
+19  A: 

Note the differences.

Design driven by contract. Contract Driven Design.

Develop driven by test. Test Driven Development.

They are related in that one precedes the other. They describe software at different levels of abstraction.

Do you discard the design when you go to implementation? Do you consider that a design document is a violation of DRY? Do you maintain the contract and the code separately?

Software is one implementation of the contract. Tests are another. User's manual is a third. Operations guide is a fourth. Database backup/restore procedures are one part of an implementation of the contract.

I can't see any overhead from Design by Contract.

  • If you're already doing design, then you change the format from too many words to just the right words to outline the contractual relationship.

  • If you're not doing design, then writing a contract will eliminate problems, reducing cost and complexity.

I can't see any loss of flexibility.

  1. start with a contract,

  2. then

    a. write tests and

    b. write code.

See how the two development activities are essentially intertwined and both come from the contract.

S.Lott
+1. Excellent answer. Development of the initial contracts is also a good activity for senior-level developers to hand off as artifacts to junior and mids, assuming you don't have an architect or a lead to do some of the prototyping.
joseph.ferris
Great answer, this is exactly what I was looking for. Thanks for explaining the distinction (and relationship) between the two so directly and concisely.
Adam Bellaire
For TDD, shouldn't that be "a. write tests and b. write code"?
Svante
@harlequin: they aren't strictly linear -- you don't write all tests before all code -- that's too hard to do. But you may be right, it may help to emphasize the point by putting tests first.
S.Lott
I read somewhere at stackoverflow that the contracts will help you 'generate' unit tests. So we can keep still adhere to DRY.
ragu.pattabi
@regu.pattabi: I can't be more clear: "1. start with a contract,thena. write tests andb. write code."
S.Lott
+4  A: 

I would add:

the API is the contract for the programmers, the UI definition is the contract with the clients, the protocol is the contract for client-server interactions. Get those first, then you can take advantage of parallel development tracks and not get lost in the weeds. Yes, periodically review to make sure requirements are met, but never start a new track without the contract. And 'contract' is a strong word: once deployed, it must never change. You should include versioning management and introspection from the get-go, changes to the contract are only implemented by extension sets, version numbers change with these, and then you can do things like graceful degradation when dealing with mixed or old installations.

I learned this lesson the hard way, with a large project that wandered off into never-never land, then applied it the right way later when seriously under the gun, company-survival, short fuse timeline. We defined the protocol, defined and wrote a set of protocol emulations for each side of the transactions (basically canned message generators and received message checker, one evenings' worth of two-brained coding), then parted to separately write the server and client ends of the app. We recombined the night of the show, and it just worked. Requirements, design, contract, test, code, integrate. In that order. Repeat until baked.

I am a little leery of design by TLA. As with Patterns, buzz-word compliant recipes are a good guide, but it is my experience that there is no such thing as a one-size-fits-all design or project management procedure. If you are doing things precisely By The Book (tm) then, unless it is a DOD contract with DOD procedural requirements, you will probably get into trouble somewhere along the way. Read the Book(s), yes, but be sure tounderstand them, and then take into account also the people side of your team. Rules that are only enforced by the Book will not get enforced uniformly - even when tool-enforced there can be drop-outs (e.g. svn comments left empty or cryptically brief). Procedures only tend to get followed when the tool chain not only enforces them but makes following easier than any possible short-cuts. Believe me, when the going gets tough, the short-cuts get found, and you may not know about the ones that got used at 3am until it is too late.

Gray Gaffer
+11  A: 

I think there is overlap between DbC and TDD, however, I don't think there is duplicated work: introducing DbC will probably result in a reduction of test cases.

Let me explain.

In TDD, tests aren't really tests. They are behavioral specifications. However, they are also design tools: by writing the test first, you use the external API of your object under test – that you haven't actually written yet – in the same way that a user would. That way, you design the API in a way that makes sense to a user, and not in the way that makes it easiest for you to implement. Something like queue.full? instead of queue.num_entries == queue.size.

This second part cannot be replaced by Contracts.

The first part can be partially replaced by contracts, at least for unit tests. TDD tests serve as specifications of behavior, both to other developers (unit tests) and domain experts (acceptance tests). Contracts also specify behavior, to other developers, to domain experts, but also to the compiler and the runtime library.

But contracts have fixed granularity: you have method pre- and postconditions, object invariants, module contracts and so on. Maybe loop variants and invariants. Unit tests however, test units of behavior. Those might be smaller than a method or consist of multiple methods. That's not something you can do with contracts. And for the "big picture" you still need integration tests, functional tests and acceptance tests.

And there is another important part of TDD that DbC doesn't cover: the middle D. In TDD, tests drive your development process: you never write a single line of implementation code unless you have a failing test, you never write a single line of test code unless your tests all pass, you only write the minimal amount of implementation code to make the tests pass, you only write the minimal amount of test code to produce a failing test.

In conclusion: use tests to design the "flow", the "feel" of the API. Use contracts to design the, well, contract of the API. Use tests to provide the "rhythm" for the development process.

Something like this:

  1. Write an acceptance test for a feature
  2. Write a unit test for a unit that implements a part of that feature
  3. Using the method signature you designed in step 2, write the method prototype
  4. Add the postcondition
  5. Add the precondition
  6. Implement the method body
  7. If the acceptance test passes, goto 1, otherwise goto 2

If you want to know what Bertrand Meyer, the inventor of Design by Contract, thinks about combining TDD and DbC, there is a nice paper by his group, called Contract-Driven Design = Test-Driven Development - Writing Test Cases. The basic premise is that contracts provide an abstract representation of all possible cases, whereas test cases only test specific cases. Therefore, a suitable test harness can be automatically generated from the contracts.

Jörg W Mittag
+1  A: 

You can also use executable acceptance tests that are written in the domain language of the contract. It might not be the actual "contract", but half way between unit tests and the contract.

I would recomment using Ruby Cucumber http://github.com/aslakhellesoy/cucumber

But since you are a Perl shop, then maybe you can use my own small attempt at p5-cucumber. http://github.com/kesor/p5-cucumber

Evgeny
+1  A: 

Microsoft has done work on automatic generation of unit tests, based on code contracts and parameterized unit test. E.g. the contract says the count must be increased by one when an item is added to a collection, and the parameterized unit test say how to add “n” items to a collection. Pex will then try to create a unit test that proves the contract is broken. See this video for a overview.

If this works, your unit test will only have to be written for one example of each thing you are trying to test, and PEX will be able to then work out witch data items will break the test.

Ian Ringrose
A: 

I had some ruminations about that topic some time ago.

You may want to take a look at

http://gleichmann.wordpress.com/2007/12/09/test-driven-development-and-design-by-contract-friend-or-foe/

Mario Gleichmann