При написании тестов, даже самых простых, зачастую используются тестовые объекты. Например в статье про основные возможности JUnit я регулярно использовал следующую конструкцию:
1
2
|
String[] expected = {"T", "E", "S", "T"};
String source="T:E:S:T";
|
Что не совсем хорошо: во-первых, это нарушает принцип DRY — Don’t Repeat Yourself (не повторяй себя); во-вторых, если б тестовые объекты были бы хоть немногим сложнее, их регулярное (пере-) создание вылилось бы в полный кошмар. Представьте себе, если бы Вы в каждом тестовом методе писали такое (осторожно, говнокод):
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
|
Organization organization = new Organization();
organization.setId(1L);
organization.setActive(true);
organization.setAddress("Test city, Test street");
organization.setBankName("Test bank");
organization.setBic("12345678987");
organization.setCode("TESTORG");
organization.setContact("Test von Testoff");
organization.setCorrespondentAccount("034009100280477");
organization.setName("Test organization");
organization.setPhones("+70952320829");
organization.setTin("987654321");
organization.setTransactionalAccount("775082001900320");
organization.setTrc("192837465");
Office expected = new Office();
expected.setId(2L);
expected.setOrganization(organization);
expected.setCode(123);
expected.setName("Test branch");
expected.setManagerName("Test Testovich");
expected.setFirstAddress("Default city");
expected.setSecondAddress("Mordor");
expected.setPayableCode("TBP");
expected.setReceivableCode("TBR");
|
Ужасно! Представляете, 80% содержимого теста было бы простым созданием одних и тех же объектов. А уж когда эти объекты меняются, это был бы просто кошмар с переписыванием тестов.
Впрочем для этой проблемы в JUnit есть решение, которое позволяет удобно подготовливать окружение для тестов.
Подготовка
Возьмём код из примера основной функциональности JUnit и скопируем класс StringUtilsTest в StringUtilsSetupTest
Before/After
Любой public void метод, с аннотацией @Before будет вызван автоматически перед запуском каждого теста (то есть столько раз, сколько в классе
тестов). Традиционно такой метод называют setUp :
1
2
3
4
|
@Before
public void setUp() {
//Do setup here
}
|
Используя эту возможность, перенесём создание тестовых данных в метод setUp():
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
|
private String testString;
private String[] testArray;
@Before
public void setUp() {
testString = "T:E:S:T";
testArray = new String[] {"T", "E", "S", "T"};
}
@Test
public void testToArray() {
assertArrayEquals("Wrong array", testArray, StringUtils.toArray(testString, ':'));
assertNull(StringUtils.toArray(null, ':'));
//Don't uncomment next line - it will fail
//assertEquals(testArray, StringUtils.toArray(testString, ':'));
}
@Test
public void testJoinArray() {
assertEquals(testString,StringUtils.joinArray(testArray, ':'));
assertNull(StringUtils.joinArray(null, ':'));
}
// Unchanged methods are not shown.
}
|
Строку и массив, используемые в тестах, я вынес в члены класса и присваиваю им значение перед запуском каждого теста. Теперь, когда мне надо будет изменить тестовые данные, я смогу это сделать в одном месте — в функции setUp() и все остальные тесты, очевидно, будут использовать новые данные.
В случае, если бы я использовал какой-либо большой объект или объект, требующий какой-нибудь обработки после теста (закрытия файла, завершения транзакции, итд.), можно использовать аннотацию @After . Эта аннотация так же требует public void метод, который будет вызван после каждого теста. Традиционно такой метод называют tearDown:
1
2
3
4
|
@After
public void tearDown() {
//Do something
}
|
В моём примере трудно найти полезное применение этому функционалу, но почему-бы не сбрасывать после каждого теста тестовую строку?
1
2
3
4
|
@After
public void tearDown() {
testString=""; //Not very useful, but good enough for the example.
}
|
BeforeClass/AfterClass
Хороший, годный программист в этом месте спросит: «А зачем вообще перед каждым методом инициализировать строку? Почему нельзя обойтись одним разом, ведь строка не меняется?» и будет прав. Обычно аннотации @Before/@After используют для тех тестовых данных, которые изменяются в ходе теста и, следовательно, требуют восстановления исходных значений.
Для значений, которые достаточно проинициализировать один раз или инициализация которых слишком медленна, чтобы делать это перед каждым тестом, предусмотрены аннотации @BeforeClass/@AfterClass . Методы, которые ими можно аннотировать, должны быть статическими, а поэтому работать они могут только с статическими членами класса. Я мог бы сделать переменные testString и testArray статическими, но в иллюстративных целях лучше заведу
новую тестовую переменную, с помощью которой проверю, как разбираются на массив длинные строки:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class StringUtilsTest {
private static String veryLargeString;
@BeforeClass
public static void setUpClass() {
veryLargeString = new BigInteger(16384, new Random()).toString();
}
@AfterClass
public static void tearDownClass() {
veryLargeString = "";
}
@Test
public void testToArrayLong() {
assertEquals("Should not be splitted", veryLargeString, StringUtils.toArray(veryLargeString, ':')[0]);
}
//Other class contents is skipped.
}
|
Обратите внимание, что функции, аннотированные @BeforeClass/@AfterClass традиционно называеются setUpClass и tearDownClass соответственно.
В примере перед запуском тестов генерируется длинная строка, которая освобождается после выполнения всех тестов. Очевидно, что все изменения, внесённые тестами в эту переменную, сохраняются. Следует помнить, что порядок тестов в обычных условиях не определён и может меняться между запусками, если другое поведение не указано явно.
Исходный код примера доступен на github