Тестирование - DmitryGontarenko/usefultricks GitHub Wiki
Модульное (Unit) - тестирование одного модуля (класса, метода) в полной изоляции от остальной программы, для всех своих зависимостей их поведение "имитируется" (используются заглушки, моки).
Интеграционное - тестирование кода в окружении, близком к фактическому окружению. Главная цель данной стратегии - убедиться в правильности взаимодействия с внешними ресурсами и взаимодействии различных технологий между собой. В интеграционных тестах часто используется работа базой данных.
Функциональное - тестирования ПО в целях проверки его способности решать задачи, нужные пользователям, проще говоря - тестирование какой-то отдельной функции.
Методологии:
TDD (Test-driven development, разработчка через тестирование) - это методология, которая предполагает сначала писать тест, а потом код реализации тестируемого метода.
BDD (Behavior-driven development, разработка через поведение) - методология, является ответвлением от TDD, она предполагает разработку, которая основана на описания поведения. Примером реализации BDD-фреймворка для Java является Cucumber.
JUnit - наиболее популярный фрейморк для модульного тестирования.
Тестовые файлы должны находится по пути /src/test/java/
.
В Intellij idea пакет java должен быть помечен как Test Source Root и благодоря этому не будет включаться в сборку проекта.
Все тестовые классы должны иметь приставку Test, например, так - ContactServiceTest
.
Зависимость:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
@Test
– определяет что метод является тестовым.
@Before
– указывает на то, что метод будет выполнятся перед каждым тестируемым методом.
@After
– указывает на то что метод будет выполнятся после каждого тестируемого метода.
@BeforeClass
– указывает на то, что метод будет выполнятся перед всеми тестами.
@AfterClass
– указывает на то, что метод будет выполнятся после всех тестов.
@Ignore
– говорит, что метод будет проигнорирован в момент проведения тестирования.
@Test(expected = Exception.class)
– указывает на то, что в данном тестовом методе вы преднамеренно ожидаете Exception.
@Test(timeout = 100)
– указывает, что тестируемый метод не должен занимать больше чем 100 миллисекунд.
fail(message)
– указывает на то что бы тестовый метод завалился и при этом выводилось текстовое сообщение.
assertTrue([message], boolean condition)
– проверяет, что логическое условие истинно.
assertsEquals([message], expected, actual)
– сравнивает два объекта методов equals()
.
Примечание: для массивов проверяются ссылки на объекты, а не содержание массивов. И тесты никогда не пройдут в случае если массив примитивного типа. Для сравнения значений массива специальные методы.
assertArrayEquals([message], expected, actual)
- сравнение массивов по значению.
assertNull([message], object)
– проверяет, что объект является пустым null.
assertNotNull([message], object)
– проверяет, что объект не является пустым null.
assertSame([String], expected, actual)
– сравнивает два объекта с помощью оператора ==
, то есть проверяет, являются ли параметры ссылками на один и тот же объект.
assertNotSame([String], expected, actual)
– проверяет, что обе переменные относятся к разным объектам.
Аргумент message, используемый в примерах выше, является опциональным.
Правила - это некое подобие утилит для тестов, которые добавляют функционал до и после выполнения теста. Есть множество встроенных правил, таких как задания таймаута, ожидаемые исключения и т.д. Для объявления правила необходимо создать поле нужного типа, входящего в пакет org.junit.rules и аннотировать такое поле с помощью @Rule
:
public class JUnitTests {
@Rule
public Timeout timeout = new Timeout(1);
@Test
public void methodOneTest() throws InterruptedException {
assertEquals("Paul", "Paul");
Thread.sleep(10); // TestTimedOutException: test timed out after 1 milliseconds
}
@Test
public void methodTwoTest() {
assertEquals("Sarah", "Sarah"); // true
}
}
В данном примере пройдет только второй тест, а первый упадет с исключением, т.к. время выполнения его больше, чем время заданное по таймауту.
JUnit позволяет конфигурировать то, как запускается тест, с помощью @RunWith
. При этом класс, указанный в аннотации должен наследоваться от Runner. Рассмотрим их поподробнее:
JUnit4.class
- runner по умолчанию, необходим для запуска тестов JUnit 4;
Suite.class
- запускает JUnit 4 тесты, но для настройки запускаемых тестов используется @Suite.SuiteClasses()
;
@Suite.SuiteClasses({ClassOneTest.class, ClassTwoTest.class})
@RunWith(Suite.class)
public class JUnitTestRunner {
...
Parameterized.class
- позволяет писать параметризированные тесты. Для этого в тест-классе объявляется статический метод возвращающий список данных, которые затем будут использованы в качестве аргументов конструктора класса.
@RunWith(Parameterized.class)
public class JUnitTestRunner {
private String name;
private int age;
public JUnitTestRunner(String name, int age) {
this.name = name;
this.age = age;
}
@Test
public void runOneTest() {
assertEquals("Sarah", name); // true
assertEquals(23, age); // ComparisonFailure: Expected: Sarah, Actual: Paul
}
@Parameterized.Parameters
public static List<Object[]> isDataEmpty() {
return Arrays.asList(new Object[][] {
{ "Sarah", 23 },
{ "Paul", 30 }
});
}
}
Theories.class
- схож с предыдущим, но параметризирует тестовый метод, а не конструктор. Данные помечаются с помощью @DataPoints
и/или @DataPoint
, тестовый метод — с помощью @Theory
:
@RunWith(Theories.class)
public class JUnitTestRunner {
@DataPoint
public static Object[] nullData = new Object[]{null, true};
@DataPoints
public static Object[][] isEmptyData = new Object[][]{
{"", true},
{"message", false}
};
@Theory
public void runOneTest(Object... testData) {
boolean actual = StringUtils.isEmpty((String) testData[0]);
assertEquals(testData[1], actual);
}
}
Результат во всех трех случаях будет успешным.
Схожие фреймворки:
TestNG - фреймворк для тестирования, схож с JUnit. Имеет такие преимущества, как: параметризированные тесты, зависимые тесты, выполнение тестов в многопоточном режиме, использование XML для конфигурирования. Фреймворк используется для модульного, интеграционного и функционального тестирования.
Moсkito - это фреймворк, который используется для модульного тестирования и позволяет имитировать ожидаемое поведение.
Использовать Mokito в тестируемом классе можно двумя аналогичными способами:
- Статический импорт:
import static org.mockito.Mockito.*;
public class ContactTest {
@Test
public void testName() {
ContactService contactService = mock(ContactService.class); // создается имитация класса ContactService
}
}
- Через аннотации
@RunWith(MockitoJUnitRunner.class)
public class ContactTest {
@Mock
ContactService contactService; // создается имитация класса ContactService
}
Создадим и инициализируем коллекцию, которая будет имитировать объект, получаемый из базы данных. Эта коллекция будет использоваться в последующих примерах:
private final List<Contact> contacts = new ArrayList<Contact>() {{
add(new Contact(1, "Sarah", "Smith"));
add(new Contact(2, "John", "Wick"));
}};
Класс ContactService
будет представлять собой сервис, который взаимодействует с базой данных и содержит такие методы поиска контактов, как - findAll()
и findByFirstName(Contact contact)
.
Определение поведения
when(mock).thenReturn(value)
- этот метод позволяет определить возвращаемое значение при вызове метода mock с заданными параметрами:
@Test
public void testList() {
when(contactService.findAll()).thenReturn(contacts); // contacts - это ArrayList
assertEquals(contactService.findAll(), contacts); // true
}
Если же требуется задать реакцию на вызов метода независимо от аргумента, можно воспользоваться методом any()
:
@Test
public void testName() {
when(contactService.findByFirstName(any())).thenReturn(contacts.get(0));
assertEquals(contactService.findByFirstName("Sarah"), contacts.get(0)); // true
}
Mockito так же позволяет вызывать исключения при определенных условиях:
@Test
public void checkName() {
when(contactService.findByFirstName(any())).thenReturn(contacts.get(0));
Mockito.when(contactService.findByFirstName("123")).thenThrow(IllegalArgumentException.class);
assertEquals(contactService.findByFirstName("123"), contacts.get(0)); // IllegalArgumentException
}
Методы thenReturn
и thenThrow
имеют перегруженные версии, принимающие varargs:
@Test
public void testOne() {
Mockito.when(contactService.findByFirstName(any()))
.thenReturn(contacts.get(0), contacts.get(1))
.thenThrow(IllegalArgumentException.class);
assertEquals(contactService.findByFirstName(any()), contacts.get(0)); // true
assertEquals(contactService.findByFirstName(any()), contacts.get(1)); // true
assertEquals(contactService.findByFirstName(any()), contacts.get(1)); // IllegalArgumentException
}
При первом вызове с заданными параметрами вернется первый элемент списка, затем второй, а третий и все последующие будет выбрасывать исключение IllegalArgumentException.
Подсчет количества вызовов
Для проверки количества вызовов определенных методов, Mockito предоставляет следующие методы:
atLeast(int n)
- не меньше n вызовов;
atLeastOnce()
- хотя бы один вызов;
atMost(int n)
- не более n вызовов;
times(int n)
- n вызовов;
never()
- вызовов не было.
Пример:
@Test
public void testName() {
when(contactService.findByFirstName(any())).thenReturn(contacts.get(0));
assertEquals(contactService.findByFirstName("Sarah"), contacts.get(0)); // true
verify(contactService, atLeastOnce()).findByFirstName(any()); // true
verify(contactService, never()).findAll(); // true
}
Интерфейс Answer
Метод thenAnswer()
принимает реализацию функционального интерфейса Answer<T>
. Он используется когда необходимо описать сложное поведение mock-объекта:
@Test
public void testTwo() {
Contact contact = new Contact(3, "Sarah", "Smith");
when(contactService.findByFirstName("Sarah")) // определяем поведение
.thenAnswer(new Answer<Contact>() {
@Override
public Contact answer(InvocationOnMock invocationOnMock) throws Throwable {
contacts.add(contact); // добавляем в коллекцию созданный выше объект Contact
return contact; // возвращаемым значением при обращении будет наш объект Contact
}
});
assertEquals(contactService.findByFirstName("Sarah"), contact); // true
}
Spy
Mockito позволяет подключать к реальным объектам "шпиона" spy, который может отслеживать возвращаемые значения метода и количество вызовов метода:
@Test
public void testThree() {
Calculator calculator = spy(new Calculator());
when(calculator.sum()).thenReturn(10);
calculator.sum(); // 1й вызов
assertEquals(10, calculator.sum()); // 2й вызов
verify(calculator, atLeast(2)).sum(); // true
}
Источники:
Moсkito. Официальная документация
Hamcrest - это фреймворк, который содержит в себе множество методов на соответствие (matcher`ов). Он используется для модульного тестирования в паре с JUnit или аналогичными фреймворками для тестирования.
Matcher – это выражение, тестирующее на совпадение с определенным условием входящие аргументы.
Подключаем зависимость:
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
Для работы в классе используем статический импорт:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
Рассмотрим простой пример сравнения:
public class HamcrestTest {
@Test
public void testOne() {
assertThat("Paul", equalTo("Paul")); // true
}
}
Единственным доступным предикатом фреймворка является функция assertThat()
.
Мы используем assertThat()
с двумя аргументами. Первым аргументом является проверяемый объект, а вторым – matcher (в данном случае equalTo()), то есть условие проверки.
Logical
allOf()
- И
anyOf()
- ИЛИ
not()
- НЕ
Например, у нас есть строка, и нам надо убедиться что она содержит подстроку "Pa" и заканчивается на "ul":
assertThat("Paul", anyOf(containsString("Pa"), endsWith("ul"))); // true
Text
equalToIgnoringCase()
- проверка строки производится независимо от регистра.
equalToIgnoringWhiteSpace()
- проверка происходит без учета лишних пробелов.
containsString()
, endsWith()
, startsWith()
- проверка на содержание подстроки в целой строке, либо только в ее части (начале или конце).
Number
closeTo()
- используется для сравнений чисел типа double с указанием погрешности.
greaterThan()
, greaterThanOrEqualTo()
, lessThan()
, lessThanOrEqualTo()
- сравнение >, >=, <, <=
Object
equalTo()
- используется для сравнения двух объектов по значению.
hasToString()
- проверяет возвращаемое значение метода toString()
у объекта
instanceOf()
- проверяет, является ли тестируемый объект экземпляром класса или его подкласса.
notNullValue()
, nullValue()
- проверка на null.
sameInstance()
- проверяет, является ли объект тем же самым экземпляром.
Arrays
Создадим массив для дальнейшего тестирования:
Integer[] array = {1, 2, 3};
assertThat(array, not(emptyArray()));
- проверка массива на пустоту
assertThat(array, arrayWithSize(3));
- проверка размера массива
assertThat(array, hasItemInArray(3));
- проверка на то, что массив содержит элемент "3"
assertThat(array, arrayContaining(1, 2, 3));
- проверяет, содержит ли массив переданные значения в строгом порядке
assertThat(array, arrayContainingInAnyOrder(3, 2, 1));
- проверка значений в произвольном порядке
Collections
Создадим коллекцию для дальнейшего тестировани:
List<String> collection = new ArrayList<String>() {{
add("Earth");
add("Mars");
add("Saturn");
}};
assertThat(collection, hasSize(3));
- проверка размера коллекции
assertThat(collection, hasItem("Earth"));
- проверка, содержит ли коллекция переданное значение
assertThat(collection, hasItems("Earth", "Mars"))
- проверка, содержит ли коллекция переданные значения
assertThat(collection, contains("Earth", "Mars", "Saturn"));
- проверка, содержи ли коллекция список переданных объектов в строгом порядке
assertThat(collection, containsInAnyOrder("Mars", "Earth", "Saturn"));
- проверка объектов в произвольном порядке
assertThat(collection, not(empty()));
- проверка коллекции на пустоту
assertThat(stringMap, equalTo(Collections.EMPTY_MAP));
- проверка Map на пустоту
assertThat(stringIterable, emptyIterable());
- проверка Iterable на пустоту
Map:
Создадим Map для тестирования:
Map<String, String> map = new HashMap<String, String>() {{
put("Mazda", "Red");
put("BMW", "Black");
}};
assertThat(map, hasEntry("BMW", "Black"));
- проверка, существует ли такая запись в Map
assertThat(map, hasKey("Mazda"));
- проверка, содержится ли переданный ключ в Map
assertThat(map, hasValue("Black"));
- проверка, содержится ли переданное значение в Map
assertThat(collectionInt, everyItem(greaterThan(10)));
- проверяет что каждый элемент коллекции больше, чем переданное значение. Используется для коллекция типа Integer.
Sugar
is()
- это оболочка, которая не прибавляет дополнительного поведения, а просто стремится повысить удобночитаемость. Все примеры ниже эквивалентны:
assertThat("Biscuit", equalTo("Biscuit"));
assertThat("Biscuit", is(equalTo("Biscuit")));
assertThat("Biscuit", is("Biscuit"));
Источники:
Hamcrest. Документация
Работа с Hamrest
Is Or equalsTo
Подлкючаем зависимости
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>3.0.2</version>
</dependency>
Selenide - это фреймворк для автоматизированного тестирования веб-приложений на основе Selenium WebDriver.
Добавляем зависимость:
<dependency>
<groupId>com.codeborne</groupId>
<artifactId>selenide</artifactId>
<version>5.5.1</version>
<scope>test</scope>
</dependency>
Используем статическим импорт:
import static com.codeborne.selenide.Selenide.*;
import static com.codeborne.selenide.Condition.*;
Рассмотрим простой пример:
@Test
public void searchTest() {
open("https://yandex.ru/");
$(By.name("text")).setValue("Selenide").pressEnter();
$$("li.serp-item").shouldHaveSize(10);
$("li.serp-item div h2 a div.organic__url-text")
.shouldHave(text("Selenide: лаконичные и стабильные UI тесты на Java"));
}
Что здесь происходит:
Мы открывем браузер и переходим по url;
Вводим в поисковую строку запрос и нажимаем Enter;
Проверяем результат: в первом случае - список результатов должен быть равным 10, а во-втором - первый результат должен иметь заданное нами значение;
Важно отметить, что поиск элементов завязан на атрибутах html-тегов странички, а это значит, что при изменении верстки, тесты могут перестать работать.
Основные методы:
open(url)
- открывает браузер по переданному url.
$(sccSelector)
- возвращает первый найденный по CSS селектору объект на странице.
$$(cssSelector)
- возвращает коллекцию найденных по CSS селектору объектов на страницу.
$(By)
- возвращает первый найденный элемен по локатору типа By.
$$(By)
- возвращает коллекцию элементов по локатору типа By.
Например: $(By.name("text"))
, мы ищем элемент, у которого в качестве атрибута name установлено значение "text".
После получения объекта (типа SelenideElement), над ним можно совершать одно или несколько действий, например: $(By.name("text")).setValue("Selenide").pressEnter();
Либо проверить какое-то условие: $$("li.serp-item").shouldHaveSize(10);
Методы поиска внутренних элементов:
find(cssSelector)
/$(cssSelector)
findAll(cssSelector)
/$$(cssSelector)
find(By)
/$(By)
findAll(By)
/$$(By)
Здесь $
и $$
просто алиасы для соответсвтующих команд.
Исходя из этого, можно пошагово найти необходимый элемент внутри внешнего элемента. Например, модифицируем наш запрос выше таким образом, что бы получить список результатов по запросу в поисковой системе Google:
open("https://google.com/");
$(By.name("q")).setValue("Selenide").pressEnter();
$(".bkWMgd").find(".srg").findAll(".g")
.shouldHaveSize(4);
Методы проверки состояния:
should(condition)
/shouldBe(condition)
/shouldHave(condition)
shouldNot(condition)
/shouldNotBe(condition)
/shouldNotHave(condition)
Применение состояний на примере поискового запроса:
open("https://google.com/");
$(By.name("q")).should(exist);
$(By.name("q")).setValue("Selenide").pressEnter();
$(By.name("q")).shouldHave(value("Selenide"));
$(".rc").$(".r a")
.shouldHave(text("Selenide: лаконичные и стабильные UI тесты на Java"));
Со всеми возможными состояниями можно ознакомиться в классе com.codeborne.selenide.Condition
или в JavaDoc. А реализовав подкласс com.codeborne.selenide.Condition
, можно создавать свои условия.
Основные методы действий над элементами:
click()
, doubleClick()
, setValue(String)
/val(String)
, pressEnter
, pressEscape
, pressTab
.
Например: $(By.name("q")).setValue("Selenide").pressEnter();
Основные методы получения статусов элементов и значений их атрибутов:
getValue()
/val()
, text()
Например: $(By.name("q")).val("Selenide")
В этом разделе описана лишь небольшая часть возможностей фреймворка Selenide, для более детального ознакомления можно воспользоваться ссылками из источников.
Источники:
Официальная документация
Selenide gitbooks
Конфигурирования, Настройка Браузера, Page Object и Билд скрипты
PODAM - это инструмент для автоматического заполнения POJO-классов данными.
Этим можно воспользоваться при разработке интеграционных тестов и избавиться от анти-паттерна чрезмерной инициализации (excessive setup).
Maven
<dependency>
<groupId>uk.co.jemos.podam</groupId>
<artifactId>podam</artifactId>
<version>7.1.0.RELEASE</version>
</dependency>
Для дальнейших примеров создадим класс Country
, который будет содержать в себе коллекцию типа City
:
@Data
public class Country {
private String name;
private List<City> cities;
@Data
private class City {
private String name;
private int population;
}
}
Создадим экземпляр Podam фабрики и заполним класс Country
данными:
PodamFactory factory = new PodamFactoryImpl();
Country country = factory.manufacturePojo(Country.class);
System.out.println(country); // Country(name=Qbn9xVjV0g, cities=[City(name=NoQaDM93yn, population=214630906) ... )
Готово! У нас сгенерировалась модель Country
, которая содержит коллекцию City
, состоящую из нескольких элементов.
У Podam существует ряд настраиваемых аннотаций.
PodamStrategyValue
Аннотация @PodamStrategyValue
- позволяет изменить стратегию генерации данных на уровне атрибутов. Для этого необходимо реализовать интерфейс AttributeStrategy<T>
, где T
- тип возвращаемого значения.
Например, я хочу что бы в наименовании страны мне выводились только существующие страны. Рассмотрим на примере:
public class CountryNameStrategy implements AttributeStrategy<String> {
@Override
public String getValue(Class aClass, List list) {
return CountryNames.getRandomCountry();
}
private enum CountryNames {
RUSSIA, CANADA, USA, CHINA, BRAZIL, AUSTRALIA;
private static final List<CountryNames> countryList =
Collections.unmodifiableList(Arrays.asList(values()));
private static final int size = countryList.size();
private static final Random random = new Random();
public static String getRandomCountry() {
return countryList.get(random.nextInt(size)).toString();
}
}
}
Теперь осталось только применим аннотацию к нужному атрибуту класса Country
и указать класс с собственной стратегией:
public class Country {
@PodamStrategyValue(CountryNameStrategy.class)
private String name;
}
PodamBooleanValue
Аннотация @PodamBooleanValue
позволяет устанавливать логическое значение для атрибута:
public Person {
@PodamBooleanValue(boolValue = true)
private boolean isMarried;
}
Numbers Value
Аннотации @PodamByteValue
, @PodamShortValue
, @PodamCharValue
, @PodamIntValue
, @PodamLongValue
, @PodamFloatValue
и @PodamDoubleValue
позволяют настраивать одноименные атрибуты:
public Numbers {
@PodamIntValue(numValue = 5)
private int intFieldPreciseValue;
@PodamIntValue(minValue = 0)
private int intFieldMinValue;
@PodamIntValue(maxValue = 1000)
private int intFieldMaxValue;
@PodamIntValue(minValue = 0, maxValue = 1000)
private int intFielMinAndMaxValue;
}
PodamStringValue
Аннотация @PodamStringValue
позволяет настраивать строковый атрибут.
public Person {
@PodamStringValue(strValue = "Sarah")
private String name;
@PodamStringValue(length = PodamTestConstants.STR_ANNOTATION_TWENTY_LENGTH)
private String lastName;
}
PodamExclude
Аннотация @PodamExclude
позволяет исключить атрибут из заполнения данными:
public Person {
@PodamExclude
private String name;
}
PODAM документация
Habr: PODAM Java объекты для Unit-тестирования
Habr: Анти-паттерны Test Driven Development