Art of Unit Testing: Part 2

William Tanksley wtanksle at dolphin.openprojects.net
Sat Aug 25 14:47:04 EDT 2001


On Fri, 24 Aug 2001 16:59:41 -0700, Jesse F. W wrote:
>William Tanksley wrote:
>> On Fri, 24 Aug 2001 11:51:07 -0700, Jesse F. W wrote:

>> >if self.app.cnt_player.battle.kind=='stop': how would this be tested?

>> It wouldn't -- it would be deleted and abhorred.  That's a violation
>> of encapsulation and of the law of Demeter (google that if you don't
>> know what it means).

>	I have looked it up on google.  For the lurkers, (and my own 
>greater knowledge), I will rephrase what I found:  The Law of 
>Demeter means that you should have as few dots(in Python) in your 
>code as possible.  You should make special functions to do 
>whatever you need done, instead of just doing the thing where you 
>need it done.(Yes, I know that's a sort of pejoritive way of putting it) 
>:-) 

That's reasonable, yes.  Another way to look at things is that no code
should tell some other object how to do its job.  In this case you're
reaching in through 3 objects and expecting to get a specific result --
that REALLY makes writing those three objects hard.

>> The problem is that each object should have an interface to do
>> everything it needs to do.  You shouldn't have to reach inside any
>> object to inspect how it's doing its job.  The above code should look
>> like 'if self.app.anyPlayersInStoppedBattles():' (I'm assuming that's
>> an appropriate name).

>> However, your question still remains: how do you unit test objects
>> which are supposed to delegate operations to other objects?  The
>> answer is that you write the unit test without assuming any
>> delegation.  That method you mentioned should work whether or not
>> delegation is happening; and your test won't care, because it only
>> tests the method, not the implementation.

>Now this, I don't understand(yet).  What do you mean, "delegate 
>operations to other objects?"  What the example code I included 
>above does is check wheather the current player in the game is 
>stopped.

The fact that I couldn't tell what that code did was a good clue that it
was written wrong.  You could have changed the names of the objects to
make the test make more sense -- for example,

if self.theGame.current_player.battle.kind=='stop':

But even this doesn't say to me "check whether the current player in the
game is stopped."  It actually says, "check whether the game's current
player's battle is of the stopped kind."

>I don't see how the method could work if there was no 
>current player object.

The existance of a 'currentPlayer' wouldn't matter to this object if
theGame had a method to answer this important question.  In order for me
to write this code, though, I have to know what it means to theGame
when the current player is stopped.

I can rewite some code right now, though:

class Player:
  def isStopped():
    return self.battle.kind == 'stop'

Now we can reasonably talk about a player being stopped -- players can
tell you whether or not they're stopped.

Of course, you'll recognise that I'm still violating the law of Demeter.
The reason I'm doing that is that I don't know why each player needs a
"battle".  Are you implying that there's a battle going on inside every
player?  Perhaps your battles should contain players, rather than your
players containing battles.  At any rate, what does it mean to ask a
player's battle whether it is of the stopped kind?  Why does having a
stopped battle also stop the player?

>Also, please say more about your last sentence, about testing only the
>method, not the implementation.

An excellent question.  I'll answer in the form of a list of rules, with
the first rule being by far the most important (in fact, all the others
follow from it).

Wait.  First let me remove a possible unclarity: when I say "method" above
I'm talking about the procedures which form an object.

1.  Write your test before you've decided how to code the guts of your
object.
2.  When you're testing an object, test ONLY properties of that object,
never properties of contained objects.
3.  Test ALL of the properties of the object which will be used by any
other object.  If a property hasn't been tested, don't ever use it; if
you're about to use a property in a manner which hasn't been tested, write
a test for that property and add that test to the object's unit tests.

Does this make sense?

If you follow these rules, your tests will not only be tests; they will
also be documentation.  Furthermore, they will document how to use the
object correctly, not how the object works internally.  When you test only
the methods and properties an object exposes, you don't have to worry
about testing the implementation -- and that means that you can change the
implementation if you feel like it.  (The last sentance uses the words
"method" and "implementation" in order to make it clear that I'm still
talking about testing the methods, not the implementation.)

>		Jesse W 

-- 
-William "Billy" Tanksley



More information about the Python-list mailing list