Вдохновение для тестов

Много слов сказано о достоинствах юнит-тестов (TDD, BDD — в данном случае неважно), а также о том, почему люди всё-таки их не используют.

Но я думаю, что одна из главных причин заключается в том, что люди не знают, с чего начать. Вот прочитал я статью про юнит-тесты, понравилось; решил, что надо бы когда-нибудь попробовать. Но что дальше? С чего начать? Как придумывать все эти требования, как называть тест-методы?

В последнее время набирает популярность тенденция превращать юнит-тесты в BDD-спецификации, то есть говорится о том, что хороший юнит-тест должен не тестировать что-то, а описывать поведение программы. Но как описать это чёртово поведение; откуда брать вдохновение, чтобы придумать названия для всех этих тест-кейсов?

Об этом и пойдёт речь:

Откуда брать вдохновение

Однажды я пришёл к удивительному открытию: это вдохновение — повсюду, оно окружает нас каждый день. Тут и придумывать ничего не надо. Его можно просто брать и использовать. Это открытие показалось мне настолько важным, что я спешу непременно поделиться им со всеми.

Итак, мой TOP-5 источников вдохновения для написания юнит-тестов.

5. Доклад начальнику

Каждое утро на собрании начальник спрашивает тебя:

Что ты вчера делал?

Багу исправлял.

Начальник так просто на слово не верит и продолжает докапываться:

-— Какую багу?

-— Ну, исправил метод validateReferenceNumber.

-— Конкретнее, что ты там исправил?

-— Ну, раньше он грохался, если дать ему на входе пустую строку, а теперь он не падает, а возвращает false.

Вот оно!

Видите, практически готовый юнит-тест висит в воздухе у вас перед носом. После собрания садитесь за компьютер и пишете:

public class ReferenceNumberTest {
  @Test
  public void emptyStringIsNotValidReferenceNumber() {
    assertFalse(validateReferenceNumber(""));
  }
}

В идеале вы должны были это сделать ещё вчера, и тогда начальник мог бы посмотреть, какие юнит-тесты вы вчера добавили, и не мучать вас своими вопросами. Я не шучу, я знаю примеры весьма успешных компаний, в которых вместо Code Review делается ревью юнит-тестов.

4. Объяснения с коллегами

К вам каждый день приходит какой-нибудь коллега и спрашивает:

-— Слушай, я тут твой код дебажу-дебажу, а всё никак не могу понять, как это работает?

Ты ему терпеливо объясняешь:

-— Ну как, ну смотри: сюда приходит Б, а отсюда выходит А.

-— Всё равно не понимаю, а почему А-то?

-— Ну потому, что для всех букв, которые меньше Я, этот метод должен возвращать следующую букву.

-— Ааа, вот теперь понятно.

И снова оно!

Вы, сами того не подозревая, только что сформулировали название тест-кейса. Мысленно послав +1 в карму коллеги, сели и написали:

public class NanoTechnologySecurityTest {
  @Test
  public void shouldReturnNextLetterForAllLettersExceptJa() {
    assertEquals("Б", encodeLetter("А"));
  }
}

3. Разговор с клиентом

В любой прекрасный день клиент может позвонить и сказать:

-— Помните, мы обсуждали, что все поля на форме должны валидироваться автоматически, как только поле теряет фокус? Так вот, я передумал. Я показывал бета-версию моей бабушке, и она сказала, что это не понятно, и вообще ajax sucks. Давайте поля будут валидироваться только тогда, когда клиент нажмёт кнопку «Submit».

И опять оно!

Клиент только что сформулировал за нас текст тест-кейса.

Мысленно послав -10 в карму клиента, мы сели и написали… UI тесты, конечно, чуток сложнее, чем обычные юнит-тесты, но это могло бы выглядеть как-то так:

public class TimotiFanClubRegistrationFormTest {
  @Test
  public void shouldValidateFieldsOnFormSubmission() {
    Form form = new Form();
    assertTrue(form.isValid());

    form.submit();
    assertFalse(form.isSubmitted());
    assertFalse(form.isValid());

    form.setName("Baba Njura");
    form.setEmail("Baba.Njura@yandex.ru");
    form.submit();
    assertTrue(form.isSubmitted());
  }
}

2. Баг

Каждый день вы заходите в Jira (а кому совсем повезло — в Pivotal Tracker) и обнаруживаете, что в вашей программе зловредные тестировщики нашли-таки багу. Если вам повезло с тестировщиком, то описание баги звучит примерно так:

«Если ввести три раза неправильный пароль, то учётная запись должна заблокироваться, а у меня не блокируется. Я уже раз пятнадцать ввёл.»

Мысленно сказав тестировщику спасибо (хватит с него и спасиба, его карму всё равно не спасёшь), садимся и пишем:

public class LoginPageTest {
  @Test
  public void shouldBlockAccountAfter3UnsuccessfulTries() {
    LoginPage page = new LoginPage();
    page.login("vasjok47", "Toiota");
    page.login("vasjok47", "Tojota");
    page.login("vasjok47", "tayota");
    assertTrue(AccountDAO.getAccount("vasjok47").isBlocked());
  }
}

Есть даже отдельный термин для этого: Bug driven development. Я сам этот подход не жалую, так как юнит-тесты (они же спецификация) всё-таки должны писаться ДО кода (основной принцип TDD), но как источник вдохновения баг-трекер вполне подходит.

1. Commit message (как это по-русски?)

Это мой любимый пункт, на нём я хотел бы остановиться подробнее.

Когда вы меняете код, для этого обычно есть причина. Причина обычно заключается в том, что вы хотите, чтобы этот код что-то делал по-другому (исключаем рефакторинг, улучшение производительности и расставление скобочек по фэншую).

Допустим, был у нас код, который валидировал email. В частности, он проверял, что в конце email должна быть точка и два символа. И даже есть для него юнит-тест, всё как у людей. И тут внезапно выясняется, что после точки может быть и больше букв, например, у некоторых клиентов email заканчивается на “.info”.

Сказано-сделано, код исправили, и даже в существующий юнит-тест добавили одну строчку:

public class EmailValidatorTest {
  @Test
  public void testValidateEmail() {
    assertTrue(validateEmail("timati@rambler.ru"));
    ...
    assertTrue(validateEmail("tina.turner@music.info")); // 4 letters now allowed!
  }
}

И теперь хотим это дело закоммитить в CVS (а кому повезло, те в SVN, а кто вообще счастливчик, те в GIT).

Для коммита надо написать пояснение (commit message), в котором обычно пишут, а что же в коде изменилось. И вот пишите вы:

svn commit -m "Теперяча мыло и на четыре буквы может заканчиваться."

И вот это самое настоящее оно-оно-оно!

Это то, что нам надо. Не нажимайте пока enter.

Остановились, выделили этот текст мышкой. Открыли класс юнит-теста. Написали Test, вставили скопированный текст и перевели на английский. Можно немножко уточнить или обобщить по вкусу.

public class EmailValidatorTest {
  @Test
  public void validEmailShouldEndWithDotFollowedBySeveralLetters() {
    assertTrue(validateEmail("timati@rambler.ru"));
    ...
    assertTrue(validateEmail("tina.turner@music.info"));
  }
}

Вот теперь можете смело закоммитить и мысленно послать +1 себе в карму. Сегодня мы смогли материализовать некоторые знания из воздуха во что-то более ощутимое. Теперь эти знания никуда не пропадут и будут автоматически проверяться при каждой сборке проекта.

Ну не здоровско ли, а?