Art of Unit Testing: Part 2

William Tanksley wtanksle at dolphin.openprojects.net
Mon Aug 27 19:32:03 EDT 2001


On Sun, 26 Aug 2001 21:26:34 -0700, Jesse W wrote:
>William Tanksley wrote:
>> 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?

>> >> 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).

>Ok.  This still seems somewhat excessive to me, but I'll let it go for 
>now.

If it looks excessive to you, it's definitely wrong for what you're doing.
You shouldn't violate the law of Demeter, but you can find a solution
which doesn't appear excessive to you and which doesn't violate the law.
One of the main tenets of extreme programming is that programming should
be fun; if the design of your program is making you uncomfortable it's a
bad design.

I suggested that name based on a guess.  I now know a lot more about your
code; not only could I suggest a better name, I could possibly suggest a
different design which would make sense to both of us.

>Now, although your point that if you could not understand what the 
>code did, it was probably written wrong has value, and especially if I 
>have to try twice before I can explain it correctly, I am going to try 
>and explain it again. :-)

Your explanation was perfect this time.  Thank you.

>	The game I am making is a card game representing a auto 
>race.  The battle part of the above line refers to the top card in a pile 
>of cards called the battle pile(hence the name).  All cards have a 
>attribute called kind which represents(and I know this is confusing, 
>and should be refactored) the general type of the card. In your 
>terms, the method should be called 
>"CurrentPlayerHasAStopTypeCardOnTopOfTheirBattlePile".  (Ooh, 
>that looks sort of like I am being sarcastic.  Since this is email, I will 
>specifically say, I do not mean to be.)

It doesn't look at all sarcastic; I know programmers who write names like
that without wincing.  I can tell that you don't like it, and neither do
I.  Unfortunately, coming up with a better name requires me to know a
little more of the context.  Right now all I know is that this information
is a property of an object named 'app'.  App doesn't mean _anything_ to
me, but I'm going to guess that it means "application".  I don't know why
an application should be able to know whether a player in a game is
stopped, so the first thing I'd do is rename "app" to "theGame".

Perhaps it makes sense to say "theGame.currentPlayerStopped()".  What do
you think?  (You answer below, so let's see:)

>> >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 don't think it would mean anything to the game if the current player 
>was stopped.  The current player could play different cards, but I 
>don't think the game would care.

Okay.  If the game doesn't care about whether the current player is
stopped, then nothing above the level of the game should ever care. (Since
the game is the only thing that knows about the current player.)

>By the way, I use the self.app object mainly as a central storehouse of
>links to the various subparts of the total program.  The app object does
>not really know or do much.

Ah.  I would consider the app object unworthy of existance, then.  Every
object should have a meaning of its own.  However, if you absolutely
cannot find a way to give it a meaning, you must violate the law of
demeter every time you use it.

>> 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.

>I was going to write here, "I really don't understand what this 
>changes", but just as I was writing it, I understood.  It adds a level of 
>abstraction, allowing the name battle to be changed, and even the 
>name kind or its value, could be set to something else.

Yes, that's a good insight.  It also allows the code to look like our
speech.  You mentioned to me that the current player was stopped; to me,
that indicates that you picture a player as being able to be stopped.
>From this it's natural to give players the ability to tell whether 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?

I understand now that every player has a number of decks, and one of them
is the battle deck.  Yes, I've played this game before :-).  Given that, I
think my implementation of isStopped is sufficient, and doesn't violate
the law of demeter.

>> 2.  When you're testing an object, test ONLY properties of
>> that object, never properties of contained objects.
>Ok; that's the law of Demeter.

Yes, the law of Demeter does require that.

>> 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.

>Ok; but how to you "test the properties" without testing the implemented?

Let's look at one of the properties of a Player in your game.  I just
decided that "isStopped" would make a good property.  Now, in order to
write a test I have to create a player, put him into a non-stopped mode,
check that isStopped isn't true, and then put him into a stopped mode.
Probably the best way to do this is to force the Player to play a Go, then
play a Stop onto his battle stack (typical in Mille Bournes), then have
him play another Go.

Note that I'm speaking in terms of the Player object; it sees the world in
terms of a complete set of game rules.  When I play a Stop against him,
the Player *knows* that it goes on his battle deck; nothing else needs to
know about the existance of the battle deck.  In fact, you may not
actually use a real deck (that is, a complete history of all cards); you
might simply use a 'stopped' state and a most-recently-played card.  Oh,
and a speed limit.  The point is that only the Player needs to know how
he's keeping track of those details.

-- 
-William "Billy" Tanksley



More information about the Python-list mailing list