Суть статей про Юнит-тестирование:В правильных тестах не должно быть [1]
- ввода-вывода
- обращения к ресурсам
- доступа к базе
- доступа к другим приложениям
Классная работа
Рассмотрим пример:package com.blazer.testing;
public class Divider {
private float dividend;
public Divider(float value) {
this.dividend = value;
}
public float divide(float divisor) throws DivisionByZeroException {
if (divisor == 0) {
throw new DivisionByZeroException();
}
return dividend / divisor;
}
public static class DivisionByZeroException extends Exception {
}
}
Покрываем его тестами:
package com.blazer.testing;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
public class DividerTest {
@Test
public void testDivision() throws Divider.DivisionByZeroException {
Divider divider = new Divider(5f);
assertEquals(2.5f, divider.divide(2f));
}
@Test
public void testZeroDividend() throws Divider.DivisionByZeroException {
Divider divider = new Divider(0f);
assertEquals(0f, divider.divide(5f));
}
@Test(expected = Divider.DivisionByZeroException.class)
public void testDivisionByZero() throws Divider.DivisionByZeroException {
Divider divider = new Divider(5f);
divider.divide(0f);
}
}
Не правда ли тестирование это просто?
Домашнее задание
Покройте тестами ваше реальное приложение. В приложении имеется пол-дюжины взаимосвязанных компонентов, доступ к СУБД, файловой системе и написано оно в процедурном стиле.Мне лень было писать процедурный код, поэтому просто опишу "реальное приложение" словами:
- приложение ищет в папке, полученной как параметр, файлы
- имена файлов должны состоять исключительно из цифр
- берется файл с самым наибольшим числом и парсится
- файл - два столбца разделенных пробелом: строка и число
- все неправильные строки игнорируются
- все правильные строки "анти-апсертятся" в базу (найденные ключи удаляются, новые добавляются)
Тестируемость
Первое что приходится делать для тестирования - жестоко дробить код: первое что приходит на ум - вынести парсер и протестировать. Тесты пишутся за пять минут.И тут ступор: что делать дальше, как тестировать остальные 90% кода непонятно [2].Ответ прост не боятся дробить и нарушать инкапсуляцию (в разумных пределах): как сказал автор статьи "доллар за нарушение красоты ОО подхода окупится двадцаткой в тестируемости".
Стабы и моки
Имитируем поведение другого объекта, возвращая на изначально заданные вопросы нужные ответы. Позволяет протестировать работу с файловой системой. Есть ограничение: в общем случае подменяются только публичные методы.
@Test
public void correctWithPadding() throws FileNotFoundException {
File file = mock(File.class);
when(file.isDirectory()).thenReturn(true);
when(file.list()).thenReturn(new String[]{"abc", "0321", "0123", "asd"});
assertEquals("0321", new FileSelector(file).getMaxFile());
}
Иногда хочется протестировать большой внутренний метод. Для этого видится два решения: либо делать метод публичным (нарушение инкапсуляции), либо передавать в класс стратегию (класс с единственным методом).
Фикстуры
Главный затык в этом месте: как создать тестовую среду, на которой можно поработать с базой. Как оказалось, все упиралось в инструменты: с maven и hibernate/migrations можно не напрягаясь подменить рабочую базу маленькой СУБД создаваемой в памяти, как правило, для одной транзакции (в особых случаях коммитить изменения таки приходится). Как только это препятствие было убрано, тестировать работу с базой стало легко и просто.
@Before
public void initDB() throws SQLException, IOException {
connection = DriverManager.getConnection(CONNECTION_STRING, "SA", "");
connection.prepareStatement(INIT_TABLE).execute();
loadFixtures(connection, "com/blazer/homework/fixtures.sql");
}
@After
public void dropDB() throws SQLException, IOException {
connection.rollback();
Closer.close(connection);
}
@Test
public void testGetData() throws SQLException {
Dao dao = new Dao(connection);
Assert.assertEquals(new HashSet() {{
add(new Domain("qwer", 123L));
add(new Domain("asdf", 456L));
add(new Domain("zxcv", 789L));
}}, dao.getData());
}
Продолжение следует
Дальше предстоит разобраться как тестировать пользовательский интерфейс. Здесь все опять упирается в инструментарий: VAADIN генерирует одноразовую и абсолютно нетестируемую разметку. Они даже написали платную тулзу поверх Selenium. При тестировании отображения - смена дизайна поломает все тесты, а тестируя только презентеры в MVP - не протестируем то, что рендерится браузером.
Еще одним переходным этапом может стать использование DI в коде, что резко понизит связанность между модулями и повысит тестируемость кода.
![]() | |||
| Превосходный результат |
[1] Отличная статья, кстати. Жаль, что я её прочитал после написания кода
[2] Застрял на этом этапе на два года
