RSpec: как мне написать тест, который ожидает определенный вывод, но не заботится о методе? - PullRequest
14 голосов
/ 16 июня 2011

Я пытаюсь разобраться в тестовом дизайне, особенно в RSpec. Но у меня возникли проблемы с некоторыми примерами из Книги RSpec.

В книге мы проверяем вывод на $ STDOUT следующим образом:

output = double('output')
game = Game.new
output.should_receive(:puts).with('Welcome to Codebreaker!')
game.start()

Ну, это работает после моды. Но с какой стати мне все равно, если объект Game использует метод put ()? Если я изменю это на print (), должно ли это действительно сломать тест? И, что более важно, разве это не противоречит одному из принципов TDD - что я должен проверять, что делает метод (дизайн), а не как он (реализация)?

Есть ли какой-нибудь способ, которым я могу написать тест, который просто проверяет, что заканчивается на $ STDOUT, не смотря на то, какой метод помещает его туда?

Ответы [ 6 ]

8 голосов
/ 16 июня 2011

Создайте класс отображения с возможностью записи статуса.

Ваш производственный код будет использовать этот экранный объект, поэтому вы можете изменять способ записи в 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

Здесь производственному коду не важно, как выводится результат. Теперь это может быть любой формой отображения абстракции.

Вся вышеприведенная теория не зависит от языка и приносит удовольствие. Вы по-прежнему издеваетесь над вещами, которые вам не принадлежат, такими как сторонний код, но вы все еще проверяете, что выполняете работу под рукой через абстракцию.

6 голосов
/ 17 июня 2011

взгляните на этот пост . Ник поднял вопросы о том же примере, и в комментариях следует очень интересная беседа. Надеюсь, вы найдете это полезным.

4 голосов
/ 14 декабря 2013

Захватите $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? был добавлен, чтобы сделать ошибки более понятными для блоков.

См. Charles ' ответ для полного решения rspec3.

3 голосов
/ 17 июня 2011

Я бы проверил это с помощью объекта 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
1 голос
/ 18 сентября 2015

Я наткнулся на это сообщение в блоге, которое помогло мне решить эту проблему:

Стандартное моделирование вывода в rspec .

Он устанавливает блоки до / после, и я закончил делать их внутри самого rspec, по какой-то причине я не смог заставить его работать из моего spec_helper.rb, как рекомендовано.

Надеюсь, это поможет!

0 голосов
/ 01 февраля 2015

Обновленная версия ответа Мэтта для 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
...