【问题标题】:how to test program without running it [ruby test/unit]如何在不运行的情况下测试程序 [ruby 测试/单元]
【发布时间】:2017-09-29 07:45:08
【问题描述】:

我有一些这样的代码

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

它有效,但我有来自文件的所有指令的输出(我在测试前看到“某事”) 如何只测试代码中的方法,而不运行所有方法?

【问题讨论】:

    标签: ruby


    【解决方案1】:

    当您require 一个文件时,您实际上是在“运行它”。这就是您的测试如何知道 something 方法已定义 - 因为它已经初始化了定义。

    我想你真正要问的是如何在requireing 文件时使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

    您可以通过 only 存根 puts 命令来实现此目的:

    # spec_helper.rb
    before do
      IO.any_instance.stub(:puts) # globally
      YourClass.any_instance.stub(:puts) # or for just one class
    end
    

    或者,对于更通用的解决方案,您可以阻止 all 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
    

    【讨论】:

      【解决方案2】:

      由于您的代码是现在编写的,因此没有简单的方法来运行 something 方法而不首先需要或加载它包含的文件,这会导致您的 puts 命令被执行。

      我的主要建议是重构您拥有的 Ruby 文件。您可以将puts 语句移动到一个方法中,这样它就不会自动运行。大多数 Ruby 库都是这样编写的:库中的文件在加载时不会有任何外部可见的副作用;它们只是定义方法、类和模块。

      如果由于某种原因重构不是一个选项,您可以使用这样的 hack 来防止输出被打印,但它可能无法在 Windows 上工作,因为它缺乏良好的 POSIX 支持:

      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"
      

      【讨论】:

        【解决方案3】:

        重申我看到的问题:“我有一个定义方法并运行其他命令的 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:条件执行

        每当运行或加载/需要文件时,文件中的所有命令都会立即执行。这些命令是类/方法定义还是独立命令都没有区别。如果您有任何独立命令应该在文件直接运行时执行(例如ruby something.rb)但在被另一个文件加载时不执行(例如require 'something.rb'),您可以像这样测试这种情况:

        if __FILE__ == $PROGRAM_NAME
          run_something
        end
        

        __FILE__ 是一个神奇的值,记录在ruby-doc.org 如下:

        当前正在执行的文件名,包括相对路径 到应用程序启动的目录(或当前 目录(如果已更改)。当前文件是,在某些 情况,不同于正在运行的应用程序的启动文件, 它在全局变量 $0 中可用。

        $0$PROGRAM_NAME 都是全局变量,可以互换使用(一个是另一个的别名)。

        所以表达式if __FILE__ == $PROGRAM_NAME 转换为“如果当前文件是启动文件”。

        【讨论】:

          猜你喜欢
          • 2023-01-09
          • 1970-01-01
          • 2011-06-13
          • 2017-12-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-09
          • 2022-10-01
          相关资源
          最近更新 更多