如何测试一段在 RSpec 引发异常时输出到 stderr 的代码?



请考虑以下方法:

class MyClass
def initialize
end
def will_throw
print_message_to_stderr
throw MyError
end
def print_message_to_stderr
STDERR.puts 'Boom!'
end
end

我想测试一下will_throw是否首先向 stderr 输出一条消息:

it 'outputs to stderr' do
instance = MyClass.new
expect { instance.will_throw }.to output('Boom!').to_stderr
end

但是,这不会成功,因为会引发异常并且测试套件会爆炸。

另一方面,无论如何,这始终是绿色的,即使测试失败:

it 'outputs to stderr' do
instance = MyClass.new
expect { instance.will_throw }.to output('Boom!').to_stderr
rescue MyError
end
end

如何正确测试(使用 RSpec 和普通 Ruby(?

RSpec 无法捕获STDERR,也无法STDOUT

注意:to_stdout和to_stderr通过临时替换$stdout或$stderr来工作,因此它们无法拦截显式使用 STDOUT/STDERR 或使用在使用匹配器之前存储的对 $stdout/$stderr 的引用的流输出。

STDERR是一个常数,不能(真的不应该(改变。最简单的解决方案是改用$stderr。更好的是,使用warn来明确您的意图并允许重定向警告。

第二个问题是throwraise不等价。throw用于控制流,raise用于异常。throw就像露比的goto。请改用raise

class MyClass
# You don't need an empty initialize, it is inherited from Object.
def will_raise
print_message_to_stderr
raise MyError
end
def print_message_to_stderr
warn 'Boom!'
end
end
class MyError < RuntimeError
end
RSpec.describe MyClass do
it 'outputs to stderr and raises MyError' do
instance = described_class.new
expect {
instance.will_raise
}.to output("Boom!n").to_stderr
.and raise_error(MyError)
end
end

由于您的"警告"紧接在异常之前,因此它不是真正的警告,而是错误消息。如果将其作为异常的一部分,它将使处理错误变得更加简单。为此,请提供默认消息。如果消息很复杂,您可以覆盖#message

现在,错误和消息可以作为一个单元进行捕获和控制。代码和测试要简单得多。

class MyClass
def will_raise
raise MyError
end
end
class MyError < RuntimeError
def initialize(message = "Boom!")
super
end
end
RSpec.describe MyClass do
it 'raises MyError' do
instance = described_class.new
expect {
instance.will_raise
}.to raise_error(MyError, "Boom!")
end
end

真正的警告和信息性消息最好通过Logger来完成。记录的消息更易于控制和重定向。

require 'logger'
class MyClass
def logger
@logger ||= Logger.new(STDERR)
end
def will_raise
logger.warn("Lookout!")
raise MyError
end
end
class MyError < RuntimeError
def initialize(message = "Boom!")
super
end
end

通常不测试日志消息,它们通常在测试期间关闭,但如果您真的想要,您可以使用消息期望和with来指定其参数进行检查。

RSpec.describe MyClass do
it 'raises MyError and logs a warning' do
instance = described_class.new
expect(instance.logger).to receive(:warn)
.with("Lookout!")
expect {
instance.will_raise
}.to raise_error(MyError, "Boom!")
end
end

通常,如果可以避免,您不希望捕获输出。您希望改为检查方法调用。

最新更新