Art of Unit Testing: Part 2

Jesse W jessw at loop.com
Mon Aug 27 06:26:34 CEST 2001


	Thank you very much, Billy.  This is very interesting and useful.  
After this discussion, I may actually be able to _use_ the wonderful 
ideas I have been reading about on various XP websites. :-)
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?
> 
> >> 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).
<snip definition of Law of Demeter>
> >> 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.
<another snip> 
> 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."
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. :-)
	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.)
> >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.  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.
> 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. 
> 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.
Thank you for your answer to this question, by the way.
> 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.
Ok.
> 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.
> 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?
> 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
> -- 
> http://mail.python.org/mailman/listinfo/python-list
> 






More information about the Python-list mailing list