如何使用 rspec 测试 .each 迭代块内的标准输出?
How to test for stdout that is inside a .each iteration block, using rspec?
我有一个示例代码:
def print_stuff(first_num, second_num)
puts 'hello'
(first_num..second_num).to_a.each do |num|
puts 'The current number is: '
puts "#{num}"
end
end
我和使用rspec,我想测试一下输出是否正确。我的尝试如下:
expect(initialize_program(1, 3)).to output(
"The current number is:
1
The current number is:
2
The current number is:
3").to_stdout
但是相反,我得到了一个输出到标准输出的预期块,但不是块错误,因为 initialize_program(1,3) 正在输出文本,但它是在 .each 块内完成的,因此方法本身 returns 数字范围数组。
如何测试块内的文本,看看输出的文本是否正确?
谢谢!
重构以检查字符串,不是标准输出
这种类型的代码是您应该首先编写测试的原因。您实质上是在测试 Kernel#puts,它总是 returns nil
,而不是验证您是否构建了您期望的字符串。不要那样做。相反,像这样重构:
def print_stuff(num1, num2)
str =
(num1..num2).map { |num|"The current number is: #{num}" }
.join "\n"
puts str
str
end
print_stuff 1, 3
#=> "The current number is: 1\nThe current number is: 2\nThe current number is: 3"
这不仅会在标准输出上打印您期望的内容:
The current number is: 1
The current number is: 2
The current number is: 3
但也会使用方法最后一行的隐式 return 来 return 一个可以用来与规范中的预期进行比较的值。
您还可以将方法重构为 return 字符串对象数组,或者您可能明确想要测试的任何其他内容。您的真实方法越能反映您计划测试的内容越好。
RSpec 例子
RSpec.describe '#print_stuff' do
it 'prints the expected message' do
expected_string = <<~EOF
The current number is: 1
The current number is: 2
The current number is: 3
EOF
expect(print_stuff 1, 3).to eql(expected_string.chomp)
end
# Even without the collection matchers removed in RSpec 3,
# you can still validate the number of items returned.
it 'returns the expected number of lines' do
lines = print_stuff(1, 3).split("\n").count
expect(lines).to eql(3)
end
end
测试 RSpec IRB 中的示例
在 irb 中,您可以像这样验证您的规范:
require 'rspec'
include RSpec::Matchers
expected_string = <<~EOF
The current number is: 1
The current number is: 2
The current number is: 3
EOF
# String#chomp is needed to strip the newline from the
# here-document
expect(print_stuff 1, 3).to eql(expected_string.chomp)
# test the returned object in other ways, if you want
lines = print_stuff(1, 3).split("\n").count
expect(lines).to eql(3)
很好,我强烈建议您仔细阅读:以 UI(在您的情况下为 CLI)最小化且易于测试的方式重构您的应用程序。但是当你想要全面覆盖时,你最终需要测试标准输出。
您现在的使用方式:
expect(initialize_program(1, 3)).to output("whatever").to_stdout
意味着当匹配器被调用时 initialize_program(1, 3)
会立即被评估,这太快了 - 它必须先做它的魔法 (*),然后 运行 你的代码。像这样尝试:
expect { initialize_program(1, 3) }.to output("whatever").to_stdout
现在,您传递一个“知道如何”调用 initialize_program(1, 3)
的块,而不是将调用 initialize_program(1, 3)
的结果传递给匹配器。那么匹配器做了什么:
- saves the block for later
- 捕获任何进入 STDOUT 的内容是否神奇(见下文)
- calls the block,调用
initialize_program(1, 3)
,捕获应该去 STDOUT 的任何内容
- compares it 与您期望的设置(
output("whatever")
部分)
https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/output-matcher
- 提到的魔法反正不是那么神奇:
https://github.com/rspec/rspec-expectations/blob/44d90f46a2654ffeab3ba65eff243039232802ce/lib/rspec/matchers/built_in/output.rb#L49
和
https://github.com/rspec/rspec-expectations/blob/44d90f46a2654ffeab3ba65eff243039232802ce/lib/rspec/matchers/built_in/output.rb#L141
它只是创建 StringIO,并用它替换全局变量 $stdout。
我有一个示例代码:
def print_stuff(first_num, second_num)
puts 'hello'
(first_num..second_num).to_a.each do |num|
puts 'The current number is: '
puts "#{num}"
end
end
我和使用rspec,我想测试一下输出是否正确。我的尝试如下:
expect(initialize_program(1, 3)).to output(
"The current number is:
1
The current number is:
2
The current number is:
3").to_stdout
但是相反,我得到了一个输出到标准输出的预期块,但不是块错误,因为 initialize_program(1,3) 正在输出文本,但它是在 .each 块内完成的,因此方法本身 returns 数字范围数组。
如何测试块内的文本,看看输出的文本是否正确?
谢谢!
重构以检查字符串,不是标准输出
这种类型的代码是您应该首先编写测试的原因。您实质上是在测试 Kernel#puts,它总是 returns nil
,而不是验证您是否构建了您期望的字符串。不要那样做。相反,像这样重构:
def print_stuff(num1, num2)
str =
(num1..num2).map { |num|"The current number is: #{num}" }
.join "\n"
puts str
str
end
print_stuff 1, 3
#=> "The current number is: 1\nThe current number is: 2\nThe current number is: 3"
这不仅会在标准输出上打印您期望的内容:
The current number is: 1 The current number is: 2 The current number is: 3
但也会使用方法最后一行的隐式 return 来 return 一个可以用来与规范中的预期进行比较的值。
您还可以将方法重构为 return 字符串对象数组,或者您可能明确想要测试的任何其他内容。您的真实方法越能反映您计划测试的内容越好。
RSpec 例子
RSpec.describe '#print_stuff' do
it 'prints the expected message' do
expected_string = <<~EOF
The current number is: 1
The current number is: 2
The current number is: 3
EOF
expect(print_stuff 1, 3).to eql(expected_string.chomp)
end
# Even without the collection matchers removed in RSpec 3,
# you can still validate the number of items returned.
it 'returns the expected number of lines' do
lines = print_stuff(1, 3).split("\n").count
expect(lines).to eql(3)
end
end
测试 RSpec IRB 中的示例
在 irb 中,您可以像这样验证您的规范:
require 'rspec'
include RSpec::Matchers
expected_string = <<~EOF
The current number is: 1
The current number is: 2
The current number is: 3
EOF
# String#chomp is needed to strip the newline from the
# here-document
expect(print_stuff 1, 3).to eql(expected_string.chomp)
# test the returned object in other ways, if you want
lines = print_stuff(1, 3).split("\n").count
expect(lines).to eql(3)
您现在的使用方式:
expect(initialize_program(1, 3)).to output("whatever").to_stdout
意味着当匹配器被调用时 initialize_program(1, 3)
会立即被评估,这太快了 - 它必须先做它的魔法 (*),然后 运行 你的代码。像这样尝试:
expect { initialize_program(1, 3) }.to output("whatever").to_stdout
现在,您传递一个“知道如何”调用 initialize_program(1, 3)
的块,而不是将调用 initialize_program(1, 3)
的结果传递给匹配器。那么匹配器做了什么:
- saves the block for later
- 捕获任何进入 STDOUT 的内容是否神奇(见下文)
- calls the block,调用
initialize_program(1, 3)
,捕获应该去 STDOUT 的任何内容 - compares it 与您期望的设置(
output("whatever")
部分)
https://relishapp.com/rspec/rspec-expectations/docs/built-in-matchers/output-matcher
- 提到的魔法反正不是那么神奇:
https://github.com/rspec/rspec-expectations/blob/44d90f46a2654ffeab3ba65eff243039232802ce/lib/rspec/matchers/built_in/output.rb#L49 和 https://github.com/rspec/rspec-expectations/blob/44d90f46a2654ffeab3ba65eff243039232802ce/lib/rspec/matchers/built_in/output.rb#L141
它只是创建 StringIO,并用它替换全局变量 $stdout。