Изучите основы и ключевые моменты тестирования

Мне нужно закончить мой проект как можно скорее. Мы можем добавить тесты позже.

— Me, 2018

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

Что именно вы подразумеваете под добавлением тестов?

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

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

Это то, что мы назвали автоматическим тестированием.

Почему важно добавлять тесты?

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

Что тестировать?

Задайте следующие вопросы

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

Это может помочь нам решить, какие части нашего приложения являются наиболее важными и важными.

Что на самом деле имеет смысл тестировать?

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

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

  • Модульные тесты
    Тестирование кода по модулям. Эта единица обычно имеет форму одной функции. Например, когда определенная функция вызывается с заданными входными данными, что будет на выходе?
  • Интеграция/функциональные тесты
    Проверка интеграции модулей в ваш код. Например, определенные функции вашего приложения или службы состоят из двух или трех функций, которые также вызывают другие функции. Интеграционный тест предназначен для того, чтобы убедиться, что эта композиция, формирующая функциональность, работает правильно.
  • Приемочные/сквозные тесты
    Если предыдущие тесты выполняются непосредственно для кода, этот тест выполняется для вашего фактического продукта. Если это веб-приложение, то оно будет щелкать и вводить текст в ваше приложение, в зависимости от того, какую функцию вы хотите протестировать и каковы критерии приемлемости. Это необходимо для того, чтобы функция работала должным образом от интерфейсной части до серверной части.

Жаргон тестирования

  • Mocks
    Когда мы хотим выполнить модульное тестирование определенной функции, которая имеет зависимость, нам нужно заменить эту зависимость чем-то, что мы можем контролировать в нашем тесте. Отчасти это потому, что нас не волнует, как эта зависимость работает под капотом. Мы просто заботимся о том, чтобы он вызывался правильно для функции, которую мы хотим протестировать. Процесс замены часто называют насмешкой. Конечным результатом является макет. Его можно считать имитацией или фиктивным клоном эталонной зависимости.
  • Заглушки
    Заглушки очень похожи на макеты. В то время как макет заменяет то, что было раньше, чтобы убедиться, что зависимость вызывается правильно, заглушка — это скорее замена определенной части. Например, мы можем заменить запрос к базе данных статическим значением.
  • Шпионы
    Шпионы — это то, что мы делаем, если хотим проверить, вызывается конкретный метод в объекте или нет.
  • Утверждение
    Стандартный инструмент для проверки компьютером определенных условий во время выполнения.

Руки вверх

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

О сквозном тестировании мы поговорим позже в другой статье.

Установить Jest

Для начала нам понадобится package.json файл. Перейдем к нашему проекту и выполним в терминале следующие команды

yarn init -y

Опция -y устанавливает все ответы для нашей package.json инициализации на "да".

Теперь, когда у нас есть файл package.json в нашей папке. Мы готовы установить шутку

yarn add --dev jest

Мы хотим использовать современные возможности JavaScript в нашем проекте, поэтому нам нужно добавить babel

yarn add --dev babel-core babel-jest babel-preset-env

Затем создайте файл пресетов babel

touch .babelrc

И, наконец, добавьте текущий пресет в .babelrc

{
  "presets": [
    "env"
  ]
}

Если мы хотим запустить тест через скрипт npm, мы можем добавить команду в package.json

{
 "name": "demo",
 "version": "1.0.0",
 "main": "index.js",
 "license": "MIT",
 "scripts": {
  "test": "jest"
 },
 "devDependencies": {
  "jest": "^23.6.0"
 }
}

Ну вот. Теперь у нас есть Jest, установленный в нашем проекте.

Структурирование тестов проекта

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

  • Вы можете разместить тесты в корневом каталоге, в папке __tests__ . Это не очень распространено, хотя для очень маленького проекта.
  • Он также может находиться внутри подкаталога (например, component/helpers) в папке __tests__. Преимущество в том, что вам не нужно будет долго ../../ импортировать файл, который вы хотите протестировать.

Запуск первого теста

Для запуска теста нам нужен код. Давайте добавим файл с именем index.js и добавим следующий код

class User {
 constructor(details) {
  const { firstname, lastname } = details
  this.firstname = firstname
  this.lastname = lastname
 }
get name() {
  return `${this.firstname} ${this.lastname}`
 }
}
  • Мы хотим проверить, вызывается ли свойство name, оно вернет правильное полное имя.
  • it и test одинаковы. Вы можете использовать любой из них.
  • describe используется для группировки тестов. это помогает нам организовать наши тесты и приводит к хорошему результату позже в терминале.

Тест обычно выглядит так.

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

Например:

describe('User', () => {
 test('when name getter is called, it should return full name', () => {
  // given certain inputs
  const mockName = {
   first: 'John',
   last: 'Doe',
  }
// when the function you want to test is executed with those inputs
  const user = new User({
   firstname: 'John',
   lastname: 'Doe',
  })
// expect it to do something or output something
  expect(user.name).toBe('John Doe');
 })
})
  • Если выходные данные соответствуют вашим ожиданиям, обычно называемым утверждением, проверка будет считаться пройденной.
  • Запустите тест, выполнив yarn jest в терминале, и вы увидите результат теста.

Шуточные ожидания

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

// simple comparison of simple value like string and number
expect('foo').toBe('foo')
// great to check complex comparison like array and object
expect(['foo', 'bar']).toEqual(['foo', 'bar'])
// you can also check the property value inside an object using toEqual
const result = {
 value: Date.now()
}
expect(result).toEqual({
 value: expect.any(Number)
})

Снапшот-тестирование с шуткой

Снимок представляет собой представление данных в строковом формате.

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

Мы можем использовать моментальный снимок для сопоставления больших объектов или даже некоторых HTML-структур.

test('return full name', () => {
 const user = {
  firstname: 'John',
  lastname: 'Doe',
  age: 35,
  job: 'Procastinator',
 }
expect(user).toMatchSnapshot();
})

Что происходит, так это то, что он сначала берет объект и сохраняет его, а затем выполняет сравнение. Если позже значение будет изменено, Jest выдаст нам предупреждение.

Если действительно нужно изменить значение, мы можем запустить тест, добавив —updateSnapshot флаг или -u для краткости при запуске теста.

Выполнение кода до и после тестов

Если вы обнаружили, что используете одни и те же данные снова и снова в нескольких тестовых примерах, вы можете объявить переменную для этих данных в родительском контексте, обычно под describe, а затем присвоить значения данных с помощью beforeEach.

import movies from './movies'
describe('Favorite Movies', () => {
 test('can add a movie', () => {
  // declared myMovies here
  const myMovies = [{
   title: 'Age of Ultron',
   rate: null
  }]
movies.add(myMovies, 'Avatar')
  expect(myMovies).toMatchSnapshot()
 })
test('can rate a movie', () => {
  // declared myMovies again here. redundant
  const myMovies = [{
   title: 'Age of Ultron',
   rate: null
  }]
movies.rate(myMovies[0], 5)
  expect(myMovies).toMatchSnapshot()
 })
})
import movies from './movies'
describe('Favorite Movies', () => {
 let myMovies = []
beforeEach(() => {
  myMovies = [{
   title: 'Age of Ultron',
   rate: null
  }]
 })
test('can add a movie', () => {
  movies.add(myMovies, 'Avatar')
  expect(myMovies).toMatchSnapshot()
 })
test('can rate a movie', () => {
  movies.rate(myMovies[0], 5)
  expect(myMovies).toMatchSnapshot()
 })
})

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

Выполнять только определенные тесты

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

import movies from './movies'
describe('Favorite Movies', () => {
 let myMovies = []
beforeEach(() => {
  myMovies = [{
   title: 'Age of Ultron',
   rate: null
  }]
 })
test('can add a movie', () => {
  movies.add(myMovies, 'Avatar')
  expect(myMovies).toMatchSnapshot()
 })
test.only('can rate a movie', () => {
  movies.rate(myMovies[0], 5)
  expect(myMovies).toMatchSnapshot()
 })
})

Вы также можете skip дело, которое не хотите запускать.

import movies from './movies'
describe('Favorite Movies', () => {
 let myMovies = []
beforeEach(() => {
  myMovies = [{
   title: 'Age of Ultron',
   rate: null
  }]
 })
test('can add a movie', () => {
  movies.add(myMovies, 'Avatar')
  expect(myMovies).toMatchSnapshot()
 })
test.skip('can rate a movie', () => {
  movies.rate(myMovies[0], 5)
  expect(myMovies).toMatchSnapshot()
 })
})

Запуск тестов в режиме просмотра

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

yarn jest --watchAll

Чтобы обновить снимок в режиме просмотра, просто нажмите клавишу u на клавиатуре, находясь в терминале.

Поначалу тестирование вашего приложения/сервиса может показаться сложной задачей, но, приложив немного усилий, вы сможете стать мастером тестирования JavaScript. Самое главное – начать и продолжать учиться. Чем больше вы будете практиковаться, тем лучше у вас получится писать эффективные и действенные тесты.