Вы когда-нибудь читали математическую теорию?
Обычно это выглядит примерно так:
Для всех a, b> 0 верно следующее: a + b> a и a + b> b
Просто заявления обычно сложнее понять.
В таком утверждении есть кое-что интересное: оно справедливо для КАЖДОГО элемента (или комбинации элементов) довольно большого (в данном случае, бесконечного) набора.
Сравните это с утверждением типичного теста:
1
2
3
4
5
6
7
|
@Test public void a_plus_b_is_greater_than_a_and_greater_than_b(){ int a = 2 ; int b = 3 ; assertTrue(a + b > a); assertTrue(a + b > b); } |
Это просто утверждение об одном элементе большого набора, о котором мы говорили. Не очень впечатляет. Конечно, мы можем это исправить, зациклив тест (или используя параметризованные тесты ):
1
2
3
4
5
6
7
8
9
|
@Test public void a_plus_b_is_greater_than_a_and_greater_than_b_multiple_values() { List<Integer> values = Arrays.asList( 1 , 2 , 300 , 400000 ); for (Integer a : values) for (Integer b : values) { assertTrue(a + b > a); assertTrue(a + b > b); } } |
Конечно, это все еще только тестирует несколько значений, но это также стало довольно уродливо. Мы используем 9 строк кода, чтобы проверить, что математик пишет в одной строке! И главное, что это отношение должно выполняться для любого значения a, b, полностью теряется в переводе.
Но есть надежда: Юнит Теории . Давайте посмотрим, как выглядит тест с этим отличным инструментом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import org.junit.experimental.theories.DataPoints; import org.junit.experimental.theories.Theories; import org.junit.experimental.theories.Theory; import org.junit.runner.RunWith; import static org.junit.Assert.assertTrue; @RunWith (Theories. class ) public class AdditionWithTheoriesTest { @DataPoints public static int [] positiveIntegers() { return new int []{ 1 , 10 , 1234567 }; } @Theory public void a_plus_b_is_greater_than_a_and_greater_than_b(Integer a, Integer b) { assertTrue(a + b > a); assertTrue(a + b > b); } } |
В JUnit Theories тест разделяется на две отдельные части: метод, предоставляющий точки данных, т.е. значения, которые будут использоваться для тестов, и саму теорию. Теория выглядит почти как тест, но имеет другую аннотацию (@Theory) и принимает параметры. Теории в классе выполняются с каждой возможной комбинацией точек данных.
Это означает, что если у нас есть более одной теории о нашем объекте испытаний, мы должны объявить точки данных только один раз. Итак, давайте добавим следующую теорию, которая должна быть верна для сложения: a + b = b + a Итак, мы добавим следующую теорию к нашему классу б + а); }
Это работает как талисман, и можно начать видеть, что на самом деле это также сохраняет некоторый код, потому что мы не дублируем точки данных. Но мы тестируем только с положительными целыми числами, в то время как коммутативное свойство должно выполняться для всех целых чисел! Конечно, наша первая теория все еще верна только для положительных чисел
Для этого также есть решение: предположим . Предположим, вы можете проверить предварительные условия для вашей теории. Если это не так для данного набора параметров, теория будет пропущена для этого набора параметров. Итак, наш тест теперь выглядит так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
@RunWith (Theories. class ) public class AdditionWithTheoriesTest { @DataPoints public static int [] integers() { return new int []{ - 1 , - 10 , - 1234567 , 1 , 10 , 1234567 }; } @Theory public void a_plus_b_is_greater_than_a_and_greater_than_b(Integer a, Integer b) { Assume.assumeTrue(a > 0 && b > 0 ); assertTrue(a + b > a); assertTrue(a + b > b); } @Theory public void addition_is_commutative(Integer a, Integer b) { assertTrue(a + b == b + a); } } |
Это делает тесты очень выразительными.
Отделение тестовых данных от реализации теста / теории может иметь другой положительный эффект, кроме краткости: вы можете начать думать о ваших тестовых данных независимо от фактического материала для тестирования.
Давайте сделаем именно это. Если вы хотите проверить метод, который принимает целочисленный аргумент, какие целые могут вызвать проблемы? Это мое предложение:
1
2
3
4
|
@DataPoints public static int [] integers() { return new int []{ 0 , - 1 , - 10 , - 1234567 , 1 , 10 , 1234567 , Integer.MAX_VALUE, Integer.MIN_VALUE};} |
Что, конечно, вызывает сбой теста в нашем примере. Если вы добавите положительное целое число в Integer.MAX_VALUE, вы получите переполнение! Итак, мы только что узнали, что наша теория в ее нынешнем виде неверна! Да, я знаю, что это очевидно, но взгляните на тесты в вашем текущем проекте. Все ли тесты, в которых используется проверка целых чисел с MIN_VALUE, MAX_VALUE, 0, положительным и отрицательным значением? Да, так и думал.
А как насчет более сложных объектов? Строки? Сроки? Коллекции? Или доменные объекты? С помощью JUnit Theories вы можете один раз настроить генераторы тестовых данных, которые будут создавать все сценарии, которые могут создавать проблемы, а затем повторно использовать их во всех ваших тестах с использованием теорий. Это сделает ваши тесты более выразительными и увеличит вероятность обнаружения ошибок.