-
-
Save esumerfd/114525 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 "test/unit" | |
class QuizTest < Test::Unit::TestCase | |
def test_correct | |
c = CollectorAccuracy.new(0, "Correct!") | |
@quiz = Quiz.new(c,c) | |
@quiz.problem | |
end | |
def test_incorrect | |
c = CollectorAccuracy.new(1, "Incorrect!") | |
@quiz = Quiz.new(c,c) | |
@quiz.problem | |
end | |
def test_sequence | |
c = Collector.new(4) | |
@quiz = Quiz.new(c,c) | |
@quiz.problem | |
end | |
end | |
class Collector | |
def initialize(return_gets) | |
@return_gets = return_gets | |
@puts = 0 | |
end | |
def gets | |
fail("Puts is zero") if @puts == 0 | |
@return_gets | |
end | |
def puts(value) | |
@puts += 1 | |
end | |
end | |
class CollectorAccuracy | |
def initialize(difference, expected) | |
@puts = 0 | |
@difference = difference | |
@expected = expected | |
end | |
def gets | |
@answer | |
end | |
def puts(value) | |
@puts += 1 | |
if @puts == 1 | |
values = value.split | |
first = values[2] | |
second = values[4] | |
@answer = first.to_i + second.to_i + @difference | |
else | |
fail(value) unless value == @expected | |
end | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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