При написании тестов, даже самых простых, зачастую используются тестовые объекты. Например в статье про основные возможности 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