使用RSpec测试使用gets获取的用户输入

7
我是RSpec和Ruby单元测试的新手,我想知道如何测试我的代码是否使用了gets方法,但不需要提示用户输入。

这是我要测试的代码。没有什么特别的,只是一个简单的一行代码。

my_file.rb

My_name = gets

这是我的规格。
require 'stringio'

def capture_name
    $stdin.gets.chomp
end

describe 'capture_name' do
    before do
        $stdin = StringIO.new("John Doe\n")
    end

    after do 
        $stdin = STDIN
    end

    it "should be 'John Doe'" do 
        expect(capture_name).to be == 'John Doe'
        require_relative 'my_file.rb'
    end
end

现在这个规范是起作用的,但当我运行代码时它会提示用户输入。我不想让它这样做。我只想测试是否调用了gets方法,并可能模拟用户输入。不太确定如何在RSpec中实现这一点。在Python中,我会利用unittest.mock,是否有类似的方法可以在RSpec中使用?
提前致谢!

这与您在stdout上的问题非常相似(https://dev59.com/cpffa4cB1Zd3GeqP4TEq)-我的文件中的代码在加载文件时立即运行。 - Frederick Cheung
啊,所以将 require_relative 'my_file.rb' 这行代码放到 expect 下面确实可以工作。但是,这仍然不能保证 gets 方法被调用了。如果我将 My_name 的值更改为除了 gets 之外的任何其他值,测试仍然会通过。我需要单元测试在 gets 没有被调用时失败。我更新了我的代码。 - salce
测试这段代码的正确方法是在引用文件后使用expect(My_name).to eq("John Doe\n")。然而,由于require_relative和标准输入之间的某些交互作用,测试将失败。总的来说,在类外执行代码并尝试通过在测试中引用它来测试它是一条错误的道路。我想建议一条好的路径,但很难为你的示例做到这一点。在所需的文件中将gets分配给常量的目的是什么?也许更多的上下文会有所帮助。 - Dave Schweisguth
我正在使用单元测试来检查用户是否使用了他们刚学习的函数。例如,用户刚学会如何使用gets方法,我希望他们声明一个变量并用gets初始化它,并通过在线编译器提交答案。我使用单元测试来检查是否正确(通过/失败)。My_name变量之所以是常量,仅因为我需要它是全局的,以便从规范文件中访问它。我希望能够创建一个基于单元测试的测试,以检查用户是否声明了一个变量并使用gets方法进行了初始化。 - salce
我建议要求用户编写一个方法,并在您的代码中调用该方法。(我之前应该说过,在方法外执行代码是走向错误方向,而不是在类外部。) - Dave Schweisguth
我发现这篇文章很有帮助。这里的解决方案包括 Myron Marston 的解决方案,他是关于使用 RSpec3 进行测试的书籍的字面作者。https://www.codewithjason.com/test-ruby-methods-involve-puts-gets/ - J.R. Bob Dobbs
1个回答

9
这里是如何用你的返回值来存根gets的方法。
require 'rspec'

RSpec.describe do
  describe 'capture_name' do
    it 'returns foo as input' do
      allow($stdin).to receive(:gets).and_return('foo')
      name = $stdin.gets

      expect(name).to eq('food')
    end
  end
end

Failures:

  1)   should eq "food"
     Failure/Error: expect(name).to eq('food')

       expected: "food"
            got: "foo"

       (compared using ==)

要测试某个东西是否被调用(例如一个函数),你可以使用expect($stdin).to receive(:gets).with('foo')来确保它被正确地调用了(仅一次)。在这种情况下,期望行必须放在name = $stdin.gets之前。


这只展示了如何捕获单个 gets,那么多个连续的 gets 呢? - notacorn
@notacorn,你可以像这样传递多个参数: allow($stdin).to receive(:gets).and_return('foo', 'bar', 'baz') - Fellps

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接