[squeak-dev] Specifying with a TestCase

nicolas cellier ncellier at ifrance.com
Thu Sep 25 23:24:23 UTC 2008


I want to relate here a discussion taking place at 
http://bugs.squeak.org/view.php?id=6347
Thanks to Norbert Hartl for his collaboration and time devoted to it.

The subject is how to write a system specification TestCase.
The example taken from the bug report is to clearly express this rule:

"For every pair (o1,o2) of equal objects (o1=o2),
of different identity (o1~~o2),
registering an action for each object of the pair at finalization,
shall trigger both actions upon finalization."

I put here a new attempt with self documenting code:

finalizationProbe := Set new.
o1 := 'hello' copy.
o2 := 'hello' copy.
forAnyTwoEqualObjects := [o1 = o2].
ofDifferentIdentity := [o1 ~~ o2].
registeringAnActionAtFinalizationForEachObject := [
	o1 toFinalizeSend: #add: to: finalizationProbe
		with: 'first hello finalized'.
	o2 toFinalizeSend: #add: to: finalizationProbe
		with: 'second hello finalized'].
thenForcingFinalizationOfObjects := [
	o1 := o2 := nil. Smalltalk garbageCollect].
implyBothRegisteredActionsAreExecuted := [finalizationProbe size = 2].

self
	assert: forAnyTwoEqualObjects;
	assert: ofDifferentIdentity;
	should: [
		registeringAnActionAtFinalizationForEachObject value.
		thenForcingFinalizationOfObjects value.
		implyBothRegisteredActionsAreExecuted value].

Preamble longer than pure English, and the #value are a bit parasitic...
And why using leading assertions? They are not the goal of the test...

Well, the TestCase don't prove the implication by itself.
It just tells whether verified or not for some objects.

Assertions are there to clearly states that the rule is not meant to be 
casually true for some objects, but shall apply to any objects verifying 
the pre-condition.
They are the left member of the implication we want to verify.
They are also self-protecting the TestCase (if ever the two objects 
chosen become identical or not equal after a refactoring).
I think they have their place here.

Trusting the comments does work as well and leads to a shorter code:

"For every pair of equal objects of different identity,"
	o1 := 'hello' copy.
	o2 := 'hello' copy.
"when both are registering an action at finalization,"
	finalizationProbe := Set new.
	o1 toFinalizeSend: #add: to: finalizationProbe
		with: 'first hello finalized'.
	o2 toFinalizeSend: #add: to: finalizationProbe
		with: 'second hello finalized'.
	self assert: finalizationProbe size = 0.
"upon finalization..."
	o1 := o2 := nil. Smalltalk garbageCollect
"...both actions shall be triggered"
	self assert: finalizationProbe size = 2.

Or see excellent variation from Norbert with a pair. Small is beautiful.
But notions of equal objects (o1 = o2) and different identity (o1 ~~ o2) 
are very much implicit here.
Good for readability, but is it a clear rationale for using 
IdentityDictionary?

Sure, the whole discussion is overkill!
Regarding productivity, including time to discuss how to write tests, 
rules shall better not change too fast! Reserved to a stable Kernel...
Or a Smalltalk academy rewriting the Kernel every 100 years or so.
I wonder if we can specify a whole Smalltalk Kernel like this by the 
way? (If we cannot specify a method, then throw it away!).

I wish this example serve as a mirror to reflect our own practices.
Do we really write TestCase with specifications in mind?
Can they serve as a rationale for an implementation?
Which readers are the TestCase addressed to?
Not sure i can understand every TestCase intention in the image... 
Shouldn't I?

Nicolas




More information about the Squeak-dev mailing list