Код, который содержит циклы или рекурсию, может, при определённых условиях, исполняться бесконечно долго.
Ещё встречается код, который должен выполняться фиксированное время. Хороший пример такого кода — генерация виджета персональных новостей на крупном сайте. Он должен выполниться за определённое время, скажем 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.