Код, который содержит циклы или рекурсию, может, при определённых условиях, исполняться бесконечно долго.
Ещё встречается код, который должен выполняться фиксированное время. Хороший пример такого кода — генерация виджета персональных новостей на крупном сайте. Он должен выполниться за определённое время, скажем 10 миллисекунд. Соответственно, он должен за указанное время либо вернуть персонализированные новости, либо вернуть какой-либо ответ по умолчанию.
Разумеется, при написании такого кода хочется написать тест, проверяющий это поведение: бесконечные рекурсии и ответ за указанное время. Последнее, правда, больше относится к интеграционным тестам.
Подготовка
Возьмём пустой maven проект и добавим к нему JUnit:
1
2
3
4
|
mvn archetype:generate -DgroupId=ru.easyjava.junit -DartifactId=timeout -Dversion=1 -DinteractiveMode=false
[INFO] Scanning for projects...
-----------
[INFO] BUILD SUCCESS
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.12</junit.version>
<hamcrest.version>1.3</hamcrest.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
|
Простой таймаут
Сегодня мы будем решать уравнения методом бисекции. Уравнение я выбрал погромоздкее, с некрасивыми коэффициентами и каким-то неровным решением: http://www.wolframalpha.com/input/?i=%281%2Fe%5E%28x*ln2.81%29%29%2B7.34sqrt%28x%29-12.6%2B2.9845sin%28x%29
1
2
3
4
5
6
7
8
9
|
/**
* f(x) part of equation
* f(x)=0.
* @param x function parameter.
* @return function value for specified parameter.
*/
public final double equation(final double x) {
return 1/Math.pow(Math.E,Math.log(2.81)*x)+7.34*Math.sqrt(x)-12.6+2.9845*Math.sin(x);
}
|
Код решения тоже прост и незатейлив — прямолинейная реализация метода половинного деления:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* Solves the f(x)=0 equation,
* where f(x) is equation().
* @param a beginning of the interval.
* @param b end of the interval.
* @param error expected precision value.
* @return equation root.
*/
public final double solve(final double a, final double b, final double error) {
double c = a+((b-a)/2);
double bValue = equation(b);
double cValue = equation(c);
if ((b-a)<error) {
return c;
}
if (cValue*bValue<0) {
return solve(c,b,error);
} else {
return solve(a,c,error);
}
}
|
И тест очень и очень простой:
1
2
3
4
|
public void testSolve() throws Exception {
Solver testedObject = new Solver();
assertThat(testedObject.solve(1, 2, 0.01), closeTo(1.65, 0.01));
}
|
Но, если с точностью 0.01 требуется всего несколько шагов итерации, то при 0.0000001 шагов будет уже больше и вычисление займёт ощутимое время (миллисекунду, ага):
1
2
3
4
5
|
@Test(timeout = 1)
public void testSolveSlow() throws Exception {
Solver testedObject = new Solver();
assertThat(testedObject.solve(1, 2, 0.0000001), closeTo(1.65, 0.01));
}
|
Этот тест провалится, если поиск корня уравнения займёт более одной миллисекунды.
Таймауты и правила
Таймаут можно задавать не только для одного теста, но и для всего класса в целом с помощью правил. Допустим, я хочу убедится, что несколько корней вышеописанного уравнения находятся одинаково быстро:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public class SolverRuleTest {
@Rule
public Timeout timeout = new Timeout(1);
@Test
public void testSolve1() throws Exception {
Solver testedObject = new Solver();
assertThat(testedObject.solve(1, 2, 0.0000001), closeTo(1.65, 0.01));
}
@Test
public void testSolve3() throws Exception {
Solver testedObject = new Solver();
assertThat(testedObject.solve(3, 4, 0.0000001), closeTo(1.65, 3.64));
}
@Test
public void testSolve4() throws Exception {
Solver testedObject = new Solver();
assertThat(testedObject.solve(4, 5, 0.0000001), closeTo(4.43, 0.01));
}
}
|
Тесты этой статьи могу проходить успешно, а могут и проваливаться на вашем компьютере. Попробуйте поизменять значения таймаутов, чтобы увидеть оба варианта поведения теста.
Код примера доступен на github.