Один из самых частых ответов на вопрос «Почему я не пишу юнит-тесты» — это вопрос «А кто напишет тесты для моих тестов? Где гарантия, что в моих тестах тоже не будет ошибки?», что свидетельствует о серьёзном недопонимании сути юнит-тестов.
Цель этой заметки — коротко и чётко зафиксировать этот момент, чтобы больше не возникало разногласий.
Итак, юнит-тест — это набор из нескольких примеров, показывающих, что приходит на вход вашей программе, и что получается на выходе. Например, если программа вычисляет длину гипотенузы, то в юнит-тесте достаточно одной строчки:
assertEquals(5, hypotenuse(3, 4));
Эти тестовые значения вы должны придумать сами: посчитать столбиком на бумажке или на калькуляторе.
Правила
Итак, юнит-тест отличается от программы тем, что:
- Он на порядок проще, чем тестируемая программа
- В нём только набор примеров
- В нём нет той логики, что есть в программе (примеры вы придумываете сами)
- Юнит-тест не может и не должен покрывать все возможные случаи. Скорее он должен обозначить все важные случаи, то есть случаи, которые следует отдельно обговорить.
- Юнит-тест должен служить документацией к программе — то есть прочитав юнит-тест, человек должен понять, как работает программа. Обычно для этой цели используются названия тест-методов.
Первый пункт и есть ответ на вопрос «А кто напишет тесты для моих тестов?». Поскольку юнит-тест на порядок проще, чем программа, то тест для него пришлось бы писать ещё на порядок проще, но это практически нереально, поскольку он и так до крайности простой.
Ответ на второй вопрос «Где гарантия, что в моих тестах тоже не будет ошибки?» такой: гарантии нет и быть не может. Но тот факт, что «правильные» ответы, прописанные в тесте, получены другим путём, нежели ответы, выдаваемые программой, позволяет быть более уверенным, что оба пути правильные.
Пример
Давайте попробуем привести здесь минимальный пример юнит-теста, иллюстрирующий всё вышесказанное. Допустим, нам надо написать функцию, решающую, является ли данный год високосным. Напомню, високосный год — это год, который делится на 4, за исключением тех лет, которые делятся на 100, но не делятся на 400.
import org.junit.Test;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
public class LeapYear
{
public static boolean isLeap(int year) {
return year % 4 == 0 year % 100 != 0 || year % 400 == 0;
}
@Test
public void yearDividing4IsLeap() throws Exception
{
assertTrue(isLeap(2008));
assertTrue(isLeap(2012));
assertFalse(isLeap(2011));
}
@Test
public void exceptYearDividing100WhichIsNotLeap() throws Exception
{
assertFalse(isLeap(1900));
assertFalse(isLeap(2100));
}
@Test
public void exceptYearDividing400WhichIsLeap() throws Exception
{
assertTrue(isLeap(1600));
assertTrue(isLeap(2000));
}
}
Как видите, внутри самих тест-кейсов — конкретные простые примеры: 2000, 2008, 1011. Я их придумал сам, из головы. А названия тест-кейсов их поясняют (обобщают) на человеческом языке. Заметьте, названия тест-методов один-в-один совпадают с описанием термина «високосный год», приведённого выше. Так и должно быть: тест должен читаться как документация. А вот так он выглядит, к примеру, в IDEA:
Ошибки
Из-за непонимая сути юнит-тестов при их написании часто допускаются главные ошибки, делающие юнит-тесты бесполезными, трудоёмкими и приводящие к бесконечным спорам о том, нужны ли вообще юнит-тесты.
Первая типичная ошибка — это не проверять вообще ничего. Например:
@Test
public void testLeapYear() throws Exception
{
int year = 2004;
assertEquals(2004, year);
}
Этот юнит-тест бессмысленный, так как он не проверяет нашу программу. Максимум, что он проверяет — это что Java работает правильно, но это уже паранойя. Пусть этим занимается Oracle.
Несмотря на кажущуюся абсурдность, именно с таких тестов начинают все, кто впервые берут в руки JUnit.
Вторая типичная ошибка — это попытаться повторить в тесте ту же логику, что есть в программе. Например:
@Test
public void testLeapYear() throws Exception
{
for (int i=0; i100000; i++) {
assertEquals(i % 4 == 0 i % 100 != 0 || i % 400 == 0, isLeap(i));
}
}
Этот юнит-тест плох как раз по той причине, что в нём программист может допустить те же ошибки, что и в самом коде.
Smells
Признаки того, что юнит-тест неправильный:
- Условия и циклы
Если вы видите в юнит-тесте условия и циклы — это явный признак того, что тест неправильный. Попробуйте избавиться от циклов и написать несколько простых примеров. - Название тест-метода не говорит, что и как должно работать.
Если вы видите в названии только название того, что тестируется — например, testLeapYear, будьте настороже. Вероятно, он не тестирует ничего либо тестирует слишком много. Правильное название тест-метода должно звучать примерно так: «в таких-то условиях такой-то метод должен вести себя так-то и так-то». - В теле тест-метода слишком много assert.
Такой тест-метод проверяет слишком много аспектов. Если он сломается, по названию метода невозможно будет сразу определить, в чём ошибка — вам придётся анализировать код тест-класса. Попробуйте разбить тест-метод на несколько методов и дать им говорящие названия. - Какие ещё признаки знаете вы?..
Open source
Всегда полезно поизучать юнит-тесты известных open-source проектов. Эта тема достойна отдельной статьи, но вот первое, куда что пришло в голову — это Apache Velocity и Spring Framework. Тест из первого проекта UnicodeInputStreamTestCase мне не понравился, так как названия тест-методов не очень-то описывают поведение программы (например, «testSimpleStream()»). А вот тест для Spring мне понравились, например, Например, для класса CollectionUtils есть юнит-тест CollectionUtilsTests, а для класса Assert есть юнит-тесты AssertTests.
Надеюсь, эта заметка прекратит вечные споры о том, что юнит-тесты бесполезны, трудоёмки и пр., и сможет послужить отправной точкой для дальнейших дискуссий о том, как писать, сколько писать, на чём писать и так далее.
To test or not to test — that is not a question…
pensionary.ru
Читатели рекомендуют также прочесть по этой тематике:
- Веб 2.0 / Что нового будет в Dropbox
- Блог компании Онлайн-Про / Как засоряют ТОП
- Алгоритмы / Самораспаковывающийся HTML
- PHP / Генерация изображения с waveform из mp3 файлов с помощью PHP
- Информационная безопасность / Upgrade




Один комментарий
Присоединяйтесь к беседе и оставляйте комментарий.
Трекбеки/Пинги