Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

[Resolved] Listener for SoftAssert + DataProvider

java
testng
Теги: #<Tag:0x00007f7b70b389e8> #<Tag:0x00007f7b70b38858>

(Sergey356) #1

Всем привет. Решил добавить в проект “мягкие” ассерты. Использую стандартную пару Java+TestNG

В целом структура следующая
Есть базовый класс

    public class TestFixture {
    SoftAsserts soft = new SoftAsserts();
    
     public void check() {
       soft.assertAll();
     }
     
     public void softAssert(boolean b, String s) {
        soft.assertTrue(b,s);
     }
}

Есть тестовый класс, унаследованный от фикстуры:

public class AuthorizationTest extends TestFixture {
  @Test
    public void isPublicAuthorizeOK() {
        softAssert(false/true,"Ошибка!");
        check();
    }
} 

Все работает замечательно.
Теперь мне захотелось не перекладывать на тесты заботы о проверке стека с ошибками, а написать свой listener, который бы выполнял эту часть работы, а из тестов убрать вызов check();

Получилось вот так:

public class SoftAssertListener implements IInvokedMethodListener {
    @Override
    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
    }

    @Override
    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
        if(method.isTestMethod()) {
               ((TestFixture)testResult.getInstance()).checkAssert();
        }
    }
}

Всем работает! Но… мне захотелось посмотреть, как будет все это работать, если тест будет через с DataProvider и не зря!
Подключаю провайдер, получается так:

@Test(dataProviderClass = DataProviders.class, dataProvider = "uni")
    public void isPublicAuthorizeOK(String s) {
        softAssert(s.contains("1"),"Не содержит 1, s = " +s);
}

Запускаю тест и вижу:

Failed tests:
pages.AuthorizationTest.isPublicAuthorizeOK(pages.AuthorizationTest)
Run 1: PASS
Run 2: AuthorizationTest>TestFixture.checkAssert:129 Обнаружены ошибки:
Не содержит 1, s = 2

Но DataProvider должен был запустить метод 7 раз! Начал экспериментировать и обнаружил, что как только в методе softAssert попадется первый false - Test сразу будет провален, последующие его вызовы с другими данными от DataProvider не последуют. Начал копать глубже. Обнаружил следующие вызовы:

Если без listener использовать метод check() прямо в тесте, то будет следующий assert:

AuthorizationTest.isPublicAuthorizeOK:27->TestFixture.checkAssert:129 Текст ошибки

Если через listener

AuthorizationTest>TestFixture.checkAssert:129 Обнаружены ошибки:

В первом случае видно, что метод check() вызывает сам тестовый метод isPublickAuthorizeOK().
Во втором случае вызывает тестовый класс!
Получается во втором случае, при возникновении assertion error при вызове из класса - тестовый метод считается сразу проваленным, не взирая на остальные данные приходящие от DataProvider. Если же вызов будет от тестового метода - то все отработает как часы :slight_smile:

Собственно у меня вопрос: как можно подкрутить мой listener, чтобы вызов был на уровне тестового метода?


(Sergey Korol) #2

По-моему, вы не до конца разобрались, что произошло на самом деле. Сама по себе идея - засунуть assertAll в afterInvocation - неплохая, но вот реализация… Вы ведь пробрасываете AssertionError, который вообще по своей природе не должен обрабатываться прямо внутри дежурного метода TestNG. Естественно никакому юнит фреймворку не понравилась бы подобная наглость. :slight_smile: Потому все последующие попытки циклического экзекьюшена DataProvider'ом будут зарублены на корню. Раз уж вы выносите ассерт на уровень слушателя, вам придется вручную маркировать тест, как зафейлившийся, избегая пробросов AssertionError. Нечто вроде этого:

    @Override
    public void afterInvocation(IInvokedMethod method, ITestResult testResult)
    {
        // RemoteDriver - custom object, which stores SoftAssert instance.
        RemoteDriver driver = (RemoteDriver) testResult.getTestContext()
                .getAttribute(DRIVER_ATTRIBUTE);
        if (driver != null && method.getTestMethod().isTest())
        {
            try
            {
                driver.getSoftAssert().assertAll();
            }
            catch (AssertionError e)
            {
                testResult.setStatus(TestResult.FAILURE);
                testResult.setThrowable(e);
            }
        }
    }

(Sergey356) #3

Спасибо, реально помогло! :slight_smile:
Получается затык был в том, что AssertionError срубал все на корню?


(Sergey Korol) #4

Да, “дежурные” методы TestNG (со спец. аннотациями) не должны плеваться эксепшенами по-дефолту, т.к. на них обычно возлагаются ключевые роли в вашем workflow. AssertionError должен генериться исключительно из тестов. Приведенный случай - скорее исключение для достижения большей универсальности, а также - избавления от дублирующегося кода. Но платить за эту универсальность приходится доп. обработкой AssertionError и ручной маркировкой теста, как упавшего.