Who's minister of propaganda this week?

Michael Chermside mcherm_python at yahoo.com
Sun Mar 18 02:14:11 CET 2001

Paul Prescod wrote:

> Michael Chermside wrote:
>> ...
>> I'm really not sure I see it this way. If the method  foo(x) is known to
>> take a FancyDateObject there are three kinds of errors we could make. 
>> One is that foo() is written badly so it doesn't do what it's supposed 
>> to. The unit tests of foo() need to guard against this.
>> Another possible error is that FancyDateObject isn't written properly.
>> The unit tests of FancyDateObject need to guard against this. And 
>> the third type of error is that that somewhere where we CALL foo(), 
>> we might pass it a DateObject instead... or even a String... which 
>> will cause it to perform wrong. To guard against this in a
>> dynamically typed language, we have to write unit tests for *every
>> single place* that we call foo(). 
> Your analysis is incomplete in two ways:
> 1. I don't think it makes sense to speak of writing a unit test for "one
> thing calling another". You test the first thing, you test the second
> thing and if the second thing depends upon the first then you've
> implicitly tested the connection between them.

If foo() is supposed to be passed a FancyDateObject, then the unit
tests for foo() will only pass it FancyDateObject's... it's basically
a precondition on foo(). If bar() calls foo(), then SOMEWHERE we
need to ensure that the value being passed to foo() is, indeed, a
FancyDateObject. (Or, in a dynamically typed language, an object
which behaves as a FancyDateObject is expected to behave.) Failure
to meet this condition might result in subtle behavior (eg, foo()
counts on the special rounding behavior in FancyDateObject, and
when passed a plain DateObject instead, certain invariants in foo()
get broken). It's possible to write unit tests for bar() which
test this, but that requires the author of unit tests for bar() to
know something about foo(). And bar2() and bar3() and bar4()... all
of which also call foo(). In this senario it's particularly convenient
to express the precondition "argument to foo must have special
rounding behavior" by declaring foo(FancyDateObject: x).

> 2. In object oriented programming languages, we can't just test one
> function against one input object class and be confident that the code
> is correct. There are an infinite number of potential subclasses of
> FancyDateObject. Therefore we should NOT depend on testing every single
> one of them with foo. We just need to make sure that foo is correct,
> that each FancyDateObject is correct and that the communication protocol
> between them is well-understood and tested by both.

Here I agree. Test FancyDateObject in its unit tests. When writing
foo(), test that it works when passed a FancyDateObject. When you
write FancyDateObjectWithBellsOnIt, test that it fulfills the contract
of FancyDateObject. Now you know that foo(x) works when x is a
FancyDateObjectWithBellsOnIt. But you could STILL mistakenly call 
foo(y) where y is NOT a DateObject... that's my case 3 above.

> 3. Statically typed languages are absolutely not immune to type errors
> for a variety of reasons:
>  * Null pointer exceptions are type errors. You've passed the null
> object when the code expected some other object. 

Excellent point!

>  * Almost every statically typed language also has a set of features for
> working around the type system (dynamic casts). 

This I don't feel bad about. If a language protects you against a class 
of errors EXCEPT when the programmer specially asks to turn OFF the
protection, then the programmer cannot complain of the language that
it fails to protect you when you turn it off. If you want protection,
don't turn it off (ie, don't dynamic cast except in cases where you
don't MIND finding out at runtime).

>  * Even when the compiler thinks the "type" of your object is correct,
> there are a variety of type-related errors you can make such as passing
> a negative number where only positive ones are allowed, passing an
> gopher: URL where only an http: URL is allowed, passing an absolute
> pathname where only a relative pathname is allowed and so forth. If you
> try to catch all of these in the static type checking system you wlil
> spend more of your life doing conversions than actually coding. *PLUS*
> you will need to do the runtime conversion-compatibility checks

True. Your point here is unassailable... static type checking will only
catch a subset of all possible type errors. Excellent examples!

> 4. Yes, if you unit test every unit in your program, and do good
> coverage testing you WILL catch ever occurrence of some code calling
> foo. That's what good testing is about. And you need it, not to catch
> the basic type errors but to catch the subtle semantic errors. Catching
> the basic type errors comes for free.

Yes, I can see that. But sometimes it's awefully nice to have the
compiler do it for you automatically. It certainly doesn't hurt. 
(But see Alex's response, where he argues that perhaps it DOES hurt,
for other reasons.)

-- Michael Chermside

Do You Yahoo!?
Get your free @yahoo.com address at http://mail.yahoo.com

More information about the Python-list mailing list