Юнит-тестирование (модульное тестирование) есть автоматизированное тестирование отдельных функций/классов в независимом окружении, то есть без использования сторонних классов или функций, от которых проверямый код зависит. Идея состоит в том, чтобы убедиться в постоянности поведения самого класса/функции, а не в том, как он взаимодействует с другим кодом.
JUnit — простой и популярный Java фреймворк для написания тестов.
Подготовка
Попросим maven содать пустой проект:
1 2 3 4 | $ mvn archetype:generate -DgroupId=ru.easyjava.junit -DartifactId=hellojunit -Dversion=1 -DinteractiveMode=false [INFO] Scanning for projects... [...skipped...] [INFO] BUILD SUCCESSFUL |
В пустом проекте не всё нас устраивает и поэтому придётся его слегка доработать. Во-первых сменим версию JUnit на более современную:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>ru.easyjava.junit</groupId> <artifactId>hellojunit</artifactId> <packaging>jar</packaging> <version>1</version> <name>hellojunit</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <junit.version>4.12</junit.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project> |
Во-вторых удалим сгенерированный тест, созданный для JUnit3:
1 | hellojunit$ rm src/test/java/ru/easyjava/junit/AppTest.java |
Приветствователь
По умолчанию maven создаёт класс, который здоровается с миром:
1 2 3 4 5 6 7 8 9 10 11 12 13 | package ru.easyjava.junit; /** * Hello world! * */ public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); } } |
Но что, если мы хотим приветствовать другие миры кого-то другого? Давайте напишем класс приветствователя, который будет знать, кого
ему приветствовать:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package ru.easyjava.junit; /** * Генератор приветствий. */ public class Greeter { /** * Создаёт простое приветствие. * * @param name Имя персоны, с которой мы хотим поздороваться или null. * @return Сформированное приветствие. */ public final String greet(final String name) { if (name == null) { return "Hello Anonymous!"; } return "Hello " + name; } } |
Добавим новый класс в приложение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package ru.easyjava.junit; /** * Hello world! * */ public class App { public static void main( String[] args ) { Greeter greeter = new Greeter(); System.out.println( greeter.greet(args[0]) ); } } |
И сразу же опробуем:
1 2 3 4 5 6 7 | $ mvn package [INFO] Scanning for projects... [...skipped...] [INFO] BUILD SUCCESSFUL $ java -cp target/hellojunit-1.jar ru.easyjava.junit.App everyone Hello everyone |
Тест
Каждый раз проверять, правильно ли работает код, довольно утомительно. Автоматизируем проверку с помощью теста:
1 2 3 4 5 6 7 8 9 10 11 12 13 | package ru.easyjava.junit; import org.junit.Test; import static junit.framework.Assert.assertEquals; public class GreeterTest { @Test public void testGreet() { Greeter testedObject = new Greeter(); assertEquals("Hello world!", testedObject.greet("world")); } } |
Напоминаю, что по принятому в maven соглашению классы тестов должны лежать в src/test/java. В нашем случае это будет «src/test/java/ru/easyjava/junit/GreeterTest.java».
Писать тесты очень просто. Каждый ничего не возвращающий ( void ) публичный метод ( public ) с аннотацией @Test является тестом. Внутри метода мы вызываем
метод Greeter.greet() и сравниваем результат ( assertEquals ) его работы с образцом. Попробуем выполнить тест:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | $ mvn test [INFO] Scanning for projects... [...skipped...] ------------------------------------------------------- T E S T S ------------------------------------------------------- Running ru.morningjava.junit.GreeterTest Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.028 sec <<< FAILURE! Results : Failed tests: testGreet(ru.easyjava.junit.GreeterTest) Tests run: 1, Failures: 1, Errors: 0, Skipped: 0 [ERROR] BUILD FAILURE [INFO] There are test failures. Please refer to hellojunit/target/surefire-reports for the individual test results. |
К сожалению тест провалился 🙁 Посмотрим что же в нём не так:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $ cat hellojunit/target/surefire-reports/ru.easyjava.junit.GreeterTest.txt ------------------------------------------------------------------------------- Test set: ru.easyjava.junit.GreeterTest ------------------------------------------------------------------------------- Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.029 sec <<< FAILURE! testGreet(ru.easyjava.junit.GreeterTest) Time elapsed: 0.007 sec <<< FAILURE! junit.framework.ComparisonFailure: expected:<Hello world[!]> but was:<Hello world[]> at junit.framework.Assert.assertEquals(Assert.java:85) at junit.framework.Assert.assertEquals(Assert.java:91) at ru.easyjava.junit.GreeterTest.testGreet(GreeterTest.java:11) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [...] at org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:1009) |
Всё ясно! Тест ожидает что приветствователь закончит приветствие восклицательным знаком, а это не происходит. Давайте немедленно исправим класс Greeter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package ru.easyjava.junit; /** * Генератор приветствий. */ public class Greeter { /** * Создаёт простое приветствие. * * @param name Имя персоны, с которой мы хотим поздороваться или null. * @return Сформированное приветствие. */ public final String greet(final String name) { if (name == null) { return "Hello Anonymous!"; } return "Hello " + name +'!'; } } |
и перепроверим:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ mvn test [INFO] Scanning for projects... [...skipped...] ------------------------------------------------------- T E S T S ------------------------------------------------------- Running ru.easyjava.junit.GreeterTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESSFUL |
Ура, тест прошёл!
В следующем примере я покажу, как писать тесты для настоящих классов, делающих полезные вещи.
Исходный код примера доступен на github