При генерации allure-отчета в проекте, написанном на Rust, я столкнулся с проблемой преобразования стандартного cargo test в формат, поддерживаемый allure. В этой статье хочу предложить свое решение.
cargo +nightly test --no-fail-fast -- -Z unstable-options --report-time --format json
Используемые флаги:
--no-fail-fast - запускает все тесты, даже если некоторые провалились
--report-time - добавляет информацию о времени выполнения каждого теста (работает только с cargo nightly)
--format json - изменяет формат вывода тес��ов на json
-Z unstable-options - разрешает использование нестабильных флагов (--report-time и --format json без него не работают)
эта команда сгенерирует json-отчет тестов, в формате:
{ "type": "suite", "event": "started", "test_count": N } { "type": "test", "event": "started", "name": "mod::test1" } ... { "type": "test", "event": "started", "name": "mod::testN" } { "type": "test", "name": "mod::test1", "event": "ok", "exec_time": T1 } ... { "type": "test", "name": "mod::testN", "event": "ok", "exec_time": TN } { "type": "suite", "event": "ok", "passed": 4, "failed": 0, "ignored": 0, "measured": 0, "filtered_out": 0, "exec_time": T}
Далее чтобы преобразовать json в формат junit использовалась утилита cargo2junit. Для генерации junit перехватывается вывод указанной выше команды и сгенерированный xml перенаправляется в файл filename.xml
cargo +nightly test --no-fail-fast -- -Z unstable-options --report-time --format json | cargo2junit > filename.xml
Сгенерируется junit-отчет в формате:
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="0" name="cargo test #0" package="testsuite/cargo test #0" tests="N" errors="0" failures="0" hostname="localhost" timestamp="..." time="T"> <testcase name="test1" time="T1" classname="mod" /> ... <testcase name="testN" time="TN" classname="mod" /> </testsuite> </testsuites>
Возникает проблема имени набора тестов, так как allure берет имя из <testsuite name="name">, а cargo2junit не подставляет имя модуля в имя тестового набора.
Рассмотрим на тестовом примере
Файлы тестов:
// tests/testsuite1 mod testsuite1 { #[test] fn test1() { assert_eq!(1, 1); } #[test] fn test2() { assert_eq!(1, 1); } #[test] fn test3() { assert_eq!(1, 1); } } // tests/testsuite2 mod testsuite2 { #[test] fn test1() { assert_eq!(1, 1); } #[test] fn test2() { assert_eq!(1, 1); } #[test] fn test3() { assert_eq!(1, 1); } }
Команда запуска тестов:
cargo +nightly test --no-fail-fast --tests -- -Z unstable-options --report-time --format json --skip clickhouse | cargo2junit > allure/tests.xml
allure/tests.xml:
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="0" name="cargo test #0" package="testsuite/cargo test #0" tests="0" errors="0" failures="0" hostname="localhost" timestamp="2025-09-25T09:41:13.800553807Z" time="0" /> <testsuite id="1" name="cargo test #1" package="testsuite/cargo test #1" tests="3" errors="0" failures="0" hostname="localhost" timestamp="2025-09-25T09:41:13.800553807Z" time="0.00000324"> <testcase name="test1" time="0.000001721" classname="testsuite1" /> <testcase name="test2" time="0.000000871" classname="testsuite1" /> <testcase name="test3" time="0.000000648" classname="testsuite1" /> </testsuite> <testsuite id="2" name="cargo test #2" package="testsuite/cargo test #2" tests="3" errors="0" failures="0" hostname="localhost" timestamp="2025-09-25T09:41:13.800553807Z" time="0.000003272"> <testcase name="test2" time="0.000000877" classname="testsuite2" /> <testcase name="test3" time="0.000000573" classname="testsuite2" /> <testcase name="test1" time="0.000001822" classname="testsuite2" /> </testsuite> </testsuites>
В allure-отчете можем заметить что имена тестовых наборов нумеруются в порядке их выполнения:

Для решения этой проблемы была использована утилита xmlstarlet. Ниже представлен bash-скрипт с решением этой проблемы.
#!/bin/bash set -e JUNIT_FILE=tests.xml ALLURE_RESULTS=allure_res rm -fr "$ALLURE_RESULTS" mkdir -p "$ALLURE_RESULTS" cargo +nightly test --no-fail-fast \ -- -Z unstable-options --report-time --format json \ | cargo2junit > "$ALLURE_RESULTS/$JUNIT_FILE" count=$(xmlstarlet sel -t -v "count(/testsuites/testsuite)" "$ALLURE_RESULTS/$JUNIT_FILE") for i in $(seq $count -1 1); do classname=$(xmlstarlet sel -t -v "/testsuites/testsuite[$i]/testcase[1]/@classname" "$ALLURE_RESULTS/$JUNIT_FILE" || echo "") if [ -n "$classname" ]; then xmlstarlet ed -L \ -u "/testsuites/testsuite[$i]/@name" \ -v "$classname" \ "$ALLURE_RESULTS/$JUNIT_FILE" else xmlstarlet ed -L \ -d "/testsuites/testsuite[$i]" \ "$ALLURE_RESULTS/$JUNIT_FILE" fi done
Помимо добавление имени тестовому набору, этот скрипт удаляет пустые наборы тестов.
В результате выполнения вышеуказанного скрипта сгенерировался файл allure_res/tests.xml:
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="1" name="testsuite1" package="testsuite/cargo test #1" tests="3" errors="0" failures="0" hostname="localhost" timestamp="2025-09-25T10:13:48.620725221Z" time="0.000003618"> <testcase name="test2" time="0.000001722" classname="testsuite1"/> <testcase name="test1" time="0.000001151" classname="testsuite1"/> <testcase name="test3" time="0.000000745" classname="testsuite1"/> </testsuite> <testsuite id="2" name="testsuite2" package="testsuite/cargo test #2" tests="3" errors="0" failures="0" hostname="localhost" timestamp="2025-09-25T10:13:48.620725221Z" time="0.00000369"> <testcase name="test2" time="0.000001487" classname="testsuite2"/> <testcase name="test3" time="0.000001253" classname="testsuite2"/> <testcase name="test1" time="0.00000095" classname="testsuite2"/> </testsuite> </testsuites>
можно увидеть что имя тестового набора приняло значение имени класса тестового случая.
В результате получился следующий allure-отчет:
