Вступление
Вы всё ещё тестируете с помощью JUnit и не обращаете внимания на TestNG? Тогда мы идём к вам.
Одним из преимуществ TestNG является возможность создания тестовых массивов данных для одного или нескольких тестов. Но мало кто использует такое преимущество от @DataProvider как пустой набор тестовых данных. В чём оно выражается?
Допустим у нас есть некий тест
testData(String value) и метод datas обеспечивающий DataProvider. Если datas вернёт нам массив из 3-х элементов, то testData выполнится 3 раза. Но если datas вернёт нам пустой массив, то testData не выполнится ни разуКартинки

Давайте попробуем воспользоваться данной особенностью.

Каркас приложения для тестирования
Предположим у нас есть некое класс
OrganizationLoader, который читает из некоего каталога XML файлы, где каждый XML представляет собой описание департамента с сотрудниками и прочитанные данные трансформируются в объект Organization. (commit)Исходный код
public class OrganizationLoader { private OrganizationLoader() { } public static Organization loader(File orgDirectory) { if (orgDirectory == null || !orgDirectory.exists() || !orgDirectory.isDirectory()) { return null; } final File[] files = orgDirectory.listFiles(); if (files == null || files.length < 1) { return null; } XStream xStream = new XStream(); xStream.processAnnotations(Department.class); Organization organization = new Organization(); List<Department> departments = new ArrayList<>(); for (File file : files) { try { String xml = FileUtils.readFileToString(file, "UTF-8"); Department department = (Department) xStream.fromXML(xml); departments.add(department); } catch (IOException e) { e.printStackTrace(); } } organization.setDepartments(departments); return organization; } }
public class Organization { private List<Department> departments; public List<Department> getDepartments() { return departments; } public void setDepartments(List<Department> departments) { this.departments = departments; } }
@XStreamAlias("department") public class Department { @XStreamAlias("name") private String name; @XStreamAlias("employees") private List<Employee> employees; public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
@XStreamAlias("employee") public class Employee { @XStreamAlias("lastName") private String lastName; @XStreamAlias("firstName") private String firstName; @XStreamAlias("middleName") private String middleName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getMiddleName() { return middleName; } public void setMiddleName(String middleName) { this.middleName = middleName; } }
Исходные условия для тестов
Итак, что мы хотим протестировать?
- Количество обработанных департаментов
- Количество сотрудников в департаменте dep1
- Фамилию у первого сотрудника в департаменте dep0 для организации org0
- Фамилию у второго сотрудника в департаменте dep2 для организации org1
Простые тесты
Здесь мы напишем обычные тесты без использования абстракций, дабы потом было с чем сравнить commit
Исходный код
public class Organization0Test { private Organization organization; @DataProvider public Object[][] dataEmployeeCount() { return new Object[][]{{"dep1", 2}}; } @DataProvider public Object[][] dataEmployeeLastName() { return new Object[][]{{"dep0", 0, "empLastName0_0"}}; } @BeforeMethod public void setUp() throws Exception { File fRoot = new File("."); File orgDir = new File(fRoot, "src/test/resources/org0"); Assert.assertTrue(orgDir.exists()); Assert.assertTrue(orgDir.isDirectory()); organization = OrganizationLoader.loader(orgDir); Assert.assertNotNull(organization); } @Test public void testDepartmentCount() throws Exception { Assert.assertEquals(organization.getDepartments().size(), 2); } @Test(dependsOnMethods = {"testDepartmentCount"}, dataProvider = "dataEmployeeCount") public void testEmployeesCount(String depName, int countEmployee) throws Exception { final List<Department> departments = organization.getDepartments(); int i = 0; Department department; do { department = departments.get(i++); } while (!department.getName().equals(depName)); Assert.assertEquals(department.getName(), depName); Assert.assertEquals(department.getEmployees().size(), countEmployee); } @Test(dependsOnMethods = {"testDepartmentCount"}, dataProvider = "dataEmployeeLastName") public void testEmployeeLastName(String depName, int employeeIndex, String lastName) throws Exception { final List<Department> departments = organization.getDepartments(); int i = 0; Department department; do { department = departments.get(i++); } while (!department.getName().equals(depName)); Assert.assertEquals(department.getName(), depName); Employee employee = department.getEmployees().get(employeeIndex); Assert.assertEquals(employee.getLastName(), lastName); } }
public class Organization1Test { private Organization organization; @DataProvider public Object[][] dataEmployeeCount() { return new Object[][]{{"dep1", 2}}; } @DataProvider public Object[][] dataEmployeeLastName() { return new Object[][]{{"dep2", 1, "empLastName2_1"}}; } @BeforeMethod public void setUp() throws Exception { File fRoot = new File("."); File orgDir = new File(fRoot, "src/test/resources/org1"); Assert.assertTrue(orgDir.exists()); Assert.assertTrue(orgDir.isDirectory()); organization = OrganizationLoader.loader(orgDir); Assert.assertNotNull(organization); } @Test public void testDepartmentCount() throws Exception { Assert.assertEquals(organization.getDepartments().size(), 3); } @Test(dependsOnMethods = {"testDepartmentCount"}, dataProvider = "dataEmployeeCount") public void testEmployeesCount(String depName, int countEmployee) throws Exception { final List<Department> departments = organization.getDepartments(); int i = 0; Department department; do { department = departments.get(i++); } while (!department.getName().equals(depName)); Assert.assertEquals(department.getName(), depName); Assert.assertEquals(department.getEmployees().size(), countEmployee); } @Test(dependsOnMethods = {"testDepartmentCount"}, dataProvider = "dataEmployeeLastName") public void testEmployeeLastName(String depName, int employeeIndex, String lastName) throws Exception { final List<Department> departments = organization.getDepartments(); int i = 0; Department department; do { department = departments.get(i++); } while (!department.getName().equals(depName)); Assert.assertEquals(department.getName(), depName); Employee employee = department.getEmployees().get(employeeIndex); Assert.assertEquals(employee.getLastName(), lastName); } }
Но мы же не хотим для каждого тестового набора писать новый тест, практически полностью повторяя программный код? Поэтому переходим к следующему шагу.
Написание абстрактного тест-класса и имплементации тестов от него
Создадим абстрактный класс AbstractTests и вынесем в него все тестовые методы. Так же, создадим методы-заглушки для DataProvider. (commit)
Исходный код
abstract public class AbstractTests { protected Organization organization; abstract protected String getOrgName(); @DataProvider public Object[][] dataDepartmentCount() { return new Object[][]{}; } @DataProvider public Object[][] dataEmployeeCount() { return new Object[][]{}; } @DataProvider public Object[][] dataEmployeeLastName() { return new Object[][]{}; } @BeforeMethod public void setUp() throws Exception { File fRoot = new File("."); File orgDir = new File(fRoot, "src/test/resources/" + getOrgName()); Assert.assertTrue(orgDir.exists()); Assert.assertTrue(orgDir.isDirectory()); organization = OrganizationLoader.loader(orgDir); Assert.assertNotNull(organization); } @Test(dataProvider = "dataDepartmentCount") public void testDepartmentCount(int count) throws Exception { Assert.assertEquals(organization.getDepartments().size(), count); } @Test(dependsOnMethods = {"testDepartmentCount"}, dataProvider = "dataEmployeeCount") public void testEmployeesCount(String depName, int countEmployee) throws Exception { final List<Department> departments = organization.getDepartments(); int i = 0; Department department; do { department = departments.get(i++); } while (!department.getName().equals(depName)); Assert.assertEquals(department.getName(), depName); Assert.assertEquals(department.getEmployees().size(), countEmployee); } @Test(dependsOnMethods = {"testDepartmentCount"}, dataProvider = "dataEmployeeLastName") public void testEmployeeLastName(String depName, int employeeIndex, String lastName) throws Exception { final List<Department> departments = organization.getDepartments(); int i = 0; Department department; do { department = departments.get(i++); } while (!department.getName().equals(depName)); Assert.assertEquals(department.getName(), depName); Employee employee = department.getEmployees().get(employeeIndex); Assert.assertEquals(employee.getLastName(), lastName); } }
И перепишем классы
Organization0Test и Organization1Test, прописав там только тестовые наборы данных:Исходный код
public class Organization0Test extends AbstractTests { @Override protected String getOrgName() { return "org0"; } @DataProvider @Override public Object[][] dataDepartmentCount() { return new Object[][]{{2}}; } @DataProvider @Override public Object[][] dataEmployeeCount() { return new Object[][]{{"dep1", 2}}; } @DataProvider @Override public Object[][] dataEmployeeLastName() { return new Object[][]{{"dep0", 0, "empLastName0_0"}}; } }
public class Organization1Test extends AbstractTests { @Override protected String getOrgName() { return "org1"; } @DataProvider @Override public Object[][] dataEmployeeCount() { return new Object[][]{{"dep1", 2}}; } @DataProvider @Override public Object[][] dataEmployeeLastName() { return new Object[][]{{"dep2", 1, "empLastName2_1"}}; } }
Фича раз
Если тестовый метод не выполнился из-за отсутствующих данных в DataProvider, то он всё равно будет считаться выполненным для зависимых тестов.
Это можно увидеть в
Organization1Test — тестовый метод testEmployeesCount зависит от testDepartmentCount, который, согласно отчетам, не выполняется, т.к. для него нет тестового набора данных.Фича два
Тесты ничем не отличаются от прочих методов в Java и их так же можно переопределять, тем самым выстраивая «деревья» из различных тестов. Если читателям интересно, то могу выложить пример такого «дерева».
Нюанс
Если в абстрактном классе
AbstractTests удалить DataProvider-заглушку, то в зависимости от последовательности выполнения тестов, могут не выполниться ни один или выполниться только часть. Ошибки при этом не будет — только предупреждение.Пример в отдельной ветке — была удалёна заглушка
dataDepartmentCount.картинка

