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:
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.
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:
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!
https://samphippen.com/introducing-rspec-smells/#smell1stubject
https://relishapp.com/rspec/rspec-mocks/v/3-6/docs/verifying-doubles/partial-doubles
https://www.destroyallsoftware.com/blog/2014/test-isolation-is-about-avoiding-mocks
https://github.com/testdouble/contributing-tests/wiki/Test-Double