Test reports a pipeline can read

The test helper

The console output of a Ruby test run is readable by a person and useless to a pipeline. Most CI systems understand one format: JUnit XML, one report per test class. Minitest writes it with a reporter, and the reporter goes into one helper file that every test requires.

Add the reporter to the Gemfile

source "https://rubygems.org" gem "minitest", "~> 5.20" gem "minitest-reporters", "~> 1.6" gem "rake", "~> 13.0"

test/test_helper.rb

require "minitest/autorun" require "minitest/reporters" Minitest::Reporters.use! Minitest::Reporters::JUnitReporter.new

Then make the test file load the helper before anything else:

require_relative "test_helper" require_relative "../lib/todo" class TodoTest < Minitest::Test # the same three tests as before end

Run with the reporter

Install the new gem with bundle install once, then run one test file directly. The reporter replaces the dots with its own two-line notice:

bundle exec ruby -Itest test/todo_test.rb

Emptying /tmp/ci-demo/test/reports
Writing XML reports to /tmp/ci-demo/test/reports

The path in the output is the project folder on the machine that ran it; on yours it is your own folder. Two dots would mean two test files; three tests in one file write one report.

The XML it writes

The file lands in test/reports/, one per class. This is the whole content for three passing tests, trimmed to the first test case:

<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="TodoTest" filepath="test/todo_test.rb" skipped="0" failures="0" errors="0" tests="3" assertions="4"> <testcase name="test_done_marks_an_item" lineno="22" classname="TodoTest" assertions="1" file="test/todo_test.rb"> </testcase> <!-- two more testcase elements --> </testsuite> </testsuites>

A failed assertion adds a failure element with the message and the backtrace. The pipeline reads that element and shows the failing test by name, which is why the format is worth the extra gem.

Keep it as an artifact

A cache carries work forward; an artifact carries a result out. The report is an artifact, and both systems ask for the path:

- name: Keep the test report uses: actions/upload-artifact@v4 if: always() with: name: test-reports path: test/reports/*.xml

artifacts: when: always paths: - test/reports/*.xml

The always() and when: always matter: a failed job is exactly when someone wants the report, and the default keeps artifacts only for a successful run.

What to watch for

The artifact path must match the reporter output path. The reporter prints the folder it uses on every run, so read that line once and copy it into the CI file.

A JUnit file also survives a truncated log: the console output keeps the first lines of a long failure, the XML keeps every test case. When a job log is too long to scroll, the report is the faster answer.

Next: Ruby in Docker images

Article author: Arthur Isaev