Вступление
Вы всё ещё тестируете с помощью 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
.картинка
