2
\$\begingroup\$

I'm relatively familiar to Ruby, but I'm brand-new to testing code. I've decided to use RSpec because as it seems like a popular option.

The game works perfectly, but the spec has a lot of repetition and I have a feeling it can be improved.

The Spec

require 'rock_paper_scissors'

# game = RockPaperScissors.new('rock')
# game.result => ['win!', 'lose!', 'tie!']
#
# 1 == rock
# 2 == paper
# 3 == scissors

describe "Rock Paper Scissors" do
  let(:answers_snippet) { /valid answers/ }

  context "when a number is passed" do

    it "fails when invalid" do
      game = RockPaperScissors.new(0)
      game.result.should match(answers_snippet)
    end

    it "passes when valid and convert to string value" do
      game = RockPaperScissors.new(1)

      game.result.should_not match(answers_snippet)
      game.choice.should == "rock"
    end
  end

  context "when a string is passed" do

    it "fails when invalid" do
      game = RockPaperScissors.new('cheese')
      game.result.should match(answers_snippet)
    end

    it "passes when valid" do
      game = RockPaperScissors.new('rock')
      game.result.should_not match(answers_snippet)
    end
  end

  it "should tie when rock vs rock" do
    RockPaperScissors.any_instance.stub(:random_choice).and_return('rock')

    game = RockPaperScissors.new('rock')
    game.result.should == "tie!"
  end

  it "rock should beat scissors" do
    RockPaperScissors.any_instance.stub(:random_choice).and_return('scissors')

    game = RockPaperScissors.new('rock')
    game.result.should == "won!"
  end

  it "paper should beat rock" do
    RockPaperScissors.any_instance.stub(:random_choice).and_return('rock')

    game = RockPaperScissors.new('paper')
    game.result.should == "won!"
  end

  it "scissors should beat paper" do
    RockPaperScissors.any_instance.stub(:random_choice).and_return('paper')

    game = RockPaperScissors.new('scissors')
    game.result.should == "won!"
  end

end

I've created a gist that has both the Rock Paper Scissors class and RSpec tests.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
let(:answers_snippet) { /valid answers/ }

it "fails when invalid" do
  game = RockPaperScissors.new(0)
  game.result.should match(answers_snippet)
end

When I read this spec code, I see that result should match valid answers, but from the spec name I see, that we test for invalid answers. Your specs code should be easy readable and I recommend to name constants correctly:

let(:invalid_answers_pattern) { /invalid answer/ }

Your specs are small and independent and if one spec will fail, it is enough to read this spec's code to understand how to reproduce problem. Specs are fine. But I would removed duplication in the last 4 specs, I think it won't hurt specs readability:

test_round_result(your_choice, opponent_choice, expected_result) do
  RockPaperScissors.any_instance.stub(:random_choice).and_return(opponent_choice)

  game = RockPaperScissors.new(your_choice)
  game.result.should == expected_result 
end

it "should tie when rock vs rock" do
  test_round_result( 'rock', 'rock', 'tie!' )
end

#...

Also I found that you do too much logic in the initializer of your game class:

def initialize(choice)
    @choice = choice

    if is_valid_choice?
      @result = play
    else
      @result = instructions
    end
end

Class initializer should accept parameters and make sure your class instance is in the consistent state (is_valid_choice? in your case). You can write your class initializer this way:

def initialize(choice)
    raise ArgumentError, instructions unless valid_choice?(choice)
    @choice = choice
end

Or even accepted choice in the play method because choice is a parameter for the round of the game, not for the whole game and leave initializer empty for now. When you will start doing BDD, you will likely star making design decisions like this. With this refactoring your tests will be a little bit cleaner, something like this:

let(:game) { RockPaperScissors.new() }

test_round_result(your_choice, opponent_choice, expected_result) do
  game.stub(:random_choice).and_return(opponent_choice)
  game.play(your_choice).should == expected_result
end
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.