Ответ 1
посмотрите этот пост. Ник поднял вопросы о том же примере, и в комментариях следует очень интересный разговор. Надеюсь, вам понравится.
Я пытаюсь поднять голову вокруг тестового дизайна, в частности RSpec. Но у меня проблемы с некоторыми примерами из книги RSpec.
В книге мы проверяем вывод на $STDOUT следующим образом:
output = double('output')
game = Game.new
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start()
Ну, это работает после моды. Но почему мне все равно, если объект Game использует метод puts()? Если я изменил его на print(), должен ли он действительно нарушить тест? И, что еще более важно, это не относится к одному из принципов TDD - что я должен проверять, что делает этот метод (дизайн), а не как он это делает (реализация)?
Есть ли способ написать тест, который просто проверяет, что заканчивается на $STDOUT, не глядя на какой метод его там помещает?
посмотрите этот пост. Ник поднял вопросы о том же примере, и в комментариях следует очень интересный разговор. Надеюсь, вам понравится.
Создайте класс отображения с возможностью записи статуса.
В вашем производственном коде будет использоваться этот экранный объект, поэтому вы можете изменить способ записи в STDOUT. Для этой логики будет только одно место, в то время как ваши тесты полагаются на абстракцию.
Например:
output = stub('output')
game = Game.new(output)
output.should_receive(:display).with('Welcome to Codebreaker!')
game.start()
В то время как ваш производственный код будет иметь что-то вроде
class Output
def display(message)
# puts or whatever internally used here. You only need to change this here.
end
end
Я бы сделал этот тестовый проход, выполнив следующее:
def start
@output.display('Welcome to Codebreaker!')
end
Здесь производственный код не заботится о том, как выводится вывод. Это может быть любая форма отображения теперь абстракция на месте.
Все сказанное выше - язык агностик и работает. Вы все еще издеваетесь над тем, что у вас нет, например с сторонним кодом, но вы все еще проверяете, что выполняете задание под рукой через свою абстракцию.
Захватите $stdout
и протестируйте против этого, вместо того, чтобы пытаться издеваться над различными методами, которые могут выводиться на stdout. В конце концов, вы хотите протестировать stdout, а не некоторый запутанный метод для имитации.
expect { some_code }.to match_stdout( 'some string' )
Что использует пользовательский Matcher (rspec 2)
RSpec::Matchers.define :match_stdout do |check|
@capture = nil
match do |block|
begin
stdout_saved = $stdout
$stdout = StringIO.new
block.call
ensure
@capture = $stdout
$stdout = stdout_saved
end
@capture.string.match check
end
failure_message_for_should do
"expected to #{description}"
end
failure_message_for_should_not do
"expected not to #{description}"
end
description do
"match [#{check}] on stdout [#{@capture.string}]"
end
end
RSpec 3 немного изменил API-интерфейс Matcher.
failure_message_for_should
теперь failure_message
failure_message_for_should_not
теперь failure_message_when_negated
supports_block_expectations?
был добавлен, чтобы сделать ошибки более четкими для блоков.
См. Чарльз для полного решения rspec3.
То, как я проверил это, - это объект StringIO. Он действует как файл, но не касается файловой системы. Извинения за синтаксис Test:: Unit - не стесняйтесь редактировать синтаксис RSpec.
require "stringio"
output_file = StringIO.new
game = Game.new(output_file)
game.start
output_text = output_file.string
expected_text = "Welcome to Codebreaker!"
failure_message = "Doesn't include welcome message"
assert output_text.include?(expected_text), failure_message
Я наткнулся на это сообщение в блоге, которое помогло мне решить эту проблему:
Измерительный стандартный вывод в rspec.
Он устанавливает перед/после блоков, и я закончил их внутри самого реального rspec, по какой-то причине я не мог заставить его работать с моим spec_helper.rb, как рекомендовано.
Надеюсь, что это поможет!
Обновленная версия ответа Matt для RSpec 3.0:
RSpec::Matchers.define :match_stdout do |check|
@capture = nil
match do |block|
begin
stdout_saved = $stdout
$stdout = StringIO.new
block.call
ensure
@capture = $stdout
$stdout = stdout_saved
end
@capture.string.match check
end
failure_message do
"expected to #{description}"
end
failure_message_when_negated do
"expected not to #{description}"
end
description do
"match [#{check}] on stdout [#{@capture.string}]"
end
def supports_block_expectations?
true
end
end