使用 RSpec 测试控制台应用程序交互

Using RSpec to Test Console Application Interaction

我有几个控制台应用程序,它们使用 readline 进行读取、评估、打印、循环 (REPL),如果可能的话,我想创建使用 RSpec 的测试。我读过有关 mock classes 的内容,但我不知道它们是如何工作的。是否只能替换一个真实的输入 class 来测试其余的 classes?我想对整个应用程序进行集成测试。对于我的一项 Web 服务,我有一个 rspec 脚本,它使用 curl 发送请求来测试套接字接口。我想为控制台界面做一些类似的事情。我只是不理解 mock classes 的概念吗?

回答

根据@Dave Schweisguth 给出的答案,我尝试了这个:

require 'rspec'
require 'pty'

describe "anthematic" do
  before(:all) do
    @output, @input = PTY.spawn('bin/anthematic')
  end

  it "turns on a zone" do
    @output.readpartial 1024 # read past the prompt
    @input.puts "on 1"
    expect(@output.readline.chomp).to eq("on 1")
    expect(@output.readline.chomp).to eq("turn on zone 1")
  end
end

我把 spawn 移了出去,这样我就可以添加其他测试了。我是 rspec 的新手,所以这可能不是最干净的解决方案。

在研究 PTY 时,我发现 this article 包含 popen、pty 和其他子流程。

所以模拟在 rspec 测试中的作用是代表方法调用并给出预期的 return 值。所以说在这种情况下你有一个 class 调用来获取哈希,但你不想在你的规范中实际进行调用。

MyClass.get_info

或类似的东西。

在 rpsec 中,您可以为调用该方法的时间指定 return value/object/whatever。为此,在规范的开头或在 do 块之前,您可以放置​​

allow(MyClass).to receive(:get_info).and_return({ key: 'value' })

现在在规范中,对 MyClass.get_info 的任何调用都会 return 您指定的哈希值。

my_info = MyClass.get_info
# my_info = { key: 'value' }

我同意你的看法,最好对整个应用程序进行至少一次或几次集成测试。这些测试不应该有模拟;模拟替换您想要单独测试的 类 的依赖项。以下是集成测试命令行应用程序的简单方法,以 irb 为例:

require 'pty'

describe "irb" do
  it "evaluates an expression" do
    PTY.spawn('irb') do |output, input|
      output.readpartial 1024 # read past the prompt
      input.puts "1 + 1"
      expect(output.readline.chomp).to eq("1 + 1")
      expect(output.readline.chomp).to eq("=> 2")
    end
  end
end

如果我有不止一个这样的测试,我会提取一个 RSpec 匹配器,但为了简单起见,我在这里保留了内联的细节。

这种测试相对较慢,因为它使用新的 Ruby 实例在单独的进程中运行应用程序,因此您只想编写其中的一个或几个来测试应用程序是否正常工作作为一个整体,然后通过个体 类 的单元测试来测试细节。这些看起来像什么,以及您是否需要模拟,完全取决于您的应用程序的内部结构。

当某些东西太难实际测试或资源太密集而无法测试时,你基本上会编写模拟。

在这种情况下,您尝试做的事情(REPL 测试)很困难但并非不可能。

我确定还有其他方法可以做到这一点,但这是我使用的方法:

第 1 步编写一个 shell 命令,将命令通过管道传递给您的 REPL 程序。

shell_cmd = %{(echo "MyClass.new.call_my_command"; echo "exit") | ruby my_repl_program.rb"}

这使用括号组合多个 "echo" 输出,然后发送到您的程序。请注意,您需要 发送退出命令,否则进程将挂起。

步骤 2 调用 shell 命令并为输出设置一个变量:

cmd_output = `#{shell_cmd}`

步骤 3 验证输出

现在您可以根据需要测试 cmd_output 的值。