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