Руководство для начинающих по тестированию функционального JavaScript - SitePoint

  1. Зачем тестировать?
  2. Почему функциональный?
  3. Как работают чистые функции
  4. Что делает функциональный код простым для тестирования?
  5. Тестирование функционального JavaScript - наш первый тест
  6. Тестирование вызывает привыкание
  7. Рефакторинг чистой функции

Функциональное программирование и тестирование. Может быть, вы дали им попробовать в одиночку, но почему-то вы никогда не стали частью вашей обычной практики. Они могут показаться невинными сами по себе, но совместное тестирование и функциональное программирование могут создать непреодолимое искушение, почти вынуждая вас писать более чистый, надежный и более понятный код.

Функциональное программирование

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

Наблюдайте, как пишут чистый, чистый код, следуя принципам функционального JavaScript

Разбейте свой код и сделайте его более понятным ... почему бы и нет?

В этой статье я познакомлю вас с принципами тестирования функционального JavaScript. Я покажу вам, как начать работу с фреймворком Jasmine и создать чистую функцию с использованием подхода, основанного на тестировании.

Зачем тестировать?

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

Тогда вы получите награду.

И да, это заставит вас чувствовать себя хорошо.

Тестирование имеет множество разновидностей, и есть место для здоровой дискуссии о том, где нарисованы границы, но в двух словах:

  • Модульные тесты подтверждают функциональность изолированного кода
  • Интеграционные тесты проверяют поток данных и взаимодействие компонентов
  • Функциональные тесты смотрят на поведение всего приложения

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

Вы можете написать тест в любой точке процесса кодирования, но я всегда обнаруживал, что наиболее эффективно написать модульный тест перед написанием функции, которую вы планируете тестировать. Эта практика, известная как разработка через тестирование (TDD) предлагает вам разбить функциональность вашего приложения до того, как вы начнете писать, и определить, какие результаты вы хотите получить для каждого раздела кода, сначала написать тест, а затем написать код для получения этого результата.

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

Почему функциональный?

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

Функциональное программирование позволяет вам независимо работать с данными и поведением в вашем приложении. Вы создаете свое приложение, создавая набор независимых функций, каждая из которых работает изолированно и не зависит от внешнего состояния. В результате ваш код становится почти самодокументируемым, связывая вместе небольшие четко определенные функции, которые ведут себя согласованным и понятным образом.

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

Как работают чистые функции

Функциональное программирование побуждает вас создавать приложение из крошечных, многократно используемых, компонуемых функций, которые выполняют только одну конкретную вещь и возвращают одно и то же значение для одного и того же ввода каждый раз. Такая функция называется чистая функция , Чистые функции являются основой функционального программирования, и все они имеют следующие три качества:

  • Не полагайтесь на внешнее состояние или переменные
  • Не вызывать побочные эффекты и не изменять внешние переменные
  • Всегда возвращать один и тот же результат для одного и того же ввода

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

Что делает функциональный код простым для тестирования?

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

Существует множество вариантов тестирования, начиная от полноценных сред и заканчивая библиотеками утилит и простыми средствами тестирования. Они включают жасмин , кофе мокко , энзим , Шутка и множество других. Каждый из них имеет свои преимущества и недостатки, лучшие варианты использования и лояльные последователи , Jasmine - это надежный фреймворк, который можно использовать в самых разных обстоятельствах, поэтому здесь кратко демонстрируется, как можно использовать Jasmine и TDD для разработки чистой функции в браузере.

Вы можете создать HTML-документ, который извлекает из библиотеки тестирования Jasmine либо локально, либо из CDN. Пример страницы, включающей библиотеку Jasmine и тестовый прогон, может выглядеть примерно так:

<! DOCTYPE html> <html lang = "en"> <head> <meta charset = "utf-8"> <title> Жасминовый тест </ title> <link rel = "stylesheet" href = "https: // cdnjs .cloudflare.com / ajax / libs / jasmine / 2.6.1 / jasmine.min.css "> </ head> <body> <script src =" https://cdnjs.cloudflare.com/ajax/libs/jasmine/ 2.6.1 / jasmine.min.js "> </ script> <script src =" https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/jasmine-html.min.js "> < / script> <script src = "https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.6.1/boot.min.js"> </ script> </ body> </ html>

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

Тестирование функционального JavaScript - наш первый тест

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

Новая функция, которую мы собираемся написать, будет называться isPalindrome, и она вернет true, если переданная строка будет одинаковой вперед и назад, и вернет false в противном случае. Тест будет выглядеть так:

description ("isPalindrome", () => {it ("возвращает true, если строка является палиндромом", () => {ожидаемо (isPalindrome ("abba")). toEqual (true);});});

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

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

const isPalindrome = (str) => true;

Да, действительно. Я знаю, это выглядит нелепо, но держись там со мной.

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

description ("isPalindrome", () => {it ("возвращает true, если строка является палиндромом", () => {Ожидаем (isPalindrome ("abba")). toEqual (true);}); it (" возвращает false, если строка не является палиндромом ", () => {Ожидаем (isPalindrome (" Bubba ")). toEqual (false);});});

Перезагрузка нашей страницы теперь делает тестовый вывод красным и сбой. Мы получаем сообщения о том, в чем проблема, и результат теста становится красным.

Красный!

Наш мозг чувствует, что есть проблема.

Есть конечно. Теперь было продемонстрировано, что наша простая функция isPalindrome, которая просто возвращает true, не работает эффективно с этим новым тестом. Итак, давайте обновим isPalindrome, добавив возможность сравнивать строку, переданную вперед и назад.

const isPalindrome = (str) => {return str .split ("") .reverse () .join ("") === str; };

Тестирование вызывает привыкание

Зеленый снова. Теперь это удовлетворяет. Вы получили этот маленький прилив допамина, когда вы перезагрузили страницу?

С этими изменениями наш тест снова проходит. Наш новый код эффективно сравнивает прямую и обратную строки и возвращает true, если строка одинакова вперед и назад, и false в противном случае.

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

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

И вы хотите удовлетворение от прохождения теста. Вы знаете, что делаете.

Рефакторинг чистой функции

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

Например, давайте посмотрим, что произойдет, если мы добавим тест для числа типа 1001, который может быть интерпретирован как палиндром, если бы он был строкой:

description ("isPalindrome", () => {it ("возвращает true, если строка является палиндромом", () => {Ожидаем (isPalindrome ("abba")). toEqual (true);}); it (" возвращает false, если строка не является палиндромом ", () => {Ожидаем (isPalindrome (" Bubba ")). toEqual (false);}); it (" возвращает истину, если число является палиндромом ", () => {Ожидайте (isPalindrome (1001)). toEqual (true);});});

Это дает нам красный экран и снова провальный тест, потому что наша текущая функция isPalindrome не знает, как обращаться с нестроковыми входами.

Наступает паника. Мы видим красный. Тест не пройден.

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

const isPalindrome = (str) => {return str .toString () .split ("") .reverse () .join ("") === str.toString (); };

И теперь все наши тесты пройдены, мы видим зеленый, и этот сладкий, сладкий дофамин заливает наш мозг, управляемый тестами.

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

Увидеть перо Руководство для начинающих по тестированию функционального JavaScript по SitePoint ( @SitePoint ) на CodePen ,

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

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

Эта статья была рецензирована Вилдан Софтик , Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

Зачем тестировать?
Почему функциональный?
Почему бы и нет?
Зачем тестировать?
Почему функциональный?
Что делает функциональный код простым для тестирования?
Вы получили этот маленький прилив допамина, когда вы перезагрузили страницу?