TestNG — большой гайд

Что такое TestNG

Фреймворк, имеющий некоторые преимущества на фоне JUnit и NUnit, например тем, что более гибкий и расширяемый. Сделан «по мотивам» JUnit с учетом его слабых мест. Открытый исходный код.

Преимущества

  • Гибкость и расширяемость — как сказано выше, фреймворк изначально создавался как альтернатива JUnit
  • Простота и удобство, юзер-френдли, особенно что касается зависимостей между тестами и параллельного выполнения
  • Акцент на покрытии — возможности гибко группировать тесты/методы и присваивать приоритеты

Архитектура

Suite: тест-сьют в TestNG представляется в виде XML-файла. Обозначается как <suite>.

Test: Здесь один (или более) выполняемых классов, в сьюте обозначается как <test>.

TestNG class: должен иметь как минимум одну аннотацию, и выполняемые методы; обозначается как <class>.

Test Method: @Test — метод с кодом который будет выполняться

Порядок выполнения. Сначала выполняется все с аннотацией @BeforeMethod, далее основные методы, в конце все с аннотацией @AfterMethod.

@BeforeMethod содержит условия, типа открытия браузера, перехода на какую-то страницу, или подобные предварительные действия

В @Test содержится бизнес-логика тест-кейса

В @AfterMethod сохраняются/записываются результаты, закрывается браузер, и т.п.

Практикум

Настройка

Добавляем зависимости:

<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>7.1.0</version>
    <scope>test</scope>
</dependency>

(Maven здесь)

Для Eclipse нужно скачать и поставить плагин из маркетплейса.

Простой кейс

Аннотируем метод через org.test.annotations:

@Test
public void givenNumber_whenEven_thenTrue() {
    assertTrue(number % 2 == 0);
}

Конфигурации

При создании тест-кейсов часто нужно изменять конфигурацию или запустить с другими параметрами инициализации, и/или очистить после выполнения:

@BeforeClass
public void setup() {
    number = 12;
}

@AfterClass
public void tearDown() {
    number = 0;
}

setup(), с аннотацией @BeforeClass, запрашивается до запуска других методов в классе; также и tearDown()-метод вызывается после запуска других методов в классе.

Таким же образом работают аннотации @BeforeMethod, @AfterMethod, @Before/AfterGroup, @Before/AfterTest и @Before/AfterSuite в любых конфигурациях на этих уровнях.

Выполнение

Можно запустить тест-кейс командой “test” в Maven, она затронет кейсы, аннотированные как @Test, и перенесет в «дефолтный» сьют. Можно запустить кейсы через XML-конфигурацию, пользуясь плагином:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <suiteXmlFiles>
            <suiteXmlFile>
               src\test\resources\test_suite.xml
            </suiteXmlFile>
        </suiteXmlFiles>
    </configuration>
</plugin>

Когда имеется несколько XML, покрывающих все кейсы, перечисляем в suiteXmlFiles:

<suiteXmlFiles>
    <suiteXmlFile>
      src/test/resources/parametrized_test.xml
    </suiteXmlFile>
    <suiteXmlFile>
      src/test/resources/registration_test.xml
    </suiteXmlFile>
</suiteXmlFiles>

Для запуска отдельного теста надо прописать библиотеку в classpath, и указать компилированный класс вместе с конфигурацией:

java org.testng.TestNG test_suite.xml

Группы

Удобно группировать тесты, например из массы тест-кейсов выделить часть и запустить отдельно. Так и делаем, через XML-файл:

<suite name="suite">
    <test name="test suite">
        <classes>
            <class name="com.baeldung.RegistrationTest" />
            <class name="com.baeldung.SignInTest" />
        </classes>
    </test>
</suite>

Здесь учтем, что классы RegistrationTest и SignInTest теперь входят в тот же сьют, и если он запускается, кейсы в классе выполнятся.

Кроме сьютов, можно группировать «по методу». Добавляем groups в @Test:

@Test(groups = "regression")
public void givenNegativeNumber_sumLessthanZero_thenCorrect() {
    int sum = numbers.stream().reduce(0, Integer::sum);
 
    assertTrue(sum < 0);
}

Запускаем группы (через XML):

<test name="test groups">
    <groups>
        <run>
            <include name="regression" />
        </run>
    </groups>
    <classes>
        <class
          name="com.baeldung.SummationServiceTest" />
    </classes>
</test>

В SummationServiceTest выполнятся методы с тегом regression.

Параметризация

Параметризация предназначена для запуска теста с разными данными (параметрами). Например, создается отдельный метод для приема данных из другого файла. Тестовый метод становится реюзабельным, может запускаться с разными подборками данных. Применяются аннотации @Parameter и/или @DataProvider. Аннотируем метод при помощи @Parameter:

@Test
@Parameters({"value", "isEven"})
public void
  givenNumberFromXML_ifEvenCheckOK_thenCorrect(int value, boolean isEven) {
    
    assertEquals(isEven, value % 2 == 0);
}

И подаем данные через XML:

<suite name="My test suite">
    <test name="numbersXML">
        <parameter name="value" value="1"/>
        <parameter name="isEven" value="false"/>
        <classes>
            <class name="baeldung.com.ParametrizedTests"/>
        </classes>
    </test>
</suite>

Так делать удобно, но иногда имеем дело со сложными структурами. Тогда работаем через @DataProvider. Например с примитивами:

@DataProvider(name = "numbers")
public static Object[][] evenNumbers() {
    return new Object[][]{{1, false}, {2, true}, {4, true}};
}
 
@Test(dataProvider = "numbers")
public void 
  givenNumberFromDataProvider_ifEvenCheckOK_thenCorrect(Integer number, boolean expected) {    
    assertEquals(expected, number % 2 == 0);
}

Объекты @DataProvider

@Test(dataProvider = "numbersObject")
public void 
  givenNumberObjectFromDataProvider_ifEvenCheckOK_thenCorrect(EvenNumber number) {  
    assertEquals(number.isEven(), number.getValue() % 2 == 0);
}
 
@DataProvider(name = "numbersObject")
public Object[][] parameterProvider() {
    return new Object[][]{{new EvenNumber(1, false)},
      {new EvenNumber(2, true)}, {new EvenNumber(4, true)}};
}

Таким образом можно параметризировать любой объект. Этот подход полезен в тест-кейсах интеграционного тестирования.

Пропуск тест-кейсов

Иногда бывает нужно пропустить («скипнуть») какие-то кейсы. В таком случае ставим enabled=false в @Test:

@Test(enabled=false)
public void givenNumbers_sumEquals_thenCorrect() { 
    int sum = numbers.stream.reduce(0, Integer::sum);
    assertEquals(6, sum);
}

Связанные тесты

Когда первый тест-кейс падает, а следующие должны выполняться, при этом не отделяясь как «пропущенные». В TestNG это делается добавлением параметра dependsOnMethod в @Test

@Test
public void givenEmail_ifValid_thenTrue() {
    boolean valid = email.contains("@");
 
    assertEquals(valid, true);
}
 
@Test(dependsOnMethods = {"givenEmail_ifValid_thenTrue"})
public void givenValidEmail_whenLoggedIn_thenTrue() {
    LOGGER.info("Email {} valid >> logging in", email);
}

В данном случае тест-кейс логина зависит от тест-кейса валидации email-адреса. Когда кейс валидации не прошел, кейс логина будет пропущен.

Параллельный запуск

В TestNG есть возможность параллельного режима запуска (многопотокового). Можно настроить выполнение методов/классов/сьютов на выполнение в отдельном потоке для экономии времени.

Классы и методы

Указываем атрибут parallel в теге suite конфигурационного XML, значение classes:

<suite name="suite" parallel="classes" thread-count="2">
    <test name="test suite">
        <classes>
	    <class name="baeldung.com.RegistrationTest" />
            <class name="baeldung.com.SignInTest" />
        </classes>
    </test>
</suite>

Если в XML-конфигурации имеется много test-тегов, они все будут запущены параллельно, если указано parallel = “tests”. Чтобы параллельно запустить отдельные методы, указываем parallel = “methods”.

Методы

Запускаем код в параллельных потоках:

public class MultiThreadedTests {
    
    @Test(threadPoolSize = 5, invocationCount = 10, timeOut = 1000)
    public void givenMethod_whenRunInThreads_thenCorrect() {
        int count = Thread.activeCount();
 
        assertTrue(count > 1);
    }
}

Значение threadPoolSize означает, что метод запущен в n потоках. Значения invocationCount и timeOut означают, что тест будет запущен [invocationCount] раз и завершится, когда выйдет время ожидания timeOut.

TestNG + Selenium

TestNG часто применяется для функционального тестирования, особенно в связке с Selenium (веб-приложения или веб-сервисы через httpClient).

testng-selenium
  1. Создаем класс в TestNG. Кликаем на пакете правой кнопкой, выбираем “Создать/New -> Другой/Other”:
testng-selenium-1
  1. В открывшемся Мастере выбираем папку TestNG и класс:
testng-selenium-2
  1. Нажимаем “New/Создать” и указываем аннотации, которые должны быть в классе:
testng-selenium-3

В нашем случае отметим аннотации BeforeMethod, AfterMethod, BeforeClass, AfterClass. Вводим название XML-файла создаваемого сьюта.

  1. Нажимаем “Finish” и уже можно писать класс, например:
testng-selenium-4
  1. В примере ниже — автоматизированный sign-up flow, то есть регистрация пользователя с именем-паролем:
import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterClass;

public class FirstTestWithTestNGFramework {

WebDriver driver;

@BeforeClass
public void testSetup()
{
System.setProperty("webdriver.chrome.driver", ".\\Driver\\chromedriver.exe");
driver=new ChromeDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.manage().window().maximize();

}

@BeforeMethod
public void openBrowser()
{
driver.get("https://www.browserstack.com/");
driver.findElement(By.id("signupModalButton")).click();
System.out.println("We are currently on the following URL" +driver.getCurrentUrl());
}

@Test(description="This method validates the sign up functionality")
public void signUp()
{
driver.findElement(By.id("user_full_name")).sendKeys("user_name");
driver.findElement(By.id("user_email_login")).sendKeys("email_id");
driver.findElement(By.id("user_password")).sendKeys("password");
driver.findElement(By.xpath("//input[@name='terms_and_conditions']")).click();
driver.findElement(By.id("user_submit")).click();

}

@AfterMethod
public void postSignUp()
{
System.out.println(driver.getCurrentUrl());

}

@AfterClass
public void afterClass()
{
driver.quit();
}

}

Применялась аннотация @BeforeClass.

Действия:

  • В методе @BeforeMethod была открыта страница, и с нее перешли на страницу регистрации.
  • В методе @Test проведена регистрация
  • В методе @AfterMethod вывод URL страницы, на которую зашли
  • В методе @AfterClass закрываем браузер.

Тест готов к запуску, далее проверим его в репортах TestNG и в консоли.

  1. Чтобы запустить репорт, надо выбрать его или прямой запуск как TestNG-класса, или запустить XML-файл с именем класса. Автоматически созданный XML-файл выглядит так:
<?XML version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="Suite">
<test name="Test">
<classes>
<class name=".FirstTestWithTestNGFramework"/>
</classes>
</test> <!-- Test -->
</suite> <!-- Suite -->

Если выполняется несколько классов, можно модифицировать XML-файл, указав их названия. Также можно указать группы тестов. После этого кликаем правой кнопкой на файле и запускаем его. В консоли видим:

testng-selenium-6
  1. Чтобы посмотреть репорт, обновляем папку проекта. Автоматически генерируется папка “test output”. В этой папке найдем файл index.html:
testng-selenium-7
  1. Открываем его и смотрим репорты, какие методы passed/failed:
testng-selenium-8
  1. Валидация. 

Часто она проводится при помощи так называемых assertions, то есть неких «допущений/предположений»:

  • assertTrue — проверяет, условие верно или нет. Если да, тест-кейс passed, прошел:
Assert.assertTrue(condition);
  • assertFalse — наоборот допускается, что условие неверное. Если оно неверное, тест-кейс будет passed:
Assert.assertFalse(condition);
  • assertEquals — сравнивает ожидаемое значение с полученным. Если равны, тест-кейс passed. Можно сравнивать строки, целые значения, объекты:
Assert.assertEquals(actual,expected);
  • assertNotEquals — наоборот:
Assert.assertNotEquals(actual,expected,Message);

Подробнее об аннотациях

Рассмотрим подробнее аннотации, поскольку они важны в TestNG.

Аннотация — это тег с дополнительной информацией о классе/интерфейсе/методе. Аннотации расширяют функциональность фреймворка. 

@Test

Самая важная из аннотаций. Обозначает метод как тестовый; любой метод с этой аннотацией TestNG рассматривает как тестовый:

@Test
public void sampleTest() {   
   //Any test logic
   System.out.println("Hi! ArtOfTesting here!");   
}

Атрибуты аннотаций в TestNG:

  1. description — (по возможности короткое) описание метода:
@Test(description = "Test summary")
  1. dataProvider — применяется в data-driven-тестах. Указывает на источник данных (провайдер):
@Test(dataProvider = "name of dataProvider")
  1. priority — как понятно из названия, установка приоритета. Дефолтное значение = 0, чем выше тем приоритетнее:
@Test(priority = 2)
  1. enabled — будет ли запущен в составе сьюта (и если false, то не будет):
@Test(enabled = false)
  1. groups — группы тестов, в состав которых метод включен:
@Test(groups = { "sanity", "regression" })
  1. dependsOnMethods — «управляющие» методы, то есть метод запустится только после их успешного выполнения:
@Test(dependsOnMethods = { "dependentTestMethodName" })
  1. dependsOnGroups — группы, от выполнения которых зависит запуск метода:
@Test(dependsOnGroups = { "dependentGroup" })
  1. alwaysRun — запуск в любом случае, независимо от «управляющего» метода:
@Test(alwaysRun=True)
  1. timeOut — если превышен этот таймаут (в мс), тест выдаст ошибку:
@Test (timeOut = 500)

Остальные атрибуты, коротко:

  1. invocationCount — сколько раз будет вызван метод
  2. invocationTimeOut — суммарное время (в мс), которое тест должен выполняться; время всех invocationCount суммируется; если invocationCount не указан, его invocationTimeOut игнорируется
  3. successPercentage — количество успешных выполнений, ожидаемое от этого метода
  4. expectedExceptions — список ожидаемых эксепшнов, которые метод должен выдать; если их нет, или эксепшн не входит в список, тест будет обозначен как failure.
  5. dataProviderClass — класс, в котором TestNG будет искать поставщика данных (провайдера). Если dataProviderClass не прописан, будет искать в классе этого метода, или в его базовых классах. Если dataProviderClass указывается, метод должен быть статическим в указанном классе.
  6. singleThreaded — если установлен в состояние true, все методы этого тестового класса будут выполняться в одном потоке, даже если тесты уж выполняются с атрибутом parallel=”methods”. Атрибут singleThreaded применяется только на уровне класса, и будет проигнорирован, если применен на уровне метода. 
  7. threadPoolSize — размер пула потоков данного метода. Метод вызывается из нескольких потоков, invocationCount раз. Если invocationCount не прописан, threadPoolSize будет проигнорен.

Аннотации @BeforeSuite, @AfterSuite, @BeforeClass, @AfterClass, @BeforeTest, @AfterTest достаточно очевидны по их названиям и подробно описаны в рунете, поэтому на них останавливаться не будем. Интересны аннотации @Parameter, @Listener и @Factory.

@Parameter

Передача параметров в сценарий через XML-файл:

@Test (timeOut = 500)<suite name="sampleTestSuite">
   <test name="sampleTest">   
      <parameter name="sampleParamName" value="sampleParamValue"/>
      <classes>
         <class name="TestFile" />
      </classes>      
   </test>
</suite>

Сам сценарий:

public class TestFile {
   @Test
   @Parameters("sampleParamName")
   public void parameterTest(String paramValue) {
      System.out.println("sampleParamName = " + sampleParamName);
   }

@Listener

Активирует некие действия по случившемуся событию. В основном для репортов/логов:

@Listeners(PackageName.CustomizedListenerClassName.class)
public class TestClass { 
    WebDriver driver= new FirefoxDriver();@Test 
    public void testMethod(){
    //test logic
    }
} 

@Factory

Для динамического выполнения кейсов. Аннотацией @Factory передаются параметры в (весь) тестовый класс во время выполнения. Переданные параметры могут быть применены к одному или нескольких методам в классе. Ниже тестовый класс TestFactory запустит метод из TestClass с разными наборами параметров, k1 и k2:

public class TestClass{
    private String str;
 
    //Constructor
    public TestClass(String str) {
        this.str = str;
    }
 
    @Test
    public void TestMethod() {
        System.out.println(str);
    }
}
 
public class TestFactory{
    //Because of @Factory, the test method in class TestClass
    //will run twice with data "k1" and "k2"
    @Factory
    public Object[] factoryMethod() {
        return new Object[] { new TestClass("K1"), new TestClass("k2") };
    }
}

Сравнение с JUnit

TestNGJUnit
Поддержка аннотаций+, лучше чем JUnit+
Поддержка сьютов++
Группирование тестов+
Параметризация примитивов++
Параметризация объектов+
Зависимости между методами+
Названия методов+, меньше ограничений+

Аннотации — в TestNG применяются аннотации для обозначения тестов, установки приоритетов и других деталей. В JUnit тоже есть аннотации, но с ними хуже. Например, в TestNG аннотация @Test идентифицирует тестовый метод; в JUnit аннотация @Test «уточняет», как тест должен запускаться. 

testng-annotations

Assertions — «допущение», что какое-то условие верно/неверно. В TestNG встроенная поддержка assertions, в отличие от JUnit.

Параллельное выполнение — полная поддержка в TestNG, JUnit не поддерживает.

Репорты — Более гибкие в TestNG

Data-driven-тестирование — простая параметризация в TestNG, что удобно в DDT-тестировании. В JUnit нет встроенной поддержки DDT (но есть подключаемые библиотеки).

Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

3 КОММЕНТАРИИ

Подписаться
Уведомить о
guest

3 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Александр
Александр
1 год назад

Спасибо за статью! Я относительно недавно перешел на автотесты и для меня очень актуально.
Подскажите, пожалуйста, как мне запустить сьюиты по отдельности? В моем мавен проекте в pom.xml описаны два сьюита с разными тестами. Но никак не получается запустить их по отдельности. mvn test — запускает оба сьюита. В самом интерфейсе мавена в idea (где можно выбирать вручную профили и плагины типа clean install) когда я ставлю галочку только на один профиль, все равно запускаются оба.
И такой же вопрос касательно группировки тестов в сьюите. Если я создам несколько групп тестов с разными классами в сьюите, то как запускать эти группы по отдельности из одного сюита?

Последний раз редактировалось 1 год назад Александр ем
Sannich
Sannich
1 год назад
Ответить на  Александр

Спасибо за статью, буду в свободное время разбираться.
Вхожу в автоматизацию, сейчас пишу в набор тестов для проекта, и очень не хватает возможностей TestNG, JUnit маловато будет, маловато (хотя я сам и тут начинающий, но тех же приоритетов для методов очень не хватает) Но, надо с XML разибраться
в общем, есть куда стремиться)

Alex
Alex
1 год назад

Как давно автор смотрел на возможности JUnit 5? Он очень активно прогрессирует и уже по многим параметрам обошёл TestNG. Не говоря уже о фактических ошибках вроде «В TestNG встроенная поддержка assertions, в отличие от JUnit.». В JUnit полный набор ассершенов (хотя я бы советовал AssertJ).

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

? Популярное

? Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас