Тестирование через абстрактные классы в TestNG

  • Tutorial

Вступление


Вы всё ещё тестируете с помощью 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;
	}
}


Исходные условия для тестов


Итак, что мы хотим протестировать?
  1. Количество обработанных департаментов
  2. Количество сотрудников в департаменте dep1
  3. Фамилию у первого сотрудника в департаменте dep0 для организации org0
  4. Фамилию у второго сотрудника в департаменте 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.
картинка
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 9
  • +3
    Не могу сдержать Grammar Nazi в себе и не заметить о том, как дико бесит слово «datas». Английское data это множественное число от datum, поэтому употребление в виде datas абсолютно неправильно)
    Конечно можно было бы написать об этом и в личку, но в последнее время встречаюсь с такой ошибкой довольно часто и потому надеюсь, что лишнее упоминание поможет в деле повышения грамотности населения)
    • 0
      Я исправлюсь и не буду больше верить гуглу :)

      ЗЫ: а вы скобки открыть забыли или двоеточие не поставили?
      • 0
        Язык оригинала: литовский
        • 0
          если переключиться на литовский, то перевод на русский будет иной — проверьте.
        • 0
          Попробуйте сделать перевод слова «данные» на английский и найти в предлагаемых вариантах слово «datas». Кроме этого, попробуйте погуглить слово datas (в кавычках, для точного соответствия).
          • 0
            Я же уже сказал, что больше не буду верить гуглу. Если вас сбил мой смайл, то мне сделали замечание с улыбкой и я ответил с улыбкой.
            Предлагаю на этом закончить тему с «datas». Или вы предлагаете скриншот перезалить?
            • 0
              Это я скорее не к текущей ситуации, а на будущее, и не в коем случае не как претензия, а просто предложил пару способов проверки. Google Translate вообще иногда выдаёт очень странные вещи, за ним лучше всегда перепроверять.
      • 0
        Вы ещё до сих пор пытаетесь найти преимущества у TestNG, вместо того, чтобы пользоваться Споком? Тогда мы идём к вам.
        Всё что сказано в данной статье относительно преимуществ TestNG над JUnit-ом — абсолютно справедливо, но сегодня в этом полностью отпадает необходимость, потому как есть более удобные и новые вещи. DataDriven тесты НАМНОГО удобней делать на споке, а главное это даёт возможность хотя бы тесты писать на груви.
        Будет время напишу статью, пока опубликую видео своего доклада с JEConfa, как только его зальют.

        Или читайте в первоисточнике: здесь
        • 0
          А можно для аналогичного синтетического теста пример на споке? для наглядности…

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.