【问题标题】:Custom RSpec formatter to display passed test and result of except自定义 RSpec 格式化程序以显示通过的测试和除异常的结果
【发布时间】:2018-01-05 15:00:02
【问题描述】:

有没有办法创建一个自定义格式化程序,其中显示通过的测试详细信息和一个除外列表?

这个问题的背景知识:我们正在尝试迁移到 RSpec 以进行硬件集成和系统测试。结果应该被推送到 CouchDB。我想要实现的是一个可以生成类似 YAML 输出的报告器,如下面的 sn-p:

   {
   "_id": "0006b6f0-c1bd-0135-1a98-455c37fe87f1",
   "_rev": "1-9c9786b4b4681ee8493f182d4fc56ef9",
   "sha1_repo": "68bb327b540097c10683830f0d82acbe54a47f03",
   "steps": [
       {
           "result": "pass",
           "description": "Time for Routing expect OK: 126 micro seconds (DLC and Data also OK)"
       },
       {
           "result": "pass",
           "description": "Time for Routing expect OK: 146 micro seconds (DLC and Data also OK)"
       },
       {
           "result": "pass",
           "description": "Time for Routing expect OK: 162 micro seconds (DLC and Data also OK)"
       }
],
 "time_start": "1513119108000",
   "time_end": "1513119108000",
   "result": "pass",
   "testcase_title": "Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
   "testcase_id": "TC_1zu1_BAF_Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
   "hierarchy": [
       "Hardware Integration Test",
       "1 - Routing",
       "1.1 Normal Routing",
       "1zu1_BAF_TestCases",
       "CAN_to_CAN"
   ]
}

如果测试失败,实现这一点没有问题,但我们还需要通过测试的结果,以便能够创建长期统计数据。

我可以覆盖 RSPec 的传递事件,但示例对象仅提供描述,没有更多信息。

class EliteReporter
  RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
  def example_passed(passed)
    @output.printf "pass \n #{passed.example.description}"
  end
end

提前感谢您的帮助。

【问题讨论】:

    标签: ruby rspec integration-testing


    【解决方案1】:

    终于在我同事的帮助和Tip from RSPec Emailing list 的感谢下,我可以做到这一点。

    我创建了一个记录器类来收集测试结果,而不是覆盖 Expect 方法。这样在自定义格式化程序中我可以收集所有传递的结果:

     class ExpectWrapper
      def initialize(_expect, _recorder, _description)
        @expect = _expect
        @recorder = _recorder
        @description = _description
      end
    
      def to(matcher, failure_message=nil)
        begin
          expect_ret = @expect.to(matcher, failure_message) # test
    
          # for tests that aggregate failures
          if expect_ret.instance_of?(TrueClass)
            @recorder.record(matcher.actual, matcher.description, @description)
          else
            @recorder.record_error(matcher.actual, matcher.description, failure_message, @description)
          end
          expect_ret
        rescue RSpec::Expectations::ExpectationNotMetError => e
          # for test that do not aggregate failures
          @recorder.record_error(matcher.actual, matcher.description, failure_message, @description)
          raise e
        end
    
      end
    end
    
    class Recorder
      def self.start
        @@data = []
        return Recorder.new
      end
    
      def record(expect, data, description)
        @@data << { :pass => true,  :expect => expect, :value => data, :description => description }
        self
      end
    
      def record_error(expect, data, failure_message, description)
        @@data << { :pass => false, :expect => expect, :value => data, :message => failure_message,  :description => description }
        self
      end
    
      def self.data
        @@data
      end
    
      def expect(object, value, description = "")
        return ExpectWrapper.new(object.expect(value), self, description)
      end
    end
    

    自定义格式化程序如下所示,只是一个示例,数据可能会被放入 JSON 并推送到 Couch:

    class EliteVerboseFormatter
      RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
    
    
      def initialize(output)
        @output = output
      end
    
      def example_passed(notification)
        @output.puts( format_output(notification.example, Recorder) )
      end
    
      def get_test_name( group, description)
        "#{group.example.example_group}/#{description}".gsub('RSpec::ExampleGroups::','')
      end
    
      def format_output( example, recorder )
        test_case = get_test_name( example.example_group, example.description)
        str = "**********TEST: #{test_case} ************\n"
        recorder.data.each do |d|
           str += sprintf("%s: ---> expected '%-10s' to '%-20s' DESC: %s \n",  d[:pass] ? 'PASS' : 'FAIL',  d[:expect], d[:value], d[:description])
        end
        str
    
      end
    
      def example_failed(notification)
        @output.puts(format_output( notification.example, Recorder))
    
        exception = notification.exception
        message_lines = notification.fully_formatted_lines(nil, RSpec::Core::Notifications::NullColorizer)
        exception_details = if exception
                              {
                                  # drop 2 removes the description (regardless of newlines) and leading blank line
                                  :message => message_lines.drop(2).join("\n"),
                                  :backtrace => notification.formatted_backtrace.join("\n"),
                              }
                            end
    
         @output.puts RSpec::Core::Formatters::ConsoleCodes.wrap(exception_details[:message], :failure)
    
      end
    
    
    end
    

    【讨论】:

    • 你有这些作品一起工作的示例回购吗?我无法通过简单地复制/粘贴给定的类来重现预期的行为。谢谢。
    【解决方案2】:

    我想你可以阅读Module: RSpec::Core::Formatters

    你可能会发现一些有用的东西。

    附:我已经用过 Cucumber 很多次了,我曾经想自定义 Cucumber 格式化程序来显示每个步骤的详细信息,无论它是失败还是通过。通过阅读黄瓜核心文档,我终于得到了解决方案。所以我认为也许rspec核心文档可以帮助您找到解决方案。

    【讨论】:

    • 感谢您的建议,但实际上我做到了。正如您在上面的示例中看到的,我已经创建了一个自定义格式化程序。我的问题是我只能获得测试通过的信息,而不是通过的原因。例如,如果我通过控制台检查硬件版本,我希望在报告中看到该版本与某个值匹配。这样我就知道测试是正确的,然后我可以在数据库中收集测试如何演变的信息。
    • 我想我知道你真正想要什么。当测试通过时,您希望在失败测试的相同格式化程序中获取其详细信息,是吗?实际上我对 Rspec 并不熟悉,但我有一个建议可能在某种程度上有所帮助。编辑您的代码如下:
      class EliteReporter RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished def example_passed(example) example_failed(example) end end
    【解决方案3】:

    我发现我无法将代码放在注释中,所以我把它放在这里。 编辑您的代码如下:

    class EliteReporter
      RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
      def example_passed(example)
          example_failed(example)
      end
    end
    

    希望对你有帮助:)

    【讨论】:

    • 感谢您的帮助,问题是当 example_passed 被触发时,没有为该示例创建异常对象,因此即使我调用 example_failed,格式化程序也无法从中读取信息。
    • 如何使用 before 钩子来引发异常,这样每个示例都会失败,即使是那些通过的示例?然后可能会为 example_passed 创建异常对象。呃,这只是一个建议,因为恐怕我对你的问题没有其他想法。希望对您有所帮助。
    猜你喜欢
    • 1970-01-01
    • 2012-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多