При генерации 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-отчет:

vabka
Буквально такая же задача в беклоге лежит и ждёт своего часа. Пока ещё не прочитал статью
Вообще такой же выхлоп можно сделать и без nightly - используя nextest.
Задача со звездочкой - как-то добавить к этому всему аннотации (со ссылками на задачи, тегами, описанием шагов), так как без них большой ценности в отчёте нет.