如何创建可展示通过测试expect详情的自定义RSpec Formatter?
解决方案:扩展RSpec Reporter以生成完整CouchDB测试结果
你已经搞定了失败测试的上报逻辑,现在只需要补全通过测试的元数据收集,就能生成符合要求的JSON结构并推送到CouchDB。下面是一步步的落地方案:
1. 准备必要依赖
先确保你的项目里有这些工具(可以加到Gemfile):
securerandom:生成唯一的_id字段json:处理JSON序列化rest-client:发送请求到CouchDB(用你熟悉的HTTP库也可以)- 若要获取Git仓库SHA,确保环境能执行
git命令
2. 完整的EliteReporter实现
我们需要在Reporter里跟踪全局测试时间、每个测试步骤的细节,还要从RSpec的example对象和元数据中提取更多关键信息。以下是完整代码:
require 'rspec/core/formatters/base_formatter' require 'securerandom' require 'json' require 'rest_client' class EliteReporter < RSpec::Core::Formatters::BaseFormatter RSpec::Core::Formatters.register self, :start, :example_passed, :example_failed, :stop def initialize(output) super(output) @test_steps = [] @global_start_time = nil # 替换成你的CouchDB地址和数据库名 @couchdb_endpoint = "http://your-couchdb-host:5984/your-test-results-db" end # 全局测试启动时记录起始时间戳 def start(_start_notification) @global_start_time = Time.now.utc.to_i * 1000 # 转为毫秒级时间戳,匹配示例格式 end # 处理通过的测试用例 def example_passed(passed_notification) example = passed_notification.example # 把单个测试步骤加入列表 @test_steps << { result: "pass", description: example.description # 如果需要单步骤执行时间,可加:run_time: example.metadata[:execution_result].run_time } end # 复用你已实现的失败测试逻辑(这里保留基础结构) def example_failed(failed_notification) example = failed_notification.example @test_steps << { result: "fail", description: "#{example.description} - 错误信息:#{failed_notification.exception.message}" } end # 全局测试结束时生成完整JSON并推送CouchDB def stop(stop_notification) global_end_time = Time.now.utc.to_i * 1000 # 获取当前Git仓库的SHA1(如果环境无git,可改用环境变量传入) sha1_repo = `git rev-parse HEAD`.strip rescue "unknown" # 确定全局测试结果 global_result = @test_steps.all? { |s| s[:result] == "pass" } ? "pass" : "fail" # 从顶层测试组提取用例元数据(假设一个RSpec Group对应一个测试用例) top_group = stop_notification.notification.group testcase_id = top_group.metadata[:testcase_id] || "TC_unknown" testcase_title = top_group.metadata[:testcase_title] || top_group.description # 构建测试层级路径 hierarchy = [] current_group = top_group while current_group hierarchy.unshift(current_group.description) current_group = current_group.parent_group end # 组装目标JSON结构 final_result = { _id: SecureRandom.uuid, # 生成唯一ID sha1_repo: sha1_repo, steps: @test_steps, time_start: @global_start_time.to_s, time_end: global_end_time.to_s, result: global_result, testcase_title: testcase_title, testcase_id: testcase_id, hierarchy: hierarchy } # 推送到CouchDB begin response = RestClient.post( @couchdb_endpoint, final_result.to_json, content_type: :json ) @output.puts "✅ 测试结果已成功推送至CouchDB: #{response.body}" rescue RestClient::Exception => e @output.puts "❌ 推送CouchDB失败: #{e.message}" end end end
3. 关键细节说明
自定义元数据提取:你可以在RSpec测试组里通过tag定义
testcase_id和testcase_title,比如:RSpec.describe "Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03", testcase_id: "TC_1zu1_BAF_Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03", testcase_title: "Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03" do it "Time for Routing expect OK: 126 micro seconds (DLC and Data also OK)" do # 测试逻辑 end end这样Reporter就能直接从
metadata里拿到这些字段,完全匹配你需要的格式。层级路径构建:通过遍历测试组的父级,从底层到顶层收集组描述,再反转得到正确的层级顺序(比如示例里的
["Hardware Integration Test", "1 - Routing", ...])。CouchDB交互:首次提交时不需要提供
_rev,CouchDB会自动生成并返回这个字段,后续更新时才需要携带。
4. 如何使用这个Reporter
运行RSpec时指定该格式化器即可:
rspec --format EliteReporter --out /dev/null
(--out /dev/null是为了屏蔽默认输出,如果你需要保留默认日志,可以同时指定多个格式化器,比如--format progress --format EliteReporter --out couchdb.log)
内容的提问来源于stack exchange,提问作者Attila Strba
相关产品推荐
相关产品推荐

