如何测试程序不运行它[Ruby测试/单元]
def something(x)
x = x * 1
end
puts "something"
一些代码,我想要测试该代码
require 'something.rb'
require 'test/unit'
class StringTest < Test::Unit::TestCase
def test_something
assert_equal(1, something(1))
end
end
它的工作原理,但我有来自文件的所有指令的输出(我在测试之前看到“某些东西”) 如何在我的代码中只测试方法而无需全部运行?
当你require
一个文件,你从字面上是“运行它”。这就是你的测试知道something
方法是如何定义的 - 因为它已经初始化了定义。
你真的在问什么,我想如何在require
这个文件的时候沉默puts
命令。有几种可能的方法 - 这里有一些建议:
请不要直接使用puts
。一个真正的原油,但简单的方法可能是包装这些调试消息一个辅助方法 - 如:
# something.rb:
def debug(message)
unless $debug_messages_disabled
puts message
end
end
def something(x)
x = x * 1
end
debug "something"
# in your spec (spec_helper.rb?):
$debug_messages_disabled = true
然而,这种方法根本不能很好地扩展...
一个更好的办法可能是use a Logger
而不是puts
。如果你选择登录到一个文件,那么你的问题已经解决了!而且,如果您坚持登录到stdout
,那么只需在运行测试时提高日志级别即可 - 只要您有方便的方式来设置此日志级别即可。例如:
# something.rb:
# ...
MyApplication.logger.debug "something" # NOT `puts`
# config/environments/development.rb
config.log_level = :debug
# config/environments/test.rb
config.log_level = :warn
......但是这种方法可能太多的努力来设置这样的单个文件!
这导致最终的简单选项隐藏这些puts
命令的输出:Suppress the STDOUT in your tests。
# spec_helper.rb
before do
IO.any_instance.stub(:puts) # globally
YourClass.any_instance.stub(:puts) # or for just one class
end
或为一个更通用的解决方案,可以阻止所有 STDOUT:
#spec_helper.rb
RSpec.configure do |config|
original_stderr = $stderr
original_stdout = $stdout
config.before(:all) do
# Redirect stderr and stdout
$stderr = File.open(File::NULL, "w")
$stdout = File.open(File::NULL, "w")
end
config.after(:all) do
$stderr = original_stderr
$stdout = original_stdout
end
end
由于您的代码是现在编写的,因此没有简单的方法来运行something
方法,而无需首先要求或加载它所包含的文件,这会导致您的puts
命令被执行。
我的主要建议是重构你的Ruby文件。您可以将puts
语句移入方法中,以使其不会自动运行。大多数Ruby库都是这样编写的:库中的文件在加载时不会有任何外部可见的副作用;他们只是定义方法,类和模块。
如果重构是不是出于某种原因的选项,你可以使用这样的黑客攻击,以防止越来越打印输出,但它可能因为它缺乏良好的POSIX支持不会工作在Windows上:
require 'fcntl'
puts "this gets printed"
# Duplicate the stdout file descriptor and then change the original
# one to be a black hole.
stdout_copy_fd = $stdout.fcntl(Fcntl::F_DUPFD)
$stdout.reopen("/dev/null", "w")
puts "this is blocked"
# you can require/load your noisy Ruby scripts here
# Restore the stdout file descriptor.
$stdout.reopen IO.new(stdout_copy_fd)
puts "this gets printed too"
要重申
您既可以通过只磕碰puts
命令实现这一目标我看到它的问题:“我有一个ruby文件,它定义了方法并运行了其他命令,我怎样才能在不运行命令的情况下测试这些方法?”
如果你的脚本的命令,完全是为了输出 - puts
,日志记录,调试,stdout
或stderr
等 - 那么这里其他的答案是绰绰有余了。
但是如果你的命令正在做其他的事情,比如设置默认值或者执行有效的加载时间工作呢?或者,也许您的文件被设计为作为独立脚本运行和/或其他文件需要?
解决方案#1:考虑重构
第一个要问的问题是,是否是有意义的重构你的代码。是否有任何非方法命令(或一系列命令)可用于包含在其他文件中?您是否对运行非方法命令的单元测试感兴趣?如果答案都是“是”,那么通过将方法中的独立命令包装起来可以更好地满足您的需求。
# other methods
def run_something
# do stuff
end
run_something
一般来说,它是很好的做法,让您的可执行文件尽可能的小,让他们从你的类和方法定义分离(小bin
,大lib
)。扩展上面的例子中,你有这样的事情:
在lib/something.rb
:
# other methods
def run_something
# do stuff
end
在bin/something.rb
:
require_relative '../lib/something.rb'
run_something
解决方案2:条件执行
每当一个文件运行或加载/需要,文件中的所有命令立即执行。这些命令是类/方法定义还是独立命令没有区别。如果你有当另一个文件(如require 'something.rb'
)加载文件时直接运行应该执行(如ruby something.rb
),但不执行任何独立的命令,你可以测试这种情况,像这样:
if __FILE__ == $PROGRAM_NAME
run_something
end
__FILE__
是魔法值,在ruby-doc.org记录如下:
文件的当前正在执行,包括路径相对 到该应用程序启动的目录(或当前 目录,如果它一直是名改变)。当前文件在某些 的情况下与正在运行的应用程序的启动文件 不同,后者在全局变量$ 0中可用。
$0
和$PROGRAM_NAME
都是全局变量,可以互换使用(一个是另一个的别名)。
因此表达式if __FILE__ == $PROGRAM_NAME
转换为“如果当前文件是启动文件”。