Раздел 29 · Тестирование

Анатомия юнит-теста: скорость, изоляция, детерминизм

middle~45 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Анатомия юнит-теста: скорость, изоляция, детерминизм

Сцена · три такта одного теста

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

А ещё юнит-тест обязан быть быстрым, ни от чего не зависеть и давать один и тот же результат всегда. Эти три свойства, скорость, изоляция, детерминизм, и делают тест юнитом. Разберём каждое, и увидим, что самое коварное из них, детерминизм, в Effect почти бесплатно.

Карта урока · что заберёшь

  • разложишь юнит-тест на три свойства: скорость, изоляцию, детерминизм;
  • научишься писать в форме Arrange-Act-Assert и поймёшь, зачем это;
  • увидишь, как Date.now() и Math.random() ломают детерминизм;
  • освоишь TestClock и Random.withSeed: время и случайность как управляемые сервисы.

Раздел 1 · что делает тест юнит-тестом

Идея словами

Три свойства, по которым юнит отличается от интеграционного:

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

Всё, что мешает любому из трёх, это либо запах в тесте, либо запах в коде. Зашитый поход в сеть убивает изоляцию и скорость. Зашитое Date.now() убивает детерминизм. Лечение почти всегда одно: вынести зависимость наружу.

Картинка · чистое ядро тестируется даром

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

// чистое ядро: все три свойства бесплатно
const next = applyPayment(makeAccount({ balance: 100 }), makePayment(30));
expect(next.balance).toBe(70);

Проблемы начинаются на границе: время, случайность, сеть. Туда и смотрим дальше.

Что взять с собой

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

Раздел 2 · Arrange-Act-Assert

Идея словами

Arrange-Act-Assert это не бюрократия, а способ читаемости. Глаз сразу находит: что было, что произошло, что проверяем. И есть железное правило: один Act на тест. Если в тесте два действия, у него два повода упасть, и при падении непонятно, какое из них сломалось.

Шаг 1 · спагетти-тест

it('платёж', () => {
  const acc = makeAccount({ balance: 100 });
  expect(applyPayment(acc, makePayment(30)).balance).toBe(70);
  const acc2 = makeAccount({ balance: 50 });
  expect(() => applyPayment(acc2, makePayment(80))).toThrow();
});

Два разных сценария в одном тесте, проверки перемешаны с подготовкой, имя ничего не говорит. Упадёт, и придётся читать тело, чтобы понять, что именно сломалось.

Шаг 2 · три явные секции

it('списывает сумму и сохраняет инвариант баланса', () => {
  // arrange
  const account = makeAccount({ balance: 100 });

  // act
  const next = applyPayment(account, makePayment(30));

  // assert
  expect(next.balance).toBe(70);
  expect(next.history).toHaveLength(1);
});

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

Что взять с собой

  • три секции: подготовь, сделай, проверь;
  • один Act на тест, иначе два повода падать;
  • имя теста это строка спецификации.

дальше после входа

Урок целиком открыт всем, кто вошёл. Войди по своему email, чтобы читать дальше.

Вход пока по приглашению. Если хочешь учиться, напиши Vassiliy.