Подтвердить что ты не робот

RSpec: как написать тест, ожидающий определенного выхода, но не заботящийся о методе?

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

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

Ответ 3

Захватите $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.

Ответ 4

То, как я проверил это, - это объект 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

Ответ 5

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

Измерительный стандартный вывод в rspec.

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

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

Ответ 6

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