Что такое 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. Кликаем на пакете правой кнопкой, выбираем “Создать/New -> Другой/Other”:
- В открывшемся Мастере выбираем папку TestNG и класс:
- Нажимаем “New/Создать” и указываем аннотации, которые должны быть в классе:
В нашем случае отметим аннотации BeforeMethod, AfterMethod, BeforeClass, AfterClass. Вводим название XML-файла создаваемого сьюта.
- Нажимаем “Finish” и уже можно писать класс, например:
- В примере ниже — автоматизированный 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 и в консоли.
- Чтобы запустить репорт, надо выбрать его или прямой запуск как 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-файл, указав их названия. Также можно указать группы тестов. После этого кликаем правой кнопкой на файле и запускаем его. В консоли видим:
- Чтобы посмотреть репорт, обновляем папку проекта. Автоматически генерируется папка “test output”. В этой папке найдем файл index.html:
- Открываем его и смотрим репорты, какие методы passed/failed:
- Валидация.
Часто она проводится при помощи так называемых 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:
- description — (по возможности короткое) описание метода:
@Test(description = "Test summary")
- dataProvider — применяется в data-driven-тестах. Указывает на источник данных (провайдер):
@Test(dataProvider = "name of dataProvider")
- priority — как понятно из названия, установка приоритета. Дефолтное значение = 0, чем выше тем приоритетнее:
@Test(priority = 2)
- enabled — будет ли запущен в составе сьюта (и если false, то не будет):
@Test(enabled = false)
- groups — группы тестов, в состав которых метод включен:
@Test(groups = { "sanity", "regression" })
- dependsOnMethods — «управляющие» методы, то есть метод запустится только после их успешного выполнения:
@Test(dependsOnMethods = { "dependentTestMethodName" })
- dependsOnGroups — группы, от выполнения которых зависит запуск метода:
@Test(dependsOnGroups = { "dependentGroup" })
- alwaysRun — запуск в любом случае, независимо от «управляющего» метода:
@Test(alwaysRun=True)
- timeOut — если превышен этот таймаут (в мс), тест выдаст ошибку:
@Test (timeOut = 500)
Остальные атрибуты, коротко:
- invocationCount — сколько раз будет вызван метод
- invocationTimeOut — суммарное время (в мс), которое тест должен выполняться; время всех invocationCount суммируется; если invocationCount не указан, его invocationTimeOut игнорируется
- successPercentage — количество успешных выполнений, ожидаемое от этого метода
- expectedExceptions — список ожидаемых эксепшнов, которые метод должен выдать; если их нет, или эксепшн не входит в список, тест будет обозначен как failure.
- dataProviderClass — класс, в котором TestNG будет искать поставщика данных (провайдера). Если dataProviderClass не прописан, будет искать в классе этого метода, или в его базовых классах. Если dataProviderClass указывается, метод должен быть статическим в указанном классе.
- singleThreaded — если установлен в состояние true, все методы этого тестового класса будут выполняться в одном потоке, даже если тесты уж выполняются с атрибутом parallel=”methods”. Атрибут singleThreaded применяется только на уровне класса, и будет проигнорирован, если применен на уровне метода.
- 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
TestNG | JUnit | |
Поддержка аннотаций | +, лучше чем JUnit | + |
Поддержка сьютов | + | + |
Группирование тестов | + | — |
Параметризация примитивов | + | + |
Параметризация объектов | + | — |
Зависимости между методами | + | — |
Названия методов | +, меньше ограничений | + |
Аннотации — в TestNG применяются аннотации для обозначения тестов, установки приоритетов и других деталей. В JUnit тоже есть аннотации, но с ними хуже. Например, в TestNG аннотация @Test идентифицирует тестовый метод; в JUnit аннотация @Test «уточняет», как тест должен запускаться.
Assertions — «допущение», что какое-то условие верно/неверно. В TestNG встроенная поддержка assertions, в отличие от JUnit.
Параллельное выполнение — полная поддержка в TestNG, JUnit не поддерживает.
Репорты — Более гибкие в TestNG
Data-driven-тестирование — простая параметризация в TestNG, что удобно в DDT-тестировании. В JUnit нет встроенной поддержки DDT (но есть подключаемые библиотеки).