SUnit vs. examples and all that Jazz (was Re: Smalltalk report)

Markus Gaelli gaelli at emergent.de
Mon Aug 14 15:47:52 UTC 2006


Hi Klaus,

On Aug 14, 2006, at 1:54 PM, Klaus D. Witzel wrote:

> Hi Markus,
>
> on Mon, 14 Aug 2006 12:12:07 +0200, you wrote:
>> On Aug 14, 2006, at 11:13 AM, Klaus D. Witzel wrote:
>> On Mon, 14 Aug 2006 10:55:29 +0200, Marcus Denker  
>> <denker at iam.unibe.ch>
>>> wrote:
>>>> And have a look at BehaviorTest>>#testChange
>>>
>>> This test is a *very* good one, 100% pure[tm] object-oriented :)
>>
>> I disagree.
>
> I don't.
>
>> Whereas the _tested_ stuff is great, we don't know what kind of  
>> test it is, without reading it.
>
> Why would reading a test method make it any better or worther?  
> Perhaps you meant something like "understand it". That would be a  
> quality that we cannot enforce on the software developer.

Yes. It is about understanding.
And I claim, that for understanding an entity you need to be able to  
read it in its context. SUnit just does not give me context of the  
tested methods and classes. So I need to read it. With SUnit I need  
to know the "how" _before_ I could understand the _what_. This is not  
so good OO i.m.h.o.

I claim that the value of examples are underestimated in OO programming.
Think of a language dictionary like Websters, where you can  
understand each word, by reading it in an exemplified use. We all  
learn by example.

>
>> Also - we would not find it easily as an example for instance  
>> specific behavior.
>
> I didn't claim that. But, because of what?

Damien asked about a paper about instance specific _behavior_.
Marcus understood that we have examples for this already built into  
Squeak, but only as a remote test case.
Maybe that was the reason why Marcus had to point him to it, and why  
Damien couldn't find this method in a context he would imagine himself.

>
>> Certainly this is the way SUnit tests are supposed to look like,  
>> but who says, that SUnit is the best way (pure[tm] object- 
>> oriented) of testing?
>
> Agreed. But also: who says that it doesn't?
;-)
>
>> I'd categorize this test as a "method test" focusing on
>> 	Behavior >> compile: aString
>

> I'd have to agree if it wouldn't use #subclass: and  
> #primitiveChangeClassTo:; if you remove these two we'd be in sync  
> with "focus is on compile: aString testing".

Aehm, here I was fuzzy and should have said that it is actually  
exemplifying two things, at least for me:

- How to create a behavior
- How to plug it in
Interesting that you would also add the subclassing aspect, this  
would be a technical detail for me (boldly spoken, of course... too  
much traits stuff going around in our group I guess... ;-) )

>
>> (See our Unit Test Taxonomy Paper below)
>
> Thanks for the pointer.
>
>> I found it more object-oriented, if the test would be explicit  
>> about what it is showing and looked like:
>> ===============
>> Behavior class (examples) >> exampleCompileThisIsATest
>> 	"I return a sample behavior of thisIsATest ^2
>> 	So I do not only exemplify an instance of this class, but also of  
>> compile: aString
>> 	(As there is no annotation a la self test:[], an extended  
>> OmniBrowser could assume that it is the last method called, which  
>> is exemplified.)
>> 	Browse for _real senders_ of me, to see me in context"
>>
>> 	|aBehavior|
>> 	aBehavior := self new.
>> 	aBehavior compile: 'thisIsATest  ^ 2'.
>> 	^aBehavior
>
> Hhm[0.5*OT]: I'm not a friend of sequences of St code which begin  
> with " := self new; more: "; as soon as "self new" is over you're  
> most likely on the wrong side (reuseability, extensibility,  
> overridability).

Don't think so for above example. Treat examples as factories -  
actually you can find a lot of examples of examples ;-) following  
this factory aspect in Squeak. They are all commands - thus directly  
executable, which is nice. Why adding another level of indirection  
here? Why adding a parallel test class hierarchy?

But as seen below, when I have to add _lots_ of methods after the new  
to get this instance into a meaningful state, I certainly agree with  
you! But this also fits nicely with the metaphor of examples.
Having the mantra of "an example for each concept"  in mind also  
helps us in coming up with better designs -  we are already starting  
to discuss if subclassing is a concept necessary for plugging in  
behavior or if is is mere implementation issue - this discussion is  
good (though I don't have the time to pursue it...)

>
>> Model class (examples) >> examplePlugInstanceBehavior
>> 	|aModel|
>> 	aModel := self new.
>> 	self precondition: [self should: [aModel thisIsATest] raise:  
>> MessageNotUnderstood].
>> 	aBehavior := Behavior exampleCompileThisIsATest.
>> 	aBehavior superclass: self.
>> 	aBehavior setFormat: self format.
>> 	aModel primitiveChangeClassTo: aBehavior new.
>> 	self assert: model thisIsATest = 2.
>> 	^aModel
>> ===============
>> I think this way
>> - is more object oriented and you could browse the class side of  
>> Behavior to see how its instances can be applied
>
> Hhm[0.5*OT], depends. How many "browse this and that" will we  
> tolerate until we'd agree on "this one is an easy to understand  
> test or method or whatsoever". Seriously, what number do you suggest?

I'd say try to exemplify the main concepts your architecture is  
relying on - think API's. This should lead to a very high  test  
coverage and better understanding. To emphasize this claim even more,  
I'd say that in the end your acceptance tests / (checked examples of  
API's) reflecting the typical outside usage your system are enough.  
Of course we are all programmers, so we are using this low-level  
Squeak stuff, and for doing so we need to understand it. So our  
acceptance tests / examples should exists also for low level stuff.
>
>> - you could reuse this instance specific behavior in more tests
>
> Reuse, yes! (I understand you meant potentially reused). But we  
> should not discuss that "this and that has potential for being  
> reused" unless there are already ~= 1 users (proof of concept  
> exists, at least).

One advantage of viewing tests as checked examples / factories is  
above explained documentation value:
People like Damien might look into Behavior (examples) category to  
find out what is already there...

An other advantage (reuse of this technique ;-) is the _possible_  
reuse of these instances in different contexts.

Other arguments include cohesion of test and code, principle of last  
surprise (examples and tests are one of a kind (commands /  
factories), tests just come with some assertions), no parallel test  
hierarchy, ah, and no need for static typing your instance variables  
- the set of examples should include all possible concrete types  
these variables can take. (Same argument for method parameters btw.)

>
>> - shows, that we might miss a single method to plug this instance  
>> specific behavior as
>> 	aBehavior superclass: self.
>> 	aBehavior setFormat: self format.
>> 	aModel primitiveChangeClassTo: aBehavior new.
>>   is a bit verbose and could be subsumed into a single
>
> Hhm, matter of taste here? I prefer verbose tests, unless there is  
> an already implemented method which I can reuse (which is not the  
> case here, I mean #superclass: - #setFormat: -  
> #primitiveChangeClassTo:).

I'd like to have a browser, where you could fold in and out code, so  
if you liked you could see above "exampleCompileThisIsATest" inlined  
(maybe even edit it there).

>
> "You shall not write *new* methods just in support of tests, they  
> are superflous and do by no means reflect the testee's situation."  
> Now how about this?
>
>  "Object >>

no helper methods which are only used by other tests. But if these  
"helper methods" also exemplify the usage of other real methods - why  
not?
Certainly I agree that methods doing some real work always should be  
factored into the real code. Luckily we have PackageInfo for making  
clear what belongs to examples/tests and what to production code.

>> changeClassTo: aBehavior" method. Thus the latter example would  
>> exemplify this method.
>
> Sure, no problem with your approach.
>
>> Thanks for this nice example, I think I can reuse it for academic  
>> purposes... ;-)
>
> You are welcome :)
>
> /Klaus
>
>> Cheers,
>>
>> Markus
>
As been suggested in our taxonomy paper Stefan Reichert uses the  
fifth pane in Christo to display tests right to the methods they are  
calling.
He has not (yet? ;-) introduced the idea of an exemplified method,  
rather all methods called in the test now can display this test as an  
example.

Besides Stefan went a bit further in an other direction concerning  
this linkage of methods and tests, and also introduced a fifth pane  
the other way round: A test also displays all methods called by it to  
its right.

Cheers,

Markus




More information about the Squeak-dev mailing list