【问题标题】:Capture Ruby Logger output for testing捕获 Ruby Logger 输出以进行测试
【发布时间】:2015-02-27 06:42:44
【问题描述】:

我有一个这样的 ruby​​ 类:

require 'logger'
class T
  def do_something
    log = Logger.new(STDERR)
    log.info("Here is an info message")
  end
end

还有一个测试脚本行:

#!/usr/bin/env ruby

gem "minitest"
require 'minitest/autorun'

require_relative 't'

class TestMailProcessorClasses < Minitest::Test
  def test_it
    me = T.new

    out, err = capture_io do
      me.do_something
    end

    puts "Out is '#{out}'"
    puts "err is '#{err}'"
  end
end

当我运行这个测试时,out 和 err 都是空字符串。我看到打印在 stderr 上的消息(在终端上)。有没有办法让 Logger 和 capture_io 一起玩得很好?

我使用的是纯 Ruby 环境,而不是 Ruby on Rails。

【问题讨论】:

标签: ruby logging minitest


【解决方案1】:

这是一个老问题,但我们这样做的一种方法是用期望来模拟记录器。类似的东西

logger.expects(:info).with("Here is an info message")

这允许我们在不改变记录器开箱即用的工作方式的情况下断言被测代码。

作为capture_io 的示例,我们有一个记录器实现,允许我们传入哈希并将它们输出到json。当我们测试该实现时,我们使用capture_io。这是可能的,因为我们在主题行中使用$stdout 初始化记录器实现。

subject { CustomLogging.new(ActiveSupport::Logger.new($stdout)) }

在测试中

it 'processes a string message' do
  msg = "stuff"
  out, err = capture_io do
    subject.info(msg)
  end
  out.must_equal "#{msg}\n"
end

【讨论】:

    【解决方案2】:

    capture_subprocess_io 的文档

    基本上,Leonard 的示例充实并用工作代码注释并指向文档。


    将 $stdout 和 $stderr 捕获为字符串,使用 Tempfile 确保也捕获子进程 IO。

    out, err = capture_subprocess_io do
      system "echo Some info"                 # echos to standard out
      system "echo You did a bad thing 1>&2"  # echos to standard error
    end
    
    assert_match %r%info%, out
    assert_match %r%bad%, err
    

    注意:此方法比 #capture_io 慢大约 10 倍,因此仅在需要测试子进程的输出时使用它。

    See Documentation

    【讨论】:

      【解决方案3】:

      神奇的是使用 capture_subprocess_io

       out, err = capture_subprocess_io do
           do_stuff
       end
      

      【讨论】:

        【解决方案4】:

        MiniTest 的 #capture_io 临时切换 $stdout$stderr 用于 StringIO 对象以捕获写入 $stdout$stderr 的输出。但是Logger 有自己对原始标准错误流的引用,它会愉快地写入。我认为您可以认为这是一个错误,或者至少是 MiniTest 的#capture_io 的一个限制。

        在您的情况下,您正在使用 STDERR 参数将块内的 Logger 创建为 #capture_ioSTDERR 仍然指向原始的标准错误流,这就是它无法按预期工作的原因。

        STDERR 更改为$stderr(此时确实 指向StringIO 对象)可以解决此问题,但前提是实际在#capture_io 中创建记录器块,因为在该块之外它指向原始标准错误流。

        class T
          def do_something
            log = Logger.new($stderr)
            log.info("Here is an info message")
          end
        end
        

        【讨论】:

          【解决方案5】:

          您需要在初始化Logger.new 时提供不同的StringIO 对象以捕获输出,而不是通常的:STDERR,它实际上指向控制台。

          我对上面的两个文件稍作修改,做成了一个文件,方便大家复制和测试:

          require 'logger'
          require 'minitest'
          
          class T
            def do_something(io = nil)
              io ||= STDERR
              log = Logger.new io
              log.info("Here is an info message")
            end
          end
          
          class TestT < Minitest::Test
          
            def test_something
              t = T.new
              string_io = StringIO.new
              t.do_something string_io
              puts "Out: #{string_io.string}"
            end
          
          end
          
          Minitest.autorun
          

          解释:

          1. 不带参数使用时,方法do_something 将在所有其他代码中正常运行。
          2. 当提供 StringIO 方法时,它会使用该方法而不是典型的 STDERR,从而能够将输出捕获到文件中,或者在这种情况下进行测试。

          【讨论】:

          • 我不想将参数传递给 do_something 函数。我从更大的上下文中提取了它,并且记录器已经配置为获取文件或标准错误。我必须将我的代码修改为“仅测试”案例。不太接受。
          • 当然,我理解您不只是为了测试而修改方法的理由。但我认为没有办法重定向控制台输出,无论如何,如果你找到更好的方法,请在这里发布。
          猜你喜欢
          • 2022-01-20
          • 2012-10-18
          • 2010-09-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-19
          • 2015-09-29
          • 2017-09-09
          相关资源
          最近更新 更多