Модульное тестирование действительно важно для качества кода (и для облегчения вашей жизни при поиске ошибок), но если вы никогда раньше этим не занимались, с чего начать? Надеюсь, этот урок поможет.
Давайте начнем
Давайте используем веб-игру TicTacToe на Java, чтобы продемонстрировать, как написать свой первый модульный тест. Во-первых, вот код, который проверяет, выиграл ли кто-нибудь игру.
public Player whoHasWon() {
ArrayList<Coordinate[]> winningPositions = new ArrayList<>();
// rows
winningPositions.add(new Coordinate[] {new Coordinate(0,0), new Coordinate(1,0), new Coordinate(2,0)});
winningPositions.add(new Coordinate[] {new Coordinate(0,1), new Coordinate(1,1), new Coordinate(2,1)});
winningPositions.add(new Coordinate[] {new Coordinate(0,2), new Coordinate(1,2), new Coordinate(2,2)});
// columns
winningPositions.add(new Coordinate[] {new Coordinate(0,0), new Coordinate(0,1), new Coordinate(0,2)});
winningPositions.add(new Coordinate[] {new Coordinate(1,0), new Coordinate(1,1), new Coordinate(1,2)});
winningPositions.add(new Coordinate[] {new Coordinate(2,0), new Coordinate(2,1), new Coordinate(2,2)});
// diagonals
winningPositions.add(new Coordinate[] {new Coordinate(0,0), new Coordinate(1,1), new Coordinate(2,2)});
winningPositions.add(new Coordinate[] {new Coordinate(2,0), new Coordinate(1,1), new Coordinate(0,2)});
for (Coordinate[] winningPosition : winningPositions) {
if (getCell(winningPosition[0]) == getCell(winningPosition[1])
&& getCell(winningPosition[1]) == getCell(winningPosition[2])) {
if (getCell(winningPosition[0]) != null) {
return getCell(winningPosition[0]);
}
}
}
return null;
}
Затем мы должны написать несколько модульных тестов, чтобы убедиться, что поведение корректно и не сломается в будущем. Так с чего же начать?
Нам нужно будет внедрить тестовый фреймворк в проект. В этом примере мы будем использовать JUnit. Чтобы сделать это в Maven, просто добавьте следующее в свои зависимости в pom.xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
Затем пришло время создать класс, в котором будут существовать модульные тесты. Мы хотим, чтобы тесты находились в том же пакете, что и класс, который мы тестируем. В нашем примере это пакет com.diffblue.javademo.tictactoe;
Это означает, что файл должен находиться в папке com/diffblue/javademo/tictactoe.
Для проекта Maven это должно иметь префикс src/test/java.
Поэтому путь от корня проекта — src/test/java/com/diffblue/javademo/tictactoe.
Это означает, что все тестовые классы хорошо отделены от основного исходного кода.
Наконец, класс должен соответствовать формату ‹тестируемый класс›Test, что в данном примере означает, что класс будет BoardTest, а файл, очевидно, BoardTest.java.
Примечание. При выполнении тестов с помощью Maven по умолчанию выполняется поиск классов с суффиксом Test.
Имея все это в виду, создайте класс:
package com.diffblue.javademo.tictactoe;
public class BoardTest {
}
В этом примере давайте создадим тест, чтобы проверить, правильно ли работает обнаружение игрока, выигравшего через верхнюю строку.
Создайте метод для этого теста:
package com.diffblue.javademo.tictactoe;
import org.junit.Test;
public class BoardTest {
@Test
public void playerOTopRow() {
// Arrange
// Act
// Assert
}
}
Это немного отличается от методов, которые вы написали в своем исходном коде. Отработка метода: Сначала идет аннотация, говорящая, что это тест:
@Test
Также обратите внимание на соответствующий импорт. Это сообщит инструментам, что этот метод является тестом. Если вы используете IntelliJ или Eclipse, вы увидите, что теперь вы можете запустить этот метод в качестве теста.
Самое время отметить, что модульные тесты предназначены для защиты от ошибок в будущем. Это означает, что ваш тест должен быть легко читаемым и понятным как другим разработчикам, так и вам в будущем. Чтобы разделить тесты для удобства чтения, давайте добавим три комментария: упорядочивать, действовать и утверждать.
Теперь приступим к созданию теста!
Нам нужно настроить среду, в которой верхняя строка доски — это нули:
package com.diffblue.javademo.tictactoe;
import org.junit.Test;
public class BoardTest {
@Test
public void playerOTopRow() {
// Arrange
Board myBoard = new Board();
myBoard.setCell(new Coordinate(0,0), Player.NOUGHT);
myBoard.setCell(new Coordinate(0,1), Player.CROSS);
myBoard.setCell(new Coordinate(1,0), Player.NOUGHT);
myBoard.setCell(new Coordinate(1,1), Player.CROSS);
myBoard.setCell(new Coordinate(2,0), Player.NOUGHT);
// Act
// Assert
}
}
Теперь у нас есть доска, на которой нолики побеждают в верхнем ряду. Затем вызовите метод whoHasWon() и соберите результат.
package com.diffblue.javademo.tictactoe;
import org.junit.Test;
public class BoardTest {
@Test
public void playerOTopRow() {
// Arrange
Board myBoard = new Board();
myBoard.setCell(new Coordinate(0,0), Player.NOUGHT);
myBoard.setCell(new Coordinate(0,1), Player.CROSS);
myBoard.setCell(new Coordinate(1,0), Player.NOUGHT);
myBoard.setCell(new Coordinate(1,1), Player.CROSS);
myBoard.setCell(new Coordinate(2,0), Player.NOUGHT);
// Act
Player result = myBoard.whoHasWon();
// Assert
}
}
Теперь у нас есть тест, который настраивает среду и вызывает тестируемый метод.
Остался последний шаг для завершения теста: добавить Assert. Утверждение является ключевой частью теста, это то, что проверяется, чтобы сказать, прошел ли тест или нет. Здесь мы проверяем, что результат равен нулю.
Вот наш полный тест:
package com.diffblue.javademo.tictactoe;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class BoardTest {
@Test
public void playerOTopRow() {
// Arrange
Board myBoard = new Board();
myBoard.setCell(new Coordinate(0,0), Player.NOUGHT);
myBoard.setCell(new Coordinate(0,1), Player.CROSS);
myBoard.setCell(new Coordinate(1,0), Player.NOUGHT);
myBoard.setCell(new Coordinate(1,1), Player.CROSS);
myBoard.setCell(new Coordinate(2,0), Player.NOUGHT);
// Act
Player result = myBoard.whoHasWon();
// Assert
assertEquals("Player O didn't win in the top row", Player.NOUGHT, result);
}
}
Глядя на утверждение, обратите внимание на сообщение (первый аргумент утверждения). Когда тест не пройден, это сообщение печатается в результатах. Это может дать четкое представление о том, что пошло не так, человеку, отлаживающему тесты.
Еще одно замечание: порядок аргументов - сначала ожидаемый результат, а затем фактический результат. Поскольку инструменты будут включать ожидаемые и фактические результаты в выходные данные, важно избежать путаницы, получая их правильным путем.
Закончив тест, теперь мы можем запустить все тесты, используя mvn test, и мы увидим, что наш тест проходит:
[INFO] ------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.diffblue.javademo.tictactoe.BoardTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 s - in com.diffblue.javademo.tictactoe.BoardTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
Поздравляю! Итак, вы написали свой первый тест. Полный исходный код этого урока доступен здесь.
Вас также может заинтересовать:
- 7 советов по модульному тестированию
- Fuzz-тестирование Java и других управляемых языков
- Написание эффективных модульных регрессионных тестов
- Какова цель модульного тестирования?
- Сдвиг влево с помощью модульных регрессионных тестов
- Введение в модульные регрессионные тесты
- Отчет Puppet о состоянии DevOps за 2020 год: отсутствие тестов — главная проблема DevOps