Your Turn

Whew! We gave you a lot to chew on in this chapter, from broad advice on test doubles to fine-grained design nuances. Here are just some of the principles we explored over several code examples:

  • Construct your test environment carefully.
  • Watch out for the “stubject.”
  • Know the risks of partial doubles.
  • Favor explicit dependency injection over more implicit techniques.
  • Avoid faking an interface you don’t control.
  • Look for high-fidelity fakes.
  • Wrap third-party interfaces.

That’s a lot to keep in your head, but we’re definitely not asking you to do that! All of these bits of advice stem from one key practice: constructing your test environment carefully. Remember the laboratory metaphor when you’re writing your specs, and you should be fine.

Above all else, when you find your test doubles straying from these principles, listen to what they’re telling you about your code’s design. Rather than looking for a different way to write the test, look for a better way to structure your code. By “better,” we don’t mean “more testable,” although as Michael Feathers explains, good design and testability are often mutually reinforcing.[117] We mean easier to maintain, more flexible, easier to understand, and easier to get right. Justin Searls discusses these trade-offs for test doubles on his “Test Doubles” wiki page and in his SCNA 2012 talk, “To Mock or Not to Mock.”[118][119]

Thank you for coming along on this journey with us! For one final time, please join us in the next section for an exercise.

Exercise

This exercise will be a little more open-ended than some of the ones from previous chapters. There’s not a single best answer. We’re going to prompt your creativity with a few questions, and then turn you loose on the code.

The following Ruby class implements a number-guessing game. Save this code into a new directory as lib/guessing_game.rb:

 class​ GuessingGame
 def​ play
  @number = rand(1..100)
  @guess = ​nil
 
  5.downto(1) ​do​ |remaining_guesses|
 break​ ​if​ @guess == @number
  puts ​"Pick a number 1-100 (​​#{​remaining_guesses​}​​ guesses left):"
  @guess = gets.to_i
  check_guess
 end
 
  announce_result
 end
 
 private
 
 def​ check_guess
 if​ @guess > @number
  puts ​"​​#{​@guess​}​​ is too high!"
 elsif​ @guess < @number
  puts ​"​​#{​@guess​}​​ is too low!"
 end
 end
 
 def​ announce_result
 if​ @guess == @number
  puts ​'You won!'
 else
  puts ​"You lost! The number was: ​​#{​@number​}​​"
 end
 end
 end
 
 # play the game if this file is run directly
 GuessingGame.new.play ​if​ ​__FILE__​.end_with?($PROGRAM_NAME)

Now, run the code with ruby lib/guessing_game.rb and try playing the game. Get a feel for how it works.

Your mission is to get this class under test. Here are some questions you might want to consider:

  • What are this class’s collaborators, and how can your test supply them?
  • What kinds of test doubles would work best here?
  • What edge cases do you need to cover in your specs?
  • Should any of this class’s responsibilities be extracted into a collaborator?

If you get stuck, you can have a peek at the specs and refactored class we wrote for this exercise. They’re in the book’s source code.[120][121]

Once you have a solution, please consider posting it in the forums.[122] We’d love to see what you came up with.

Happy testing!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset