Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Утверждения (Asserts) JS: Автоматическое тестирование

Каждую проверку, которую мы написали для функции capitalize(), в тестировании принято называть утверждением (assert). Утверждения — ключевая часть тестов. Именно они проверяют функциональность кода:

import capitalize from '../src/capitalize.js';

// Первое утверждение (проверка на пустую строку)
if (capitalize('') !== '') {
  throw new Error('Функция работает неверно!');
}

// Второе утверждение (проверка на слово)
if (capitalize('hello') !== 'Hello') {
  throw new Error('Функция работает неверно!');
}

Можно заметить, что все проверки строятся одинаковым способом: условие => исключение. Node.js поставляется с модулем assert, в котором есть несколько функций, упрощающих написание утверждений:

// Такой необычный импорт связан с тем,
// что assert, экспортируемый по умолчанию, считается устаревшим
// Правильно использовать strict
import { strict as assert } from 'assert';
import capitalize from '../src/capitalize.js';

// Проверка сменилась с отрицательной на положительную
assert(capitalize('') === '');
assert(capitalize('hello') === 'Hello');

В самом простом случае assert используется как функция, которая проверяет истинность переданного значения. Другими словами, assert(true) означает, что всё хорошо, а assert(false) говорит об ошибке. Последний вариант выбрасывает исключение с таким сообщением:

AssertionError [ERR_ASSERTION]: false == true

Расшифровка сообщения: "Ожидалось, что значением выражения будет истина, но оказалось, что это ложь". Кроме сообщения, выводится бектрейс, по которому можно найти сработавшее утверждение:

// В данном случае assert сработал на 15 строчке файла capitalize.js
at first (file:///src/capitalize.js:15:19)
at default (file:///src/capitalize.js:11:3)
at file:///test.js:5:13

Функция assert() сделала наш код короче и проще для восприятия. Положительная проверка смотрится естественнее, так как это то, что мы ожидаем.

С другой стороны, вывод сообщения об ошибке крайне неинформативный. Единственный способ понять, что произошло — открывать код с упавшим утверждением (ещё есть вариант передать сообщение об ошибке последним параметром, но так не делают, потому что это слишком "ручной" способ, требующий больших усилий). Это пытаются исправить с помощью специализированных утверждений, заточенных под конкретные ситуации. Например, при сравнении двух значений подходит функция assert.strictEqual(actual, expected). Перепишем код выше:

import { strict as assert } from 'assert';
// при использовании strict-режима
// проверка equal равносильна strictEqual

import capitalize from '../src/capitalize.js';

// Проверка сменилась с отрицательной на положительную
assert.equal(capitalize(''), '');
// Первый параметр actual – то, что пришло
// Второй параметр expected – то, что ожидает тест
// Правильный порядок аргументов имеет большое значение при анализе ошибки
assert.equal(capitalize('hello'), 'Hello');

Вывод таких утверждений значительно понятнее:

Thrown:
AssertionError [ERR_ASSERTION]: 'hello' == 'Hello'
  generatedMessage: true,
  code: 'ERR_ASSERTION',
  actual: 'hello',
  expected: 'Hello',
  operator: '=='

https://repl.it/@hexlet/js-testing-asserts-capitalize#tests/capitalize.test.js

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

Однако, будьте осторожны. Функция strictEqual(actual, expected) проверяет равенство по ссылке. То есть два разных объекта, имеющих одинаковое содержание, рассматриваются как не эквивалентные:

AssertionError [ERR_ASSERTION]: Values have same structure but are not reference-equal:

{
  key: 'value'
}

    at repl:1:8
    at Script.runInThisContext (vm.js:131:20)
    at REPLServer.defaultEval (repl.js:436:29)
    at bound (domain.js:429:14)
    at REPLServer.runBound [as eval] (domain.js:442:12)
    at REPLServer.onLine (repl.js:763:10)
    at REPLServer.emit (events.js:327:22)
    at REPLServer.EventEmitter.emit (domain.js:485:12)
    at REPLServer.Interface._onLine (readline.js:337:10)
    at REPLServer.Interface._line (readline.js:666:8) {
  generatedMessage: true,
  code: 'ERR_ASSERTION',
  actual: [Object],
  expected: [Object],
  operator: 'strictEqual'
}

Для сравнения по значению используется ещё одно утверждение: assert.deepEqual(actual, expected). Оно опирается только на содержимое:

assert.deepEqual({}, {}); // всё ок
assert.deepEqual({ key: 'value' }, { key: 'value' }); // всё ок
assert.deepEqual({ key: 'value' }, { key: 'another value' }); // Бум!

На самом деле правила проверки этой функции достаточно сложны. Подробнее об этом можно прочитать в документации

Для тестирования негативных сценариев предназначены функции assert.notStrictEqual(actual, expected) и assert.notDeepStrictEqual(actual, expected). Они тестируют то, что значения не равны. Эти утверждения используются крайне редко, но знать о них всё равно полезно:

assert.notDeepEqual({ a: 1 }, { a: '1' }); // OK!

https://repl.it/@hexlet/js-testing-asserts-methods#index.test.js


Самостоятельная работа

  1. Посмотрите в конце урока ссылки на документацию на библиотеки утверждений
  2. Замените в вашем репозитории ручные утверждения на использование модуля assert
  3. Запустите тесты, убедитесь что они работают. Попробуйте их сломать
  4. Добавьте код на гитхаб

Дополнительные материалы

  1. Asserts
  2. Chai

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов для веб-приложений
29 сентября 10 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов для веб-приложений
29 сентября 10 месяцев
Иконка программы Fullstack-разработчик
Профессия
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
29 сентября 16 месяцев

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и соглашаетесь с «Условиями использования»