CNN TürkMASTERCHEF ELEME ADAYLARI BELLİ OLDU: 5 Kasım MasterChef'te dokunulmazlığı kim kazandı? İşte eleme adaylarıInquirerZalameda asked to restore people’s trust in the justice systemPunchGamblers behind 42% of tennis players’ online abuse — ReportBollywood HungamaRanveer Singh’s Pralay shoot sparks BIG controversy at Mumbai’s Azad Maidan; Under-14 and Under-16 cricket matches POSTPONED; MCA FUMES over disruptionThe Jerusalem PostNorth Korea's army of world-class hackers is secret weapon that took down BinanceSky TG24Data center in Italia, investimenti potenziali per 37 miliardi: da Amazon a OpenAI한겨레11월7일 인사ZDF heuteEntdecken Sie das ZDF-Nachrichtenstudioسكاي نيوز عربيةبلاغات غامضة تشل رحلات جوية.. ماذا يحدث في سماء أوروبا؟VanguardBullem takes over as C’River PDP ChairmanAntara NewsScarlett Becomes the Only Indonesian Brand Selected for BeautyMatter USA & BeautyMatter China ASIA15 202620 MinutenHandel in Südkorea kurz ausgesetzt: Aktienmärkte im Sinkflug
The Daily Newsstand · Free, Always
Friday, November 6, 2026

Как не кликать предусловия в UI-тестах: готовим полис через API и Oracle

Translate

О чём статья. UI-тест часто проверяет одну кнопку, а подготовка данных занимает больше места, чем сама проверка. Покажем, как мы решаем это в автотестах страхования жизни: человека создаем через API, полис оформляем в браузере, нужный статус выставляем процедурой Oracle, а результат проверяем в интерфейсе.

Что вы получите. Рабочую схему подготовки данных с реальным кодом и пятью приемами, которые можно перенести на свой проект.

Кому интересно. Автоматизаторам UI-тестов в системах, где у сущности длинный жизненный цикл: договоры, заявки, заказы.

Допустим, нам нужно проверить кнопку отмены полиса. Открыть договор, нажать кнопку отмены, проверить результат. Звучит как небольшой UI-тест.

Но сначала договор должен существовать. У него должны быть страхователь, нужный способ подписания, даты и подходящий статус. Если проверяем поведение действующего полиса, только что сохраненная форма нам еще не подходит. Подготовка начинает занимать больше места, чем само действие, ради которого написан тест.

Разберем, как эта задача решена в проекте автотестов команды инвестиционного и накопительного страхования жизни в АльфаСтрахование-Жизнь: человека создаем через API, полис оформляем в браузере, нужное состояние подготавливаем через Oracle. Затем возвращаемся в интерфейс и проверяем, что получили именно тот договор, который ожидали.

Проект написан на Java 21 и собирается Maven. Тесты запускает JUnit 5, а Spring Framework связывает клиенты, сервисы и фабрики данных. Для запросов к API используем REST Assured, для работы с браузером — Selenide, для доступа к Oracle — Spring JDBC. Проверки пишем с AssertJ, ожидание асинхронных результатов — с Awaitility, а шаги и результаты собираем в отчеты Allure.

Пойдем по одному сценарию из PolicyCancelTest. По дороге посмотрим внутрь сервисов и фабрик — чтобы за коротким вызовом activatePolicy() не оставалось магии. Импорты и часть служебных аннотаций опущены, фрагменты из разных классов показаны отдельно.

Сначала договоримся, что мы готовим

Страхователь — человек, который заключает договор. Застрахованный — тот, чья жизнь или здоровье застрахованы. В нашем основном сценарии это один человек. Страховая премия — сумма, которую он платит по договору.

У договора есть несколько этапов оформления. Сохранить предложение, оформить полис и получить состояние, которое тест считает действующим или оплаченным, — разные шаги. Конкретное название статуса в интерфейсе также зависит от способа подписания.

Поэтому дальше будем различать две вещи: действия, которые меняют данные системы, и Java-объект с ожидаемыми значениями. Названия методов иногда похожи, но разница принципиальная. Создать ожидаемую строку со статусом «Действует» можно одной операцией в памяти; реальный полис от этого действующим не станет.

Весь маршрут выглядит так:

1. Создадим страхователя через API

Подготовка начинается перед каждым тестом. Открываем страницу оформления продукта и создаем человека (страхователь/застрахованный):

@BeforeEach
void openCreatePolicyPage() {
    productsListSteps.openCreatePolicyPage(businessRequirements);
    insurant = contractorService.createPersonForPolicy(agent.username());
}

agent.username() определяет агента-партнёра, от имени которого выполняется API-операция. insurant — поле тестового класса типа PersonDetails; дальше его передадим шагам браузера и фабрике ожиданий.

Почему для этого не заполняется отдельная форма в UI? В данном сценарии нас интересует поведение полиса. Создание человека — его предусловие. Здесь удобно использовать уже имеющийся API-клиент, а браузерные действия оставить для оформления договора и проверяемой операции.

При этом человек действительно создается в системе. Мы не подменяем ответ браузеру и не возвращаем выдуманный ID из заглушки. Посмотрим на методы ContractorService:

public PersonDetails createPersonForPolicy(String agentLogin) {
    return createPerson(agentLogin, PASSPORT, FACT, REGISTRATION, MOBILE, EMAIL);
}

public PersonDetails createPerson(
        String agentLogin, ContractorParamType... params) {
    ContractorPersonDto person = contractorFactory.createPersonWithAllFields();

    Long id = contractorController
            .putContractorPerson(person, agentLogin).getData();
    PersonDetails personDetails = contractorMapper
            .mapToPersonDto(person).setId(id);

    Arrays.stream(params).forEach(
            param -> createPersonParam(personDetails, param, id, agentLogin));

    return personDetails;
}

Сначала фабрика собирает данные человека, затем контроллер отправляет запрос и получает ID. После этого добавляются паспорт, фактический и регистрационный адреса, телефон и email. Метод createPersonParam() выбирает нужную операцию по типу реквизита и обновляет возвращаемый объект.

На выходе у теста есть PersonDetails: идентификатор, ФИО и дополнительные данные. Этот объект связывает дальнейшие шаги. По нему выбирается страхователь в форме, по ФИО ищется договор, из него же строится ожидаемое имя в таблице.

Интересная деталь: основной объект здесь собирается из отправленных данных и полученного ID. Полного повторного чтения человека внутри этих двух методов нет. Это подготовка данных для следующего действия, а не самостоятельная проверка корректности всех API-операций создания контрагента.

2. Оформим полис в браузере

Теперь передаем человека, способ подписания и премию в ProductSteps. В проекте этот слой объединяет действия с несколькими частями страницы. Тесту не нужно знать, в какой секции находится поле суммы и где выбирается подпись.

Внутри два последовательных шага:

public ProductSteps createPolicyWithSignatureType(
        PersonDetails client, String signatureType, PremiumParam premium) {
    fillPolicyWithSignature(client, signatureType, premium);

    productPage.clickOnSaveButton();
    productAlertSteps.verifySuccessAlert(OPERATION_SUCCESS);
    verifyContractNumberCreated();
    return this;
}

public ProductSteps createAndConvertToPolicy(
        PersonDetails client, String signatureType, PremiumParam premium) {
    createPolicyWithSignatureType(client, signatureType, premium);

    productPage.clickOnConvertToPolicyButton();
    productAlertSteps.verifySuccessAlert(POLICY_CREATED);
    verifyContractNumberCreated();
    return this;
}

fillPolicyWithSignature() выбирает страхователя, заполняет премию и валюту, затем устанавливает способ подписания. Первый метод сохраняет форму и проверяет результат. Второй нажимает кнопку оформления полиса и снова проверяет сообщение и наличие номера.

Такой код удобно читать сверху вниз: заполнить → сохранить → проверить → оформить → проверить. Если сломается сохранение, выполнение остановится на этом участке. Тест не будет искать внутренний ID для договора, который еще не удалось оформить.

Премия в этом сценарии выбирается фабрикой из параметров продукта. Это помогает получить входные данные для поддерживаемой конфигурации, но здесь мы не разбираем граничные значения суммы. Их проверяют другие тесты. Для нашей задачи важно сохранить выбранную премию в договоре и пройти оформление.

3. Свяжем номер из интерфейса с ID в базе

После оформления тест извлекает два номера, а затем находит внутренний ID:

long offerNumber = productSteps.getOfferNumber();
String contractNumber = productSteps.getContractNumber();

Long policyId = policyDao.getPolicyIdByPolicyNumber(contractNumber);
LocalDate insuranceStartDate = policyActivationSteps.activatePolicy(policyId);

Здесь три похожих идентификатора, которые легко перепутать. offerNumber — номер предложения: позже по нему будет выбрана строка в таблице. contractNumber — номер договора, который читается в UI и используется для открытия карточки. policyId — внутренний идентификатор записи, который нужен процедурам Oracle.

Подготовка должна изменить тот полис, который только что оформил текущий тест. Нельзя взять случайный ID со стенда и затем проверить другой договор в браузере: такой сценарий может выглядеть рабочим, но его шаги не будут связаны между собой полноценным E2E сценарием.

Поиск ID устроен в PolicyDao так. Аннотации кеширования и Allure здесь опущены:

private static final String SELECT_POLICY_ID_BY_POLICY_NO =
        "select policy_id from policy where policy_no = ?";

public Long getPolicyIdByPolicyNumber(String policyNumber) {
    return queryForLong(SELECT_POLICY_ID_BY_POLICY_NO, policyNumber);
}

Номер передается отдельным аргументом вместо подстановки в строку SQL. В базовом DAO queryForLong() вызывает JdbcTemplate.queryForObject() с типом Long.

В этом методе нет собственного цикла ожидания появления записи. Он выполняет запрос. Если в конкретном окружении между оформлением в UI и доступностью записи возникнет задержка, способ ожидания придется определить отдельно.

4. Узнаем, какая дата открыта в учетной системе

Наш тестовый стенд обновляется раз в неделю. Поэтому календарная дата запуска теста и актуальная дата тестового окружения могут расходиться. Именно эту дату окружения возвращает open_date — открытая дата учетной системы INSIS. Ее получаем отдельным запросом из Oracle.

Вот соответствующая часть DualDao:

private static final String SELECT_OPEN_DATE =
        "SELECT sys_days.get_open_date FROM dual";

private static final DateTimeFormatter INSIS_DATE_FORMATTER =
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public LocalDate getOpenDate() {
    return LocalDateTime.parse(
            queryForString(SELECT_OPEN_DATE), INSIS_DATE_FORMATTER
    ).toLocalDate();
}

DAO ожидает строку заданного формата, разбирает ее как LocalDateTime и оставляет календарную дату. Результат затем используется в подготовке полиса.

Для стенда это важное ограничение: дата оформления полиса не должна быть позже open_date. Если создать договор через UI в текущий календарный день, его даты могут оказаться в будущем относительно тестового окружения.

Например, сегодня 07.10, а на стенде open_date равна 04.10. Созданный сегодня полис нельзя корректно активировать с датами оформления и начала страхования 07.10: для учетной системы эта дата еще не наступила. Поэтому откатываем даты оформления и страхового периода полиса, используя 03.10 как опорную дату — open_date минус один день. Затем вызываем автоконвертацию созданного полиса в активный статус.

Если вместо этого взять LocalDate.now().minusDays(1), получим 06.10. Это по-прежнему позже 04.10, и задача подготовки не решена. Поэтому здесь принципиально важно отсчитывать день от open_date, а не от часов машины теста.

И еще: мы не переводим глобальную дату стенда. Дальше в процедуру передается ID одного полиса и целевая дата. Это подготовка конкретного договора, а не изменение времени для всех пользователей системы.

5. Передвинем даты и вызовем автоконвертацию

Теперь можно прочитать PolicyActivationSteps целиком в части основного сценария:

@Step("Активировать полис с id {policyId}")
public LocalDate activatePolicy(long policyId) {
    LocalDate openDate = getDefaultActivationDate();
    isjPkAutoTestProcedures.movePolicyDates(policyId, openDate);
    isjPkAutoTestProcedures.autoConvertPolicy(policyId);
    return openDate;
}

@Step("Получить дату активации по умолчанию")
public LocalDate getDefaultActivationDate() {
    return dualDao.getOpenDate().minusDays(1);
}

Метод получает дату, передает ее в перенос дат полиса, вызывает автоконвертацию и возвращает использованное значение. Если первый вызов завершится исключением, второй не будет выполнен: отдельного обработчика ошибок здесь нет.

Возвращаемая дата — полезная часть контракта этого метода. Мы уже приняли решение, с какой датой готовить полис. Теперь можем передать ровно это значение в ожидаемую строку интерфейса. Повторно вычислять «вчера» в другом классе не требуется.

Метод возвращает тот LocalDate, который сам передал на подготовку. Проверка, что интерфейс действительно показывает ожидаемые значения, будет следующим этапом. Она не заменяется успешным возвратом из activatePolicy().

В этом фрагменте длинные SQL-строки перенесены для чтения; имена вызовов и способ передачи значений сохранены:

private static final String MOVE_POLICY_DATES =
        "BEGIN move_policy_dates(%d, "
        + "to_date('%s', 'dd.mm.yyyy')); END;";

private static final String CHECK_AUTO_CONVERT_POLICY =
        "BEGIN check_auto_convert_policy(%d); END;";

public void movePolicyDates(long policyId, LocalDate date) {
    query(MOVE_POLICY_DATES.formatted(
            policyId, date.format(DD_MM_YYYY.getFormatter())));
}

public void autoConvertPolicy(long policyId) {
    query(CHECK_AUTO_CONVERT_POLICY.formatted(policyId));
}

Под query() здесь скрывается вызов JdbcTemplate.update(). В отличие от поиска по номеру, эти обертки формируют PL/SQL-строку через formatted().

Это скорее компромисс, чем рекомендация. В строку попадают только числовой policyId и дата, отформатированная из LocalDate. Эти значения приходят из кода самого теста, а не из пользовательского ввода. Стенд внутренний, процедуры служебные, поэтому риск SQL-инъекции здесь практически нулевой.

В общем случае так делать не стоит. Безопаснее и единообразнее передавать значения параметрами, как в getPolicyIdByPolicyNumber(). Если будете переносить подход к себе, берите вариант с параметрами.

Исходников самих процедур в репозитории не храним. Важно просто знать, что при выполнении процедуры создаются тестовые платежные записи по полису и происходит конвертация оформленного полиса в действующий статус.

6. Соберем то, что хотим увидеть в таблице

Полис подготовлен. Теперь нужен ожидаемый результат для UI. В тесте это ContractTableRow: номер договора, номер предложения, продукт, даты, статус и ФИО страхователя / застрахованного.

Чтобы не собирать строку руками в каждом тесте, используется ContractFactory. Сначала передаем ей исходные значения через ContractConfig:

ContractConfig config = ContractConfig.builder()
        .offerNumber(offerNumber)
        .contractNum(contractNumber)
        .insurant(insurant)
        .insured(insurant)
        .isPaperSignature(Objects.equals(type, PAPER.getName()))
        .insuranceStartDate(insuranceStartDate)
        .build();

ContractTableRow expectedContract = contractFactory.createPaidContract(config);

Обратите внимание на две строки.

  1. .insured(insurant) задает для этого сценария одного человека в обеих ролях.

  2. .insuranceStartDate(insuranceStartDate) передает дату, которую вернула подготовка через Oracle.

Фабрика получает уже известные тесту данные. Для выбранного способа подписания фабрика определяет ожидаемый статус и собирает объект результата.

Вот метод, отвечающий за соответствующее состояние:

public ContractTableRow createPaidContract(ContractConfig config) {
    ContractState state = config.isPaperSignature() ? ACTIVE : PAID;
    LocalDate insuranceDate = config.getInsuranceStartDate() != null
            ? config.getInsuranceStartDate()
            : LocalDate.now().minusMonths(1);

    return createContract(config.toBuilder()
            .forcedState(state)
            .insuranceStartDate(insuranceDate)
            .build());
}

В основном сценарии дата явно передана, поэтому ветка с LocalDate.now().minusMonths(1) не используется. Значение по умолчанию не обязано совпадать с датой, которую выбрала подготовка через Oracle.

createContract() заполняет номера и имена, переводит состояние в текст и форматирует даты для интерфейса. Имя участника формируется в сокращенном виде из данных человека. Дата оформления в фабрике рассчитывается через LocalDate.now(), а начало страхования в нашем сценарии берется из переданного значения.

Различия состояний можно свести в небольшую таблицу:

Этап ожидания

Бумажная подпись договора

Электронная подпись договора

Предложение

OFFER

QUOTATION

Оформленный полис

POLICY

PENDING

Подготовленное оплаченное состояние

ACTIVE

PAID

Например, ACTIVE отображается как «Действует (Оплачен до конца срока страхования)», а PAID — как «Paid (Regular)». Поэтому в тесте нельзя выбрать одно текстовое ожидание для всех способов подписания.

7. Проверим подготовку глазами пользователя

Теперь снова открываем поиск договоров. searchContractByInsurant() переходит на нужную вкладку, закрывает уведомление, если оно есть, и заполняет фильтр ФИО страхователя. После поиска проверяем строку.

Ниже вызовы из теста и метод проверки из ContractAsserts. Это фрагменты разных классов:

// В тесте:
searchPolicySteps.searchContractByInsurant(insurant);
contractAsserts.checkContractContainsInTable(
        registerInsurancePage, expectedContract);

// В ContractAsserts:
public ContractAsserts checkContractContainsInTable(
        RegisterInsurancePage registerInsurancePage, ContractTableRow expected) {
    assertThat(registerInsurancePage.getSearchContractsTab()
            .getContractsTable().getTableRowByText(expected.getOfferNum()))
            .isEqualTo(expected);
    return this;
}

Поиск сужает список по человеку, а нужная строка выбирается по номеру предложения. Затем сравнивается весь ContractTableRow. У класса задано сравнение полей через Lombok @EqualsAndHashCode, поэтому проверка не ограничивается наличием текста статуса.

Здесь важный момент: до нажатия отмены полиса мы убеждаемся, что подготовка привела к ожидаемому отображению. Если процедура не дала нужного состояния или интерфейс показал другую дату, тест остановится здесь. Падение не будет смешано с результатом следующего пользовательского действия.

У проверки есть понятная граница: она сравнивает поля, которые извлекаются в ContractTableRow.

8. Соберем сценарий целиком

После разбора деталей основной тест уже не выглядит набором неизвестных вызовов. Ниже activeTest: убраны служебные аннотации, длинные вызовы перенесены на несколько строк. Подготовка страхователя остается в показанном выше @BeforeEach.

@ParameterizedTest(name = "с типом подписания: {0}")
@MethodSource("signatureTestArgs")
void activeTest(String type) {
    productSteps.createAndConvertToPolicy(
            insurant, type, policyFactory.getRandomPremium(productParams()));

    long offerNumber = productSteps.getOfferNumber();
    String contractNumber = productSteps.getContractNumber();

    Long policyId = policyDao.getPolicyIdByPolicyNumber(contractNumber);
    LocalDate insuranceStartDate = policyActivationSteps.activatePolicy(policyId);

    ContractConfig config = ContractConfig.builder()
            .offerNumber(offerNumber)
            .contractNum(contractNumber)
            .insurant(insurant)
            .insured(insurant)
            .isPaperSignature(Objects.equals(type, PAPER.getName()))
            .insuranceStartDate(insuranceStartDate)
            .build();
    ContractTableRow expectedContract = contractFactory.createPaidContract(config);

    searchPolicySteps.searchContractByInsurant(insurant);
    contractAsserts.checkContractContainsInTable(
            registerInsurancePage, expectedContract);

    registerInsurancePage.getSearchContractsTab().getContractsTable()
            .openPolicyInfo(contractNumber);
    productSteps.cancelPolicy();

    searchPolicySteps.searchContractByInsurant(insurant);
    contractAsserts.checkContractContainsInTable(
            registerInsurancePage, expectedContract);
}

signatureTestArgs() получает поддерживаемые способы подписания из параметров продукта. Для каждого варианта JUnit запускает сценарий со своим аргументом, а @BeforeEach снова подготавливает человека.

Здесь хорошо видны две проверки одной строки: до пользовательского действия и после. Между ними тест открывает карточку договора и вызывает cancelPolicy(). Этот метод в ProductSteps нажимает кнопку отмены и возвращает управление. expectedContract после этого не меняется.

Почему ожидание остается прежним? По требованиям оплаченный и действующий полис нельзя отменить из интерфейса. При этом система ничего не сообщает: нет ни сообщения об ошибке, ни заблокированной кнопки. Клик просто игнорируется. Проверять можно только результат: тест заново ищет договор, получает строку из системы и убеждается, что статус и даты прежние.

Это более слабая проверка, чем в сценарии с явным сообщением об отказе. Она докажет, что договор не изменился. Но сама по себе не отличит «система проигнорировала нажатие» от «нажатие не дошло до системы». Об этой границе — в разделе ниже.

9. В каком случае ожидается изменение статуса полиса на “Отмена”?

В том же классе есть соседний cancelPolicyTest, который работает с оформленным полисом, но без перевода полиса в оплаченный и действующий статус, путём Oracle-процедуры. В нем после отмены меняется и ожидаемый статус.

Ниже заключительная часть этого теста. К этому моменту expectedContract уже создан через createPolicyContract(config) и проверен в таблице:

registerInsurancePage.getSearchContractsTab().getContractsTable()
        .openPolicyInfo(contractNumber);
productPage.clickOnCancelPolicyButton();

policyStatusManager.moveToCanceledStatus(
        expectedContract, CancellationType.USER_ERROR_CANCEL);

searchPolicySteps.searchContractByInsurant(insurant);
contractAsserts.checkContractContainsInTable(
        registerInsurancePage, expectedContract);

Сначала происходит действие в UI. Затем PolicyStatusManager меняет объект ожиданий. Порядок помогает отличить причину изменения реального полиса от подготовки данных для assert.

За moveToCanceledStatus() находится выбор ожидаемого состояния. Вот фрагменты из одного класса; вспомогательные методы determinePaperSignatureType() и updateContract() опущены:

public void moveToCanceledStatus(
        ContractTableRow contract, CancellationType cancellationType) {
    TargetStatus targetStatus = cancellationType == CancellationType.REGULAR_CANCEL
            ? TargetStatus.CANCELED_STATUS
            : TargetStatus.USER_CANCELED_STATUS;

    moveToStatus(contract, targetStatus);
}

public void moveToStatus(ContractTableRow currentContract, TargetStatus targetStatus) {
    boolean isPaperSignature = determinePaperSignatureType(currentContract);
    ContractState newState = determineContractState(targetStatus, isPaperSignature);
    updateContract(currentContract, newState);
}

private ContractState determineContractState(
        TargetStatus targetStatus, boolean isPaperSignature) {
    return switch (targetStatus) {
        case PROJECT_STATUS -> PROJECT;
        case OFFER_STATUS -> isPaperSignature ? OFFER : QUOTATION;
        case POLICY_STATUS -> isPaperSignature ? POLICY : PENDING;
        case PAYMENT_STATUS -> isPaperSignature ? ACTIVE : PAID;
        case CANCELED_STATUS -> isPaperSignature ? CANCELED : QT_CANCELLED;
        case USER_CANCELED_STATUS -> isPaperSignature ? USER_CANCELED : UW_CANCELLED;
    };
}

Способ подписания здесь определяется по текущему тексту состояния в ожидаемом объекте. updateContract() присваивает новый текст и при необходимости обновляет даты. В этих методах нет API-вызова или DAO-операции.

То есть PolicyStatusManager не управляет реальными переходами полиса. Он помогает тесту описать ожидаемый результат. При чтении теста полезно сразу понимать, какая строка меняет систему, а какая — только ожидание.

Что остается за пределами такого теста

У схемы API → UI → Oracle → UI есть конкретная цель: получить договор с нужными условиями и проверить выбранное поведение интерфейса. Она не доказывает, что тот же договор пройдет весь реальный путь оплаты и обработки без процедур подготовки.

Например, успешное сравнение состояния PAID в таблице не подтверждает работу платежной интеграции. Мы знаем, какие подготовительные методы вызвал тест, но не видим здесь полный платежный сценарий. Процедура перевода полиса в действующий лишь имитирует оплату на тестовом стенде, заполняя необходимые сущности и переводит полис в оплаченный действующий статус. Для проверки пути оплаты с платёжными интеграциями нужен отдельный тест со своими действиями и ожидаемым результатом.

Есть граница и у проверки отмены из раздела 8. Система молча игнорирует нажатие, поэтому проверить текст отказа невозможно. Тест подтверждает, что состояние договора осталось прежним, но не доказывает, что система «увидела» нажатие и осознанно отказала. Если для вас это критично, добавьте независимый источник правды: например, прочитайте состояние полиса в базе до и после клика. В нашем тесте этого нет.

Зависимость от БД тоже вполне реальная. Если недоступна Oracle, не находится запись, “инвалидный” пакет или изменилась процедура, тест может остановиться еще до проверяемой кнопки. В проекте многие шаги размечены @Step: названия операций помогают понять, на каком участке произошел отказ. Очевидно, что наличие шагов не заменяет проверку результата, но делает разбор последовательности удобнее.

Наконец, перенос дат — часть подготовки под условия нашего тестового стенда, который обновляется раз в неделю. open_date задает его актуальную дату, и полис не должен быть оформлен позже нее. В примере с запуском 07.10 и открытой датой 04.10 сначала переносим даты оформления и страхового периода относительно 03.10, затем выполняем активацию.

Что здесь можно использовать в своем проекте

Самая полезная часть этого решения — ясная связь шагов. Созданный через API человек попадает в UI. Номер оформленного договора позволяет найти его внутренний ID. Выбранная дата возвращается из подготовки и попадает в ожидание. Перед проверяемым действием тест убеждается, что интерфейс показывает нужное предусловие.

Для похожей задачи я бы сохранил пять приемов:

  • Вынести подготовку человека или другого вспомогательного объекта в API-сервис с понятным результатом.

  • Сделать подготовку состояния отдельным шагом, принимающим ID конкретного объекта.

  • Возвращать использованные значения, например дату, чтобы дальше не вычислять их заново.

  • Проверить подготовленное состояние через тот интерфейс, с которым работает основной сценарий.

  • Если система не сообщает об отказе, сверять состояние объекта до и после действия, а ограничение проверки честно описать.

В итоге тест можно читать как небольшую историю: подготовили человека, оформили договор, получили нужное состояние, убедились в нем и выполнили действие. При этом в каждом месте видно, что меняется в системе и что именно проверяется. Кроме того, подобный тест будет иметь большую стабильность при регулярных прогонах, ежели тест, в котором вся подготовка и действия пользователя проходят только через UI-интерфейс.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.