RSpec’sAPI for verifying collection behavior (not testing!) reads so well I just had to share with a fellow professional .NET coder (albeit a Ruby enthusiast also). Low-brow hilarity ensued…
zerokarmaleft: damn, i just love reading code that looks like this:
specify “all Players’ hands should have 2 cards” do
@game.players.each { |player| player.hand.should_have( 2 ).cards }
end
zerokarmaleft: instead of:
[Test]
public void TestAllHandsAfterDealing() {
foreach(Player player in game.Players) {
Assert.AreEqual( 2, player.Hand.Cards );
}
}
jm: yeah thats pretty nice code
jm: i’d stick my dick in that code
zerokarmaleft: rofl
jm: its puuuurdy
If you haven’t figured it out already, I started writing a generic card game (buzzword warning) framework to get my feet wet with BDD. Even after my limited foray, I feel that RSpec encourages thinking from a high-level abstraction standpoint and finding descriptive names. Both very good things in my book. To get this particular specification to read naturally, I had to extract an Array attribute out of a class and wrap it in a new class.
class Player
attr_reader :hand
def initialize; @hand = Array.new; end
end
became
class Player
attr_reader :hand
def initialize; @hand = Hand.new; end
end
class Hand
attr_reader :cards
def initialize; @cards = Array.new; end
end
In this minimal form, Hand is fairly worthless, but I wasn’t sure what kind of functionality it needed. So I added some more Player specs to see what would surface. In a card game like Uno, there are many times you have several cards that are the same and playing one is equivalent to playing any other. But Players shouldn’t be concerned with selecting a specific Card instance and deleting it from his/her Hand. They should just be able to say, “I want to play a Skip card, and I don’t care which one.”
class Player
[snip]
def play( klass_of_card, discard_pile )
cards = @hand.cards.select { |c| c.is_a? klass_of_card }
raise CardNotInHandError if cards.empty?
discard_pile << @hand.cards.delete( cards.first )
end
end
class Card; end
class Skip < Card; end
@edward = Player.new
@edward.play Skip
Since I want to pass a Card’s class as a parameter instead of an instance I can’t use Array#include? to cleanly test for inclusion. I wanted a similar one-liner with Hand.
class Player
[snip]
def play( card, discard_pile )
raise CardNotInHandError unless @hand.has_this? card
discard_pile << @hand.discard( card )
end
end
class Hand
[snip]
def has_this?( klass_of_card )
@cards.select { |card| card.is_a? klass_of_card }
@cards.empty?
end
def discard( klass_of_card )
@cards.delete { |card| card.is_a? klass_of_card }
end
end
Still doesn’t look like much but I think the clarity in Player is increased by isolating class-handling logic in Hand. As far as the Player is concerned, he/she is playing a Card…not a Card.class.
My experience with RSpec has been pretty positive so far. I’ll get back to my humble chess variant project in a bit and see how well test2spec executes Test::Unit migration.
