Skip to content

Instantly share code, notes, and snippets.

@rab
Forked from ryanb/quiz.rb
Created May 20, 2009 00:15
Show Gist options
  • Save rab/114522 to your computer and use it in GitHub Desktop.
Save rab/114522 to your computer and use it in GitHub Desktop.
added test for puts/gets swap
# COMMUNITY CHALLENGE
#
# How would you test this Quiz#problem method? Only two rules:
#
# 1. The tests should fail if any part of the application breaks.
# For example: If "gets" is moved before "puts" then the tests should
# fail since that breaks the application.
#
# 2. You cannot change the Quiz class. But you can use whatever framework
# and tools you want for the tests. (RSpec, Cucumber, etc.)
#
# Note: The first rule used to be "no mocking" but I changed it. If you
# can accomplish the first rule with mocks then go ahead. I'm looking
# for the simplest/cleanest solution whatever that may be.
#
class Quiz
def initialize(input = STDIN, output = STDOUT)
@input = input
@output = output
end
def problem
first = rand(10)
second = rand(10)
@output.puts "What is #{first} + #{second}?"
answer = @input.gets
if answer.to_i == first + second
@output.puts "Correct!"
else
@output.puts "Incorrect!"
end
end
end
require 'rubygems'
require "test/unit"
require 'shoulda'
class MyIO
attr_reader :val
attr_accessor :answer
def initialize(answer=-1)
@answer = answer
end
def puts(val)
self.instance_eval %{def gets; "#{@answer}"; end}
@val = val
end
def gets
raise "gets before puts"
end
end
class QuizTest < Test::Unit::TestCase
context 'Quiz' do
setup do
@io = MyIO.new
@quiz = Quiz.new(@io, @io)
end
should 'ask question before getting answer' do
assert_nothing_raised { @quiz.problem }
end
should 'incorrect answer' do
@io.answer = -1
@quiz.problem
assert_match /incorrect/i, @io.val
end
should 'correct answer' do
srand(1)
f=rand(10)
s=rand(10)
srand(1)
@io.answer = f+s
@quiz.problem
assert_match /\Acorrect/i, @io.val
end
end
end
SOLUTIONS
There are many ways to solve this problem. If you come up with more let me know
and I'll add them here.
Custom IO Class
This was by far the most common solution. It involves creating a class which
has its own "puts" method to record the output and a "gets" method to calculate
the proper response based on that output.
Overall this is an elegant solution. The only thing I don't care for is that
it requires all logic to be set up front and the @quiz.problem call must
happen at the end.
Favorites:
1. http://gist.github.com/112547 by matflores
2. http://gist.github.com/112378 by ljsc
3. http://gist.github.com/112309 by sandal
Mocking Methods
This was originally against the rules because I was looking for other
creative alternatives which mimicked the user at a higher level and cared
little about the implementation. However, the mocking solution proved cleaner
than I originally expected. It also provided the most helpful error messages
when the app broke (if the order got switched, etc.).
Like the previous solution the @quiz.problem is called at the end. It also has
some dependencies on the implementation, specifically the "rand" method must
be stubbed. IMO this is acceptable but might become an issue in more complex
scenarios where more would need to be stubbed.
Favorites:
1. http://gist.github.com/112300 by intinig
2. http://gist.github.com/112411 by jimweirich
Separate Thread or Process
Here the Quiz#problem runs in a separate thread or process. I consider it to be
a higher level test than the others because it is the closest to mimicking a real
user. The @quiz.problem is called first and the test assertions happen live as it
is running.
I was hoping to see a few more experimentations along this line. You can see my
take on the problem below which uses a separate generic class to handle the dirty
work and leaves the test implementation very clean and self contained.
Favorites:
1. http://gist.github.com/112476 by ryanb
2. http://gist.github.com/112326 by nimbletechnique
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment