Вот мысленное упражнение. Вы разработчик JavaScript, Ruby, Python или PHP. Следовательно, в вашем языке нет почти чего-либо во время компиляции. Фактически, ваш язык даже не требует этапа компиляции. В некоторых случаях вы не узнаете о синтаксических ошибках, пока не запустите код.

Вы ответственный разработчик, поэтому пишете тесты. Потому что это то, что делают ответственные разработчики.

Продолжая это мысленное упражнение: что вы проверяете? Вероятно, такие случаи, среди прочих:

  • Убедитесь, что функции ведут себя должным образом при задании недопустимых аргументов, таких как null или неожиданные типы.
  • Убедитесь, что для необязательных параметров функции по умолчанию заданы правильные значения, если они не указаны вызывающими абонентами.
  • Убедитесь, что функция существует в правильном модуле и может быть вызвана.

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

В качестве бонуса ваши тесты также предоставляют следующие неявные тестовые примеры:

  • Убедитесь, что в коде нет синтаксических ошибок.
  • Убедитесь, что код работает во всех средах выполнения, которые вам нужны (например, в разных браузерах).
  • Убедитесь, что код правильно использует базовые библиотеки (например, сложные ORM API).

Линтер может дать вам некоторые из этих преимуществ, но, по моему опыту, этого никогда не будет достаточно.

Разработчики часто стремятся к 100% тестированию, что означает, что они используют инструменты для отслеживания отслеживания того, какие строки кода выполняются во время выполнения их тестов. 100% означает, что весь ваш код выполняется хотя бы одним из ваших тестов.

С такими языками я обычно стремлюсь к 100% тестированию. Что-нибудь меньшее, и этот маленький дьявол из пословиц появляется у меня на плече и говорит:

У тебя нет компилятора. Ваши тесты являются вашим компилятором. Вы должны получить 100% покрытие, иначе вы отправляете нескомпилированный код, и вы плохи.

Я склонен слушать этого дьявола, потому что не хочу быть плохим разработчиком.

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

Понимаете, раньше я писал много кода на C ++ и Java. В этих языках есть то, что называется «компилятором». Это инструмент, который проверяет ваш код на наличие синтаксических ошибок перед его запуском. Это действительно новинка в современном мире.

Вы можете думать о компиляторе как о простой системе модульного тестирования, которая дает вам некоторые гарантии:

  • 100% вашего кода гарантированно не содержит синтаксических ошибок.
  • 100% вызовов функций гарантированно вызывают функции, которые действительно существуют.
  • Если общедоступный API базовой библиотеки изменяется, ваш компилятор гарантирует, что вы узнаете об этом до запуска кода.
  • Если вы переименуете функцию, ваш компилятор сообщит вам, если какой-либо из узлов вызова этой функции не был обновлен до нового имени.

И все же для каждой функции, которую я пишу на JavaSript / Python / Ruby / PHP, мне приходится писать модульные тесты, чтобы получить эти гарантии. Нет, это еще хуже. Для каждой функции call site я должен написать еще один тест, который выполняет call site.

Например, если у меня есть функция, которая вызывается из 10 мест в моем коде, я должен протестировать не только функцию, но и все 10 из этих сайтов вызова.

Это O (N) ребят.

Да, и если будущий разработчик проведет реорганизацию моего кода, и мои тесты больше не будут использовать те же самые пути кода, я молча потеряю преимущества этих тестов. Извини, брат.

Компилятор полностью заменяет модульные тесты? Нет, я не о том.

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

Забавная история из окопов Go: несколько лет назад мой друг перешел с JavaScript на Go. После написания кода он попытался написать тест, чтобы убедиться, что он будет вести себя правильно при передаче недопустимого типа. Он боролся с компилятором, пока наконец не понял, что это вообще невозможно.

Я не говорю, что го - единственный истинный язык. Эта концепция применима к любому языку с компилятором. Даже языки с, возможно, ужасной системой типов, такие как C ++, по-прежнему дают вам все преимущества, о которых я говорил. Необязательно быть самодовольным разработчиком Haskell, чтобы воспользоваться этой концепцией.

Каждый раз, когда вы пишете тест, подумайте, реализуете ли вы ограничение, которое компилятор предоставит вам бесплатно.

Затем найдите время, чтобы обдумать свой жизненный выбор. Подумайте обо всех этих умных и полезных авторах компиляторов. Они пытаются помочь. Может, стоит им позволить.

Эпилог

Серьезно, вам не нужно больше читать эту статью.

Я тебя предупреждал.

К счастью для разработчиков JavaScript, сообщество заметило появление этой проблемы и решило ее с помощью таких инструментов, как Flow и TypeScript. Но поскольку эти инструменты в основном набираются постепенно, вы никогда не можете быть уверены, что каждая строка кода в вашем проекте действительно получает все преимущества системы типов. Да, они полезны, но поскольку они совместимы с (или надмножеством) JavaScript, у вас нет стопроцентной подстраховки, которую дает традиционный компилируемый язык.

Более продвинутые системы типов могут развить эту концепцию еще дальше. Например, Ада может обеспечивать принудительное выполнение значения целых чисел во время компиляции. Это может даже предотвратить индексацию в массиве с целым числом, которое, как известно, слишком велико для этого массива. Во время компиляции.

В Haskell также есть несколько действительно мощных концепций принудительного применения типов. Тип «Maybe», например, сообщает компилятору, что значение может ничего не содержать (аналогично null), и любой код, использующий это значение, должен знать об этом, иначе это вызовет ошибку компилятора.

Инструменты сборки JavaScript, такие как webpack и Browserify, могут очень помочь с этой проблемой, даже если у вас нет принудительного контроля типов во время компиляции. Например, если у вас есть синтаксическая ошибка в вашем JavaScript, webpack выдаст ошибку и не выведет ваш пакет. Это очень удобно.

Поздравляем тех, кто читает между строк, чтобы увидеть, что это за статья: тонко завуалированная напыщенная речь против динамически типизированных языков. Я действительно не ненавижу эти языки, но тестирование может оказаться довольно дорогостоящим. Конечно, есть и другие компромиссы, которые делают статически типизированные языки дорогостоящими: время сборки, подробные интерфейсы, пути сборки и т. Д. Некоторые статически типизированные языки (Go) решают эти проблемы лучше, чем другие (C ++).