[Перевод] Когда юнит-тестрирование действительно необходимо
Если вы ответственный разработчик, то напишите тесты сами. Так поступают все ответственные разработчики. Но что вы будете тестировать?
Возможно, вы захотите убедиться в том, что:
- Функции будут вести себя должным образом, несмотря на неправильные аргументы — например, null, или другие неожиданные типы аргументов.
- Необязательные параметры функций имеют правильные значения по умолчанию — в тех случаях, когда эти значения вызывающая сторона не указывает.
- Функция расположена в правильном модуле, и её можно вызвать.
Конечно, вы можете много чего проверить. Например, делает ли код то, что он, по идее, должен. По моему опыту, вне зависимости от языка, такие тесты необходимы. Но это не всё, о чем я сегодня хочу поговорить.
Дополнительно можно убедиться в том, что код:
- Не имеет синтаксических ошибок.
- Работает во всех средах, которые вам интересны (к примеру, в разных браузерах).
- Правильно использует библиотеки (например, запутанные ORM APIs).
Вы не прогадаете, если будете использовать линтер. Правда, его помощи недостаточно.
Разработчики часто пытаются протестировать весь код. Они берут инструменты, которые помогают проследить, какие строки кода выполняются во время тестов. Полноценная проверка означает, что каждая строчка кода будет выполнена как минимум одним из тестов.
С языками программирования, которые я перечислил выше, всегда приходится тестировать всё по-максимуму. Если у вас мало тестов, то готовьтесь к диалогу с персональным чертенком: «Ты помнишь, что у тебя нет компилятора? Твой компилятор — это твои тесты. Либо все протестируешь, либо выпустишь некомпилирующийся код. Не надо быть плохим разработчиком!»
Я склонен прислушиваться к этому голосу — потому что не хочу быть плохим разработчиком.
Периодически я глубоко дышу — с надеждой прозреть. И когда я это делаю, мне становится ясно, что я замешан, возможно, в крупнейшей растрате сил в истории человека.
Я привык писать код на C++ и Java. Как раз у них есть что-то под названием «компилятор». То есть инструмент, который, кроме всего прочего, проверяет код на наличие синтаксических ошибок. До того, как программа запустится в первый раз. Это всё ещё новинка в мире веб разработки.
К компилятору можно относиться как к простой системе юнит-тестирования. Она гарантирует, что:
- 100% вашего кода будет без синтаксических ошибок;
- 100% вызовов функций будут вызывать функции. Причем те, которые существуют.
Если изменяется публичный API библиотеки низкого уровня, ваш компилятор не забудет сообщить вам об этом до того, как вы запустите программу. Если вы переименуете функцию, компилятор подскажет, что нужно обновить имя также там, где она вызывается.
Для каждой функции, которую я писал на JavaSript/Python/Ruby/PHP, мне приходилось писать юнит-тесты, чтобы всё это проверить. Даже не так. Хуже. Я писал отдельный тест для каждого места вызова функции, который проверял, вызывается ли она в этом месте. Чтобы вы поняли — если у меня есть функция, которая вызывается в 10 разных местах моего кода, я должен протестировать не только функцию, но ещё и все эти десять мест.
Примечание переводчика: на самом деле это не так и юнит тест тестирует «юнит». Но в разрезе статьи, если рассматривать юнит-тесты как способ добавить статических проверок для динамических языков, можно сделать такое допущение.
Это O (N), ребята.
Если какой-нибудь разработчик будет рефакторить мой код, и мои тесты перестанут тестировать те же пути выполнения кода, тогда я потеряю преимущества этих тестов и никогда об этом не узнаю. Прости, брат.
Братья-программисты говорят «Прости» и пожимают плечами.
Компилятор полностью заменяет юнит-тесты? Не совсем. Смысл в другом. Если мы выбираем язык программирования без компилятора и компенсируем это написанием множества тестов, мы повторяем то, что уже сделали несколько очень крутых разработчиков компилятора. Просто подумайте. Если я пишу код на Go, мне не нужно писать тесты. Разработчики компилятора на Go уже сделали это — для каждого проекта на Go, который будет существовать.
Расскажу забавную историю. Несколько лет назад мой друг переключился с JavaScript на Go. Он написал немного кода, а потом попытался написать тест, который проверил бы, что код корректно себя ведет с неправильным типом. Он долго боролся с компилятором. Пока не осознал, что такая ситуация, в принципе, невозможна.
Не утверждаю, что Go — единственный правильный язык. Так работают все языки, у которых есть компилятор. Даже языки с ужасной системой типов, как C++, тоже так работают. Вам не нужно быть опытном Haskell-разработчиком, чтобы получить все бонусы от этой концепции.
…
Всегда, когда пишете тест, подумайте: если компилятор может сделать проверку за вас, нужно ли вам это делать? Отложите работу и задумайтесь о жизненных приоритетах. О тех умных авторах компиляторов. Они пытаются вам помочь. Возможно, стоит им это позволить.
Эпилог.
Дальше можете не читать.
Я предупредил.
На счастье JavaScript-разработчиков, сообщество увидело надвигающуюся угрозу — и предотвратило её с помощью инструментов Flow и TypeScript. Но поскольку эти инструменты используют постепенное типизирование, вы никогда не сможете быть полностью уверены в том, что каждая строчка кода проекта «берет всё» от системы типов. Да, они помогают. Но поскольку они интероперабельны (или являются надстройкой над) JavaScript, вы не нуждаетесь в 100% «страховочной сети», которую компилируемый язык дает вам.
Более продвинутые системы типов могут пойти дальше. Например, Ada может обеспечить проверку значения числовых переменных на этапе компиляции. Даже может предотвратить индексирования массива числом, которое заведомо больше, чем размерность массива. На этапе компиляции.
Haskell тоже имеет несколько мощных концепций типизации. Тип Maybe говорит компилятору, что значение может быть пустым (аналог null). И любой код, который использует это значение, должен быть уведомлен об этом, или возникнет ошибка компиляции.
Средства сборки JavaScript webpack and Browserify могут сильно помочь с этой проблемой. Если у вас нет проверки типов на этапе компиляции. Например, если у вас есть синтаксическая ошибка в вашем JavaScript, webpack выбросит ошибку во время создания бандла. Это очень полезно.
Поздравляю тех, кто читал между строк, и понял, о чём эта статья. Здесь были завуалированы громкие слова против динамически типизированных языков. На самом деле, я спокойно отношусь к этим языкам. Но негативные побочные эффекты при тестировании могут дорого обходиться разработчику. Конечно, есть другие негативные эффекты, которые делают статически типизированные языки затратными: время сборки, излишне многословные интерфейсы, поиск путей, где/что лежит и так далее. Некоторые статически типизированные языки (Go) решают эти неудобства лучше, чем другие (С++).