Юнит-тестирование (модульное тестирование) есть автоматизированное тестирование отдельных функций/классов в независимом окружении, то есть без использования сторонних классов или функций, от которых проверямый код зависит. Идея состоит в том, чтобы убедиться в постоянности поведения самого класса/функции, а не в том, как он взаимодействует с другим кодом.
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