Тема
Адаптер JUnit 4
app.doqa:doqa-junit4 отправляет результаты тестов JUnit 4 в DoQA: автотесты создаются и обновляются сами, результаты приходят с шагами, фикстурами, параметрами, вложениями и ссылками. Как и у JUnit 5, ошибка отправки никогда не роняет сборку. Тесты проходят как обычно.
Установка и обязательная регистрация
Нужны JDK 11 или новее (адаптер проверяется на JDK 11, 17 и 21) и JUnit 4.13 (проверяется на 4.13.2; на версиях 4.11 и ниже гарантий нет). Добавьте зависимость:
xml
<dependency>
<groupId>app.doqa</groupId>
<artifactId>doqa-junit4</artifactId>
<version>0.1.6</version>
<scope>test</scope>
</dependency>groovy
testImplementation("app.doqa:doqa-junit4:0.1.6")В отличие от JUnit 5, у JUnit 4 нет ни SPI, ни ServiceLoader для RunListener. Без явной регистрации адаптер молчит: зависимость подключена, но результаты никуда не уходят, даже в файлы. Выберите один из двух способов.
Maven (surefire): листенер
Одна настройка на весь прогон, тестовый код не трогаете:
xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<properties>
<property>
<name>listener</name>
<value>app.doqa.junit4.DoqaRunListener</value>
</property>
</properties>
</configuration>
</plugin>Gradle и запуск из IDE: @RunWith
Для Gradle и запуска из IDE точки регистрации листенера в JUnit 4 нет вовсе. Разметьте класс (или общий базовый класс) рунером. Он сам подцепит листенер:
java
@RunWith(app.doqa.junit4.DoqaRunner.class)
public class LoginTests { /* … */ }Способы совместимы: если внешний листенер уже зарегистрирован, рунер второй раз его не добавляет. DoqaRunner даёт больше, чем просто отправку результатов, — подробности в разделе «Особенности и ограничения».
Внимание
DoqaRunner — обычный BlockJUnit4ClassRunner, он не совместим с классами, у которых уже есть свой рунер (SpringRunner, MockitoJUnitRunner, Suite, Enclosed). Для таких классов остаётся листенер (базовый уровень отправки результатов) и, для селективного запуска, DoqaSelectRule — см. «Селективный запуск: три уровня». У @RunWith(Parameterized.class) рунер адаптера свой, он подключается фабрикой — см. «Параметризованные тесты».
Сразу после подключения зависимости и регистрации листенера или рунера адаптер пишет результаты в ./results/, файлы Allure-совместимого формата, которые принимает конвейер загрузки DoQA. Загрузить их можно любым способом со страницы Отправка результатов автотестов. Аннотации не обязательны: каждый тест получает стабильный идентификатор автоматически.
Разметка тестов
Аннотации @Doqa* — из пакета app.doqa.annotations, общего для всех JVM-адаптеров DoQA. Полный список с примерами — на странице Адаптер JUnit 5. Смена фреймворка не требует менять импорты.
java
public class LoginTests {
@Test
@DoqaId("LOGIN-1")
@DoqaTitle("Успешный вход")
@DoqaLabels({"smoke"})
public void loginHappyPath() { /* … */ }
}JUnit 4 дополнительно читает нативную разметку, двойная разметка не нужна:
| Что в коде | Что получается в DoQA |
|---|---|
@Category({Smoke.class, Ui.class}) на методе и/или классе | теги Smoke, Ui (простые имена классов-категорий, метод и класс объединяются) |
@Ignore("нужен стенд") | исход skipped с причиной; на классе — по каждому @Test-методу |
Если @DoqaId нет, идентификатор ищется в порядке: маркер [DOQA-123]/@DOQA:123 в имени теста → Allure @AllureId → детерминированный хэш сигнатуры метода. Имя теста в JUnit 4 — это имя метода, поэтому маркер доступен только в имени инвокации @Parameters(name = …). Обычный путь — явный @DoqaId. Хэш идёт с префиксом фреймворка (junit4:<хэш> против junit5:<хэш>), да и сама подпись у фреймворков разная: переход на другой JVM-адаптер без явного @DoqaId начинает историю автотеста заново.
Конфигурация
Источники настроек, по возрастанию приоритета: файл doqa.properties (в рабочей директории запуска тестов, для Maven — директория модуля; путь переопределяется -Ddoqa.config=… или DOQA_CONFIG) → переменные окружения DOQA_* → JVM-свойства -Ddoqa.*. Механизм общий для всех JVM-адаптеров DoQA.
properties
url=https://demo.doqa.app
token=<project-токен>
spaceId=42С этими тремя ключами адаптер сам создаёт прогон и наполняет его. Как создать токен и узнать ID пространства — в разделах «Создание API-токена» и «Как узнать ID пространства».
Все ключи
Ключ (doqa.properties / -Ddoqa.<ключ>) | Переменная окружения | Что это | Дефолт |
|---|---|---|---|
reporting | DOQA_REPORTING | куда слать: api / files / auto / off | auto |
resultsDir | DOQA_RESULTS_DIR | каталог файлового режима | results |
url | DOQA_URL | адрес DoQA | — |
token | DOQA_TOKEN (алиас DOQA_PRIVATE_TOKEN) | project- или personal-токен | — |
spaceId | DOQA_SPACE_ID (алиас DOQA_PROJECT_ID) | ID пространства | — |
configurationId | DOQA_CONFIGURATION_ID | конфигурация прогона (браузер/ОС и т. п.) | — |
testRunId | DOQA_TEST_RUN_ID | существующий прогон (нужен для режимов 0 и 1) | — |
testRunName | DOQA_TEST_RUN_NAME | имя создаваемого прогона (режим 2) | — |
adapterMode | DOQA_ADAPTER_MODE | режим выбора прогона: 2 — new, 1 — existing, 0 — selective | 2 |
importRealtime | DOQA_IMPORT_REALTIME | true — отправка результатов по ходу прогона (по границе класса) | false (порция в конце) |
certValidation | DOQA_CERT_VALIDATION | false — доверять самоподписанным TLS-сертификатам (отключает и проверку hostname) | true |
proxy | DOQA_PROXY | HTTP-прокси, host:port | — |
pipelineId | DOQA_PIPELINE_ID | привязка прогона к CI-пайплайну | авто: CI_PIPELINE_ID / GITHUB_RUN_ID |
ciRunId | DOQA_CI_RUN_ID | ID CI-запуска, инициированного из DoQA; приезжает в пайплайн сам и отправляется обратно с результатами | — |
branch | DOQA_BRANCH | ветка прогона | авто: CI_COMMIT_REF_NAME / GITHUB_REF_NAME |
batchSize | DOQA_BATCH_SIZE | максимум результатов в одной порции (одном запросе) | 100 |
requestTimeoutMs | DOQA_REQUEST_TIMEOUT_MS | таймаут HTTP-запроса, мс | 30000 |
retries | DOQA_RETRIES | попыток на запрос (повторяются только безопасные) | 3 |
retryBackoffMs | DOQA_RETRY_BACKOFF_MS | базовая пауза между попытками (растёт экспоненциально), мс | 500 |
maxTraceLength | DOQA_MAX_TRACE_LENGTH | лимит длины стек-трейса, символов | 100000 |
maxMessageLength | DOQA_MAX_MESSAGE_LENGTH | лимит длины сообщений, символов | 10000 |
maxParameterLength | DOQA_MAX_PARAMETER_LENGTH | лимит длины значений параметров, символов | 2000 |
pipelineId и branch подхватываются автоматически из переменных GitLab и GitHub Actions; для остальных CI задавайте DOQA_PIPELINE_ID вручную.
Что отправляется
Рантайм-API из тела теста — тот же, что у остальных JVM-адаптеров DoQA:
java
Doqa.step("открыть страницу", () -> page.open());
Doqa.addParameter("env", "staging");
Doqa.addAttachments("target/screenshot.png");
Doqa.addLink("https://jira/TASK-5", LinkType.REQUIREMENT);
Doqa.addCaseIds(1042);Декларативные шаги @Step работают так же, как в JUnit 5, и так же требуют AspectJ-агента в тестовой JVM — как его подключить.
Полнота шагов и фикстур зависит от способа регистрации:
| Что | Листенер (базовый уровень) | @RunWith(DoqaRunner.class) |
|---|---|---|
| Исход, сообщение, трейс, длительность | да | да |
Doqa.step и @Step, вложения, ссылки, метки, параметры | да, одним блоком шагов | да, разложено по фазам |
@Before/@After | входят в тело результата, отдельных узлов нет | отдельный шаг на каждый метод |
@BeforeClass/@AfterClass | один агрегированный узел на класс (время — между событиями JUnit, включает и работу рунера) | точный узел на каждый метод и точное время |
Параметризованные тесты
Параметризованные тесты (@Parameterized) публикуют аргументы только через свою фабрику (JUnit 4 не отдаёт их ни в Description, ни листенеру):
java
@RunWith(Parameterized.class)
@Parameterized.UseParametersRunnerFactory(DoqaParametersRunnerFactory.class)
public class LoginParamTests { /* … */ }Фабрика подставляет рунер адаптера, поэтому класс с ней получает и остальное, что даёт DoqaRunner: отдельные шаги на @Before/@After, физический деселект в селективном прогоне и порядок по плану. Узлы @BeforeClass/@AfterClass остаются на базовом уровне: Parameterized выполняет их один раз вокруг всех наборов данных, во внешнем рунере.
Имена параметров берутся из полей @Parameterized.Parameter, а при инъекции через конструктор — из имён его аргументов, для чего проект компилируется с флагом -parameters. Иначе параметры называются arg0, arg1.
Без фабрики тест всё равно репортится, но параметр восстанавливается из имени инвокации: @Parameters(name = "{index}: browser={0}") даст один параметр arguments со значением browser=chrome, а имя по умолчанию (только индекс) — ни одного. Плейсхолдер {имяАргумента} в @DoqaId/@DoqaTitle при этом не раскрывается; обходной путь — Doqa.addParameter("browser", browser) или Doqa.addExternalId("LOGIN-" + browser) в теле теста.
Маппинг исходов
| Что случилось | Исход в DoQA |
|---|---|
Тест прошёл (в том числе @Test(expected = …), отработавший как ожидалось) | passed |
Упала проверка (AssertionError, ComparisonFailure, AssertJ, opentest4j) | failed |
| Любое другое исключение | broken |
@Ignore на методе или на классе | skipped |
Невыполненное Assume — в тесте, в @Before или в @BeforeClass | skipped |
Упало @BeforeClass/@ClassRule — результат заводится по одному на каждый тест класса, плюс ошибка на узле @BeforeClass. Один тест может упасть дважды, в теле и в @After: адаптер склеивает сообщения и трейсы в один результат, исход broken, если хотя бы одна из ошибок не assertion, иначе failed.
Особенности и ограничения
Адаптеру в JUnit 4 негде отфильтровать тесты: листенер не может пропустить тест, а Filter провайдеры surefire наружу не отдают. Поэтому исключение из селективного прогона устроено тремя уровнями, а не одним PostDiscoveryFilter, как в JUnit 5.
Селективный запуск: три уровня
При запуске выбранных автотестов из DoQA (adapterMode=0) применяется тот из уровней, что доступен классу:
- Базовый слой (только листенер, код менять не нужно) — гейт на отправке: тесты исполняются все, результаты невыбранных не отправляются. Прогон корректный, но время CI не экономится.
@RunWith(DoqaRunner.class)— физический деселект: невыбранный тест помечается ignored, JUnit сообщаетskipped, адаптер по нему молчит. То же включает и порядок по плану (см. ниже).DoqaSelectRule— для классов со своим рунером (SpringRunner,MockitoJUnitRunner,Suite,Enclosed), гдеDoqaRunnerне подходит; у@Parameterizedфизический деселект даёт фабрика. Правило скипает невыбранный тест через assumption — не исполняются ни@Before, ни тело:
java
public class SpringLoginTests {
@ClassRule
public static final DoqaSelectRule CLASS_SELECT = new DoqaSelectRule();
@Rule
public final DoqaSelectRule select = new DoqaSelectRule();
}@Rule скипает конкретный тест, @ClassRule — весь класс целиком, если не выбран ни один его тест. Вне режима 0, а также если DoQA не смог сопоставить тест по внешнему ID, фильтр — строгий no-op: адаптер не имеет права выкинуть тест из прогона из-за собственной ошибки идентификации.
Порядок прохождения по плану DoQA
Работает автоматически, но только с рунером адаптера — @RunWith(DoqaRunner.class) или фабрикой для @Parameterized: методы внутри класса идут по позиции их внешнего ID в плане. Порядок классов задаёт хост (например, runOrder surefire), а для классов с @FixMethodOrder план не применяется никогда.
Параллельность
С surefire parallel в базовом слое (только листенер) шаги и вложения внутри класс-фикстур не гарантируются. Сами узлы фикстур при этом записываются. С @RunWith(DoqaRunner.class) этого ограничения нет.
Доставка результатов в реальном времени
При importRealtime=true результаты класса уходят одной порцией, когда класс завершился, уже после его @AfterClass, с полными узлами класс-фикстур. На JUnit 4.12 suite-событий нет, и предыдущий класс отправляется только со стартом следующего, а последний — вместе с концом прогона (ещё один довод держать минимум 4.13).
Прочие ограничения
- Минимум 4.13 — на 4.12 не работают suite-события и перехват отдельного фикстур-метода: узлы
@BeforeClass/@AfterClassбазовым слоем не собираются,@Before/@Afterне становятся отдельными шагами даже с рунером. @Rule/@ClassRule(кромеDoqaSelectRule) не становятся отдельными узлами отчёта.@Theory-тесты репортятся как обычные. Значения датапоинтов в параметры не попадают. Динамических тестов (аналог JUnit 5@TestFactory) в JUnit 4 нет вовсе.- Несколько форков surefire (
forkCount>1илиreuseForks=false): каждый форк создаёт свой прогон в режиме2. Решение: задатьtestRunIdиadapterMode=1(или0). - Не подключайте к одному прогону сразу два JVM-адаптера DoQA: идентификация фреймворка в общем ядре одна на JVM.
JUnit 4 через JUnit Platform (vintage)
Прогон переходит на платформу сам, как только на тестовом classpath появляется junit-jupiter: surefire переключается на платформенный провайдер, и без junit-vintage-engine классы JUnit 4 перестают находиться. Сборка не падает, а в логе Tests run: 0.
Если JUnit 4 гоняется через junit-platform с junit-vintage-engine, деселект на discovery выполняет doqa-junit5 своим PostDiscoveryFilter: тесты JUnit 4 приходят к нему от vintage-движка. doqa-junit4 в таком прогоне не участвует: специфика этой страницы (узлы @BeforeClass, фазы @Before/@After, три уровня селекции) не появляется, а fallback-ID считается иначе. На этом пути размечайте тесты явным @DoqaId.
Совместимость с Allure
Проект, размеченный Allure-аннотациями, адаптер подхватывает без правок кода, как и остальные JVM-адаптеры DoQA. Что именно поддерживается и как переехать на разметку @Doqa* — Переезд с Allure.
Смотрите также
- Адаптер JUnit 5 — общий для всех JVM-адаптеров DoQA справочник по аннотациям, ключам конфигурации и режимам прогона
- Адаптеры для тестовых фреймворков — когда адаптер, а когда отчёты
- Запуск автотестов из интерфейса DoQA — как формируется прогон для селективного режима
- Первый прогон автотестов — сценарий первой интеграции с DoQA