Unit tests vs Assertions (was Generics)
Colin Putney
cputney at wiresong.ca
Mon Oct 6 21:26:56 UTC 2003
Phil's heresy has taken a new direction of late, :-) so here's a few
thoughts on why I prefer SUnit tests over inline assertions for
improving the quality of my code.
First, I think separating assertions from production code improves the
simplicity and clarity of both. A unit test is almost a piece of
documentation; a well written unit test makes clear our expectations of
the production code in a very detailed and precise way. Similarly,
checking for unexpected errors inline obfuscates the production code.
When the assertions are separate, we can view the presence of
error-handling code as an indication that this error is possible
because of a runtime condition; we are checking for the error because
we expect it to happen and we can't prevent it by careful coding.
The second reason is an issue of statically- vs. dynamically-compiled
languages. Assertions are essentially modal. You want to turn them on
for debugging purposes and (probably) turn them off for deployment. In
a statically compiled language, this makes sense. You have two
different builds processes: one for debugging and one for deployment.
The deployment build disables the assertions somehow - by preprocessing
them away, linking against different libraries, whatever. Since you're
frequently rebuilding the entire app anyway, it's not a big deal.
In a dynamically compiled language like Smalltalk, however, there's no
meaningful distinction between development time and runtime, and
therefore between debugging mode and deployment mode. It's absolutely
normal to be using some code (which should be in deployment mode) to
develop other code (which needs to be in debugging mode) in the same
image. In fact, there's no other way to do it in Smalltalk.
As a result, the mode governing the behaviour of the assertion code
can't be set globally. Now, it's certainly possible to come up with
some way to set the assertion mode for "only this code over here." But
it invariably ends up being complex enough that it adds significant
overhead even in deployment mode, which defeats the purpose of having
modes at all.
Probably the best way to determine when assertions should be turned on
is to use dynamic scoping. We could create a method somewhere called
#allowAssertionsDuring: which took a block as a parameter and turned on
debug mode while evaluating it. So now we can bring up a workspace and
write a block that exercises the code in such a way that the assertions
will fire, and we'll get an error if there's a bug in the code.
This brings me to the final reason I prefer unit tests over inline
assertions. The blocks we're passing to #allowAssertionsDuring: are
effectively very simple unit tests, and they point out a weakness of
inline assertions. Inline assertions can only enforce strict
invariants, facts that must be true in all situations everywhere, no
matter how the code is being used, not matter what the input. That's a
big restriction.
If we pull the assertions out of the production code and into the test
code–those blocks we pass to #allowAssertionsDuring:–we suddenly have
an opportunity to make them much more effective. Since we know the
parameters passed to the code under test, we can now assert that it
produces correct results, rather than just adhering to general
invariants. Once all the assertions have been pulled out of the
production code and into the test blocks, the blocks are significant
enough that they really ought to become methods in some sort of testing
class, instead of one-off DoIts in a workspace. After that, it's a
small matter of refactoring to create an SUnit-like framework to help
with creating and running test cases. I consider unit testing not an
alternative to inline assertions, but an evolution of the same
technique.
Cheers,
Colin
More information about the Squeak-dev
mailing list
|