请考虑以下方法:
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
来明确您的意图并允许重定向警告。
第二个问题是throw
raise
不等价。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
通常,如果可以避免,您不希望捕获输出。您希望改为检查方法调用。