При генерации 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-отчете можем заметить что имена тестовых наборов нумеруются в порядке их выполнения:

Allure-отчет без указания имен тестовых наборов
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-отчет:

Allure-отчет с указанием имен тестовых наборов
Allure-отчет с указанием имен тестовых наборов

Комментарии (1)


  1. vabka
    02.10.2025 20:36

    Буквально такая же задача в беклоге лежит и ждёт своего часа. Пока ещё не прочитал статью

    Вообще такой же выхлоп можно сделать и без nightly - используя nextest.

    Задача со звездочкой - как-то добавить к этому всему аннотации (со ссылками на задачи, тегами, описанием шагов), так как без них большой ценности в отчёте нет.