如何测试程序不运行它[Ruby测试/单元]

问题描述:

我有这样如何测试程序不运行它[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,日志记录,调试,stdoutstderr等 - 那么这里其他的答案是绰绰有余了。

但是如果你的命令正在做其他的事情,比如设置默认值或者执行有效的加载时间工作呢?或者,也许您的文件被设计为作为独立脚本运行和/或其他文件需要?

解决方案#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转换为“如果当前文件是启动文件”。