DevGang
Авторизоваться

Как начать писать модульные тесты в JavaScript 

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

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

Различные виды тестирования

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

Модульные тесты

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

export function getAboutUsLink(language){
  switch (language.toLowerCase()){
    case englishCode.toLowerCase():
      return '/about-us';
    case spanishCode.toLowerCase():
      return '/acerca-de';
  }
  return '';
}

Интеграционные тесты

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

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

Функциональные тесты

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

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

Итак, теперь давайте углубимся в юнит-тесты.

Почему я должен беспокоиться о написании юнит-тестов?

Всякий раз, когда я спрашиваю разработчиков, пишут ли они тесты для своего приложения, они всегда говорят мне: «У меня не было на них времени» или «Мне они не нужны, я знаю, что это работает».

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

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

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

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

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

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

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

Модульные тесты помогут вам обнаружить все эти случаи. Посмотрите снова на вопросы, и вы обнаружите, что именно это определяет ваши тесты.

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

Как написать свой первый модульный тест на JavaScript

Но вернемся к JavaScript. Мы начнем с Jest, который представляет собой среду тестирования JavaScript. Это инструмент, который обеспечивает автоматическое модульное тестирование, обеспечивает покрытие кода и позволяет легко макетировать объекты.

npm i jest --save-dev

Давайте использовать ранее упомянутый метод getAboutUsLink в качестве реализации, которую мы хотим протестировать:

index.js
const englishCode = "en-US";
const spanishCode = "es-ES";
function getAboutUsLink(language){
    switch (language.toLowerCase()){
      case englishCode.toLowerCase():
        return '/about-us';
      case spanishCode.toLowerCase():
        return '/acerca-de';
    }
    return '';
}
module.exports = getAboutUsLink;

Я положил этот код в файл index.js. Мы можем написать тесты в одном и том же файле, но хорошей практикой является разделение модульных тестов в отдельный файл.

Общие шаблоны именования включают {filename}.test.js и {filename}.spec.js. Я использовал первый index.test.js:

index.test.js
const getAboutUsLink = require("./index");
test("Returns about-us for english language", () => {
    expect(getAboutUsLink("en-US")).toBe("/about-us");
});

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

В этом случае мы вызываем функцию getAboutUsLink с параметром en-US. Мы ожидаем, что результат будет /about-us.

Теперь мы можем установить Jest CLI глобально и запустить тест:

npm i jest-cli -g
jest

Если вы видите ошибку, связанную с конфигурацией, убедитесь, что у вас есть файл package.json. Если он отсутствует, сгенерируйте его, используя npm init.

Вы должны увидеть что-то вроде этого:

 PASS  ./index.test.js
  √ Returns about-us for english language (4ms)
  console.log index.js:15
    /about-us
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        2.389s

Прекрасная работа! Это был первый простой модульный тест JavaScript от начала до конца. Давайте попробуем, расширив тест этой строкой:

expect(getAboutUsLink("cs-CZ")).toBe("/o-nas");

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

Тестирование расширенной функциональности и сервисов с Mocking

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

import { englishCode, spanishCode } from './LanguageCodes'

Вы можете импортировать эти константы в тест таким же образом. Но ситуация станет более сложной, если вы будете работать с объектами, а не с простыми константами. Взгляните на этот метод:

import { UserStore } from './UserStore';

function getUserDisplayName() {
  const user = UserStore.getUser(userId);
  return `${user.LastName}, ${user.FirstName}`;
}

Этот метод использует импортированный UserStore:

class User {
    getUser(userId){
        // logic to get data from a database
    }
    setUser(user){
        // logic to store data in a database
    }
}
let UserStore = new User();
export { UserStore }

Чтобы правильно провести юнит-тестирование этого метода, нам нужно смоделировать UserStore. Mock является заменой оригинального объекта. Это позволяет нам отделять зависимости и реальные данные от реализации тестируемого метода так же, как манекены помогают в краш-тестах автомобилей вместо реальных людей.

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

Mock сервиса

Чтобы макетировать объекты, вы можете предоставить имитацию или ручной макет. Я остановлюсь на последнем, поскольку у меня есть простой и понятный вариант использования.

jest.mock('./UserStore', () => ({
    UserStore: ({
        getUser: jest.fn().mockImplementation(arg => ({
            FirstName: 'Ondrej',
            LastName: 'Polesny'
        })),
        setUser: jest.fn()
    })
}));

Во-первых, нам нужно указать, над чем мы издеваемся - модуль ./UserStore. Далее нам нужно вернуть Mock, содержащий все экспортированные объекты из этого модуля.

В этом примере это только объект User, экземпляр которого хранит функцию getUser. Но с реальными реализациями макет может быть намного длиннее. Любые функции, которые вам не нужны в модульном тестировании, могут быть легко смоделированы jest.fn().

Модульный тест для функции getUserDisplayName похож на тот, который мы создали ранее:

test("Returns display name", () => {
    expect(getUserDisplayName(1)).toBe("Polesny, Ondrej");
})

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

Отчет о покрытии кода

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

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

test("Returns about-us for english language", () => {
   expect(getAboutUsLink("en-US")).toBe("/about-us");
});

Он проверяет английскую ссылку, но испанская версия остается непроверенной. Покрытие кода составляет 50%. Другой модульный тест тщательно проверяет функцию getDisplayName, и его охват кода составляет 100%. Вместе общий охват кода составляет 67%. У нас было 3 варианта использования для тестирования, но наши тесты охватывают только 2 из них.

Чтобы увидеть отчет о покрытии кода, введите в терминал следующую команду:

jest --coverage

Запустив проверку покрытия, Jest создаст HTML-отчет. Найдите его в папке вашего проекта по адресу coverage/lcov-report/index.html.

coverage/lcov-report/index.html
coverage/lcov-report/index.html

Теперь я не должен упоминать, что вы должны стремиться к 100% охвату кода, верно? :-)

Источник:

#JavaScript #NodeJS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

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

Попробовать

Оплатив хостинг 25$ в подарок вы получите 100$ на счет

Получить