[Python-ideas] Why is design-by-contracts not widely adopted?

Steven D'Aprano steve at pearwood.info
Sun Sep 30 11:31:32 EDT 2018

On Sun, Sep 30, 2018 at 10:29:50AM -0400, David Mertz wrote:

> I think Steven's is backwards in its own way.
>    - Contracts test the space of arguments *actually used during testing
>    period* (or during initial production if the performance hit is
>    acceptable).
>    - Unit tests test the space of arguments *thought of by the developers*.
> *A priori,* either one of those can cover cases not addressed by the
> other.

Fair point.

But given that in general unit tests tend to only exercise a handful of 
values (have a look at the tests in the Python stdlib) I think it is 
fair to say that in practice unit tests typically do not have anywhere 
near the coverage of live data used during alpha and beta testing.

> If unit tests use the hypothesis library or similar approaches,
> unit tests might very well examine arguments unlikely to be encountered in
> real-world (or test phase) use... 


We can consider all of these things as complementary:

- doctests give us confidence that the documentation hasn't rotted;

- unit tests give us confidence that corner cases are tested;

- contracts give us confidence that regular and common cases are tested;

- regression tests give us confidence that bugs aren't re-introduced;

- smoke tests give us confidence that the software at least will run;

- static type checking allows us to drop type checks from our unit 
  tests and contracts;

but of course there can be overlap. And that's perfectly fine.

> - Half of the checks are very far away, in a separate file, assuming
> >   I even remembered or bothered to write the test.
> >
> To me, this is the GREATEST VIRTUE of unit tests over DbC.  It puts the
> tests far away where they don't distract from reading and understanding the
> function itself.  I rarely want my checks proximate since I wear a very
> different hat when thinking about checks than when writing functionality
> (ideally, a lot of the time, I wear the unit test hat *before* I write the
> implementation; TDD is usually good practice).

I'm curious. When you write a function or method, do you include input 
checks? Here's an example from the Python stdlib (docstring removed for 

# bisect.py
def insort_right(a, x, lo=0, hi=None):
    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if x < a[mid]: hi = mid
        else: lo = mid+1
    a.insert(lo, x)

Do you consider that check for lo < 0 to be disruptive? How would you 
put that in a unit test?

That check is effectively a pre-condition. Putting aside the question of 
which exception should be raised (AssertionError or ValueError), we 
could re-write that as a contract:

def insort_right(a, x, lo=0, hi=None):
        lo >= 0
    # implementation follows, as above
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if x < a[mid]: hi = mid
        else: lo = mid+1
    a.insert(lo, x)

Do you consider that precondition check for lo >= to be disruptive? More 
or less disruptive than when it was in the body of the function 

> > - The post-conditions aren't checked unless I run my test suite, and
> >   then they only check the canned input in the test suite.
> >
> Yes, this is a great advantage of unit tests.  No cost until you explicitly
> run them.

If you're worried about the cost of verifying your program does the 
right thing during testing and development, I think you're doing 
something wrong :-)

If there are specific functions/classes where the tests are insanely 
expensive, that's one thing. I have some code that wants to verify that 
a number is prime as part of an informal post-condition check, but if it 
is a *big* prime that check is too costly so I skip it.

But in general, if I'm testing or under active development, what do I 
care if the program takes 3 seconds to run instead of 2.5 seconds? 
Either way, its finished by the time I come back from making my coffee

But more seriously, fine, if a particular contract is too expensive to 
run, disable it or remove it and add some unit tests. And then your 
devs will complain that the unit tests are too slow, and stop running 
them, and that's why we can't have nice things... *wink*


More information about the Python-ideas mailing list