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

[Resolved] Запуск теста с разными данными. TestNG @Factory, бросает NullPointerException

tests
factory
data
multiple
java
testng
Теги: #<Tag:0x00007f7b6275ff28> #<Tag:0x00007f7b6275fde8> #<Tag:0x00007f7b6275fca8> #<Tag:0x00007f7b6275fb68> #<Tag:0x00007f7b6275fa28> #<Tag:0x00007f7b6275f8e8>

(James May) #1

Добрый день, использую связку Java+TestNG+Selenium

Возникла потребность в многократном запуске тестов с различными данными.
В итоге было решено использовать @Factory, которая отсылает в тестовый класс проперти с необходимыми данными.

@Factory
public Object[] createInstances() {
    Object[] result = new Object[getTestDataSet().size()];
    for(int i = 0; i < result.length; i++) {
        result[i] = new TestClass(getTestDataSetAsProperties(getTestDataSet().get(i)));
    }
    return result;
}

private ArrayList<String> getTestDataSet(){
    ArrayList<String> testdataPathFiles = new ArrayList<String>();
    Scanner scanner = null;
    try {
        scanner = new Scanner(new File("src\\test\\resources\\testdata\\Testset.txt"));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    while(scanner.hasNext()){
        testdataPathFiles.add(scanner.next());
    }

    /*for(String s : testdataPathFiles){
        System.out.println(s);
    }*/
    return testdataPathFiles;
}

private Properties getTestDataSetAsProperties(String path){
    Properties properties = new Properties();
    InputStream inputStream = null;
    File file = new File(path);
    try{
        inputStream = new FileInputStream(file.getAbsoluteFile());
        properties.load(inputStream);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return properties;
}

В общем то проблем никаких не было, даже с параллельным/удаленным запусками. Ровно до тех пор, пока не был создан testng.xml в котором был уже набор тестов.

<?xml version="1.0" encoding="UTF-8" ?>
<suite name="Projectname. Whatwetestname">
    <test name="WIN8. Chrome">
        <parameter name="pcId" value="win8"/>
        <packages>
            <package name ="de.test.package.testclasses"/>
        </packages>
    </test>
</suite>

Собственно, не важно, параллельный ли запуск, или только на одной машине, не важно, расписаны ли классы после теста в или указан пэкэдж, результат всегда один и тот же.
Выполняется первый класс(не определенный, а тот, который тестнг запустил первым), а затем появляется эксепшн java.lang.NullPointerException

Т.е. такое ощущение, что данные из фабрик не передались в тест, хотя они явно считались.

Как мне кажется, ошибка зарыта где-то в аннотациях, но я, естественно, могу и ошибаться.
Структура тестов выглядит так:

@BeforeTest
Инициализация драйвера, открытие страницы, логин в страницу.

Данная аннотация вынесена в отдельный класс, от которого наследуются всего последующие тесты.
В тестовых классах

@BeforeClass
открытие нужной страницы.

Дальше, в зависимости от ф-циональности мы иногда используем @BeforeMethod который устанавливает какой-нибудь прекондишн

Оканчиваются тесты аннотацией

@AfterMethod
Где мы возвращаемся к какому-то начальному состоянию

@AfterClass
Где мы закрываем страницу

@AfterTest
Где убивается драйвер. 

Собственно, если есть идеи, подскажите, куда копать.

Спасибо!


(Sergey Korol) #2

Во-первых, пока собственно не ясно, откуда вылазит NPE, ибо вы не показали стектрейс.
Во-вторых, вы ведь понимаете, что фабрика создает классы динамически? Т.е. в xml вам нужно указывать лишь классы с фабриками, а не оригиналы. Указав уровень пакета, TestNG будет пытаться экзекьютить все классы. Так вот NPE как раз может лететь на классе-оригинале, который не имеет ни малейшего понятия о данных, которые вы ему хотите передать.

П.С. А вы уверены, что вашу задачу не сможет решить чистый датапровайдер?


(James May) #3

В xml я конечно-же прописывал фабрики.

Задача такая
у нас есть сайт, мы заходим в определенный раздел и тестируем только его.
Для этого в аннотации @BeforeTest мы заходим на сам сайт.

В самом классе, в аннотации @BeforeClass мы заходим в сам раздел.

@BeforeClass
public void setup(){
site.openCatalog(property.getProperty("CATALOG_NAME");
}

И вот NPE валится именно здесь. Т.е. фабрика не передает ему имя каталога.

Причем, если поменять эту аннотацию на @Test(priority = 1), а всем остальным тестам проставить приоритет 2, то этот метод выполняется и вообще никаких проблем не возникает.
Хотя фабрика берет данные еще до того, как инициализировались именно тесты.

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


(Sergey Korol) #4

NPE валится на чем? На site или чтении проперти?
А site на каком уровне создается? Внутри тестового класса или за его пределами? Модификатор доступа у него какой?

П.С. Было бы намного понятней, если бы вы привели весь код. У вас много переменных, которые создаются не пойми где / как, что порождает много вопросов. Суть ведь в том, на каком уровне они создаются и как передаются тестовому классу, который к слову динамический.


(James May) #5

На проперти. Не найдена проперти. Но если выставить все через @Test,все находится.
Site - Создается на уровне BeforeTest и является protected.
Модификатор - protected

Попробовал перенести инициализацию переменной Site в тестовый класс - все заработало.


protected PortalBasePage portalBasePage;

        @BeforeTest
            @Parameters("pcId")
            public void init(String pcId) {
                setupDriver(pcId); //просто создает драйвер в зависимости от того, какое имя браузера установленно
                this.portalBasePage = new PortalBasePage();
                this.portalBasePage.openUrl(WebTestConfig.getInstance().getBaseUrl());
                this.portalBasePage.waitForPage();
                this.portalBasePage.changeTheActualLanguageToTheConfigLanguage();
                this.portalBasePage.loginToPortal(WebTestConfig.getInstance().getFirmName(), WebTestConfig.getInstance().getUserName(), WebTestConfig.getInstance().getPassWord());
                if(!portalBasePage.isLanguageAsExpected()){
                    portalBasePage.changeTheActualLanguageToTheConfigLanguage();
                }
            }

В тестовом классе

@BeforeClass
    public void openCatalog() {
        portalBasePage.clickSpecifiedBrandLogo(properties.getProperty("BRAND"));
    }

В общем, когда я поменял PortalBasePage на private и объявил/инициализировал ее отдельно в тестовом классе, все заработало.

Вопрос - почему?


(Sergey Korol) #6

Значит проблема была не в проперти, а в пейдже. Вы ее инициализировали в @BeforeTest, а обращались - в @BeforeClass. Ввиду того, что фабрика динамически создает тестовые классы, не сложно допустить, что ссылка на вашу пейджу просто теряется, и новые сгенерированные классы ничего о ней не знают. Т.е. первый класс выполнился нормально, ибо @BeforeTest как раз для него и сработал. Интереса ради, выведите в консоль id потока для каждого класса. Если TestNG создает свой независимый поток на каждый динамический класс, то это многое объясняет.

Можете конечно попробовать засетить пейджу в контекст TestNG и вычитывать ее в @BeforeClass. Или как-то расшарить. Но я не уверен, что это сработает. Скорее всего каждому динамическому классу нужен будет свой инстанс пейджи.


(James May) #7

Да. Потоки разные.

Спасибо!
Тему можно пометить как resolved.