October 3, 2024

Unit-тестирование

Unit-тестирование помогает стабилизировать процесс разработки при помощи тестов на базовые составляющие кода: функции, методы, классы и целые блоки кода, например:

  • Функция вычисления общей стоимости заказа
  • Метод сортировки новостей по дате
  • Колбек при переходе на страницу

Покрытие unit-тестами позволяет:

  • Повысить стабильность рефакторинга
  • Уменьшить количество ошибок на ранних стадиях разработки
  • Увеличить доверие + получить гарантии работоспособности кода
  • Задокументировать код

Без покрытия unit-тестами:

  • Дефекты будут найдены позже, их исправление станет дороже
  • Новый код будет лежать «рядом» с исходным, вместо изменений
  • Проект быстро устареет и его потребуется переписать с нуля

Unit-тестирование трудоемкий процесс отнимающий значимое количество рабочего времени, поэтому перед тем как начать писать тест следует обдумать что конкретно вы собираетесь покрыть тестом.

Что покрывать unit-тестами?

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

Концептуально стоит тестировать любой код, отказ которого может привести к потере денег, например:

  • Бизнес-сценарий не может быть завершен
  • Интерфейс работает некорректно

На прикладном уровне unit-тестами следует тестировать состояние и поведение кода.

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

Тестируя состояние мы подаем на вход данные и проверяем:

1. Корректность вывода утилитарных функций

function sum(a: number, b: number) :number {
    return a + b
}

test("sum of 2 numbers return correct value", () => {
    const result = sum(1, 2)
    expect(result).toEqual(3)
})

2. Корректность отображения props глупых компонентов

import { render } from "@testing-library/react";

test("breadcrumbs renders correct count of items", () => {
    const items = ["Главная", "Категория", "Статья"]
    const component = render(<Breadcrumbs items={items} visible={2} />)
    
    const items = component.querySelector('.items')
    
    // позитвные сценарии
    expect(items).toHaveLength(2)
    expect(items).toHaveTextContent("Главная")
    expect(items).toHaveTextContent("Категория")
    
    // негативные сценарии
    expect(items).not.toHaveTextContent("Статья")
})

3. Корректность изменения state глупых компонентов:

import { renderHook, waitFor } from "@testing-library/react";

function useCounter() {
  const [count, setCount] = useState(0)
  const increment = () => setCount((x) => x + 1)

  return { count, increment }
}

test("useCounter increment method works correct", async () => {
  const result = renderHook(useCounter)
   
  waitFor(() => {
    result.current.increment()
  })
   
  expect(result.current.count).toBe(1)
})

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

Тестируя поведение мы совершаем действия и смотрим:

1. Как компонент взаимодействует c другими компонентами

  • Взаимодействие данных между страницами

2. Как компонент взаимодействует с пользователем

  • Прохождение happy-path
  • Заполнение форм

3. Как компонент реагирует на ответы с сервера

  • Серверная валидация формы
  • Корректное отображение данных с сервера

Что такое хороший тест?

  • Проверяет только 1 утверждение
  • Изолирован от других тестов и окружения
  • Выполняться быстро и параллельно
  • Соблюдает структуру, конвенцию именования
  • Имеет небольшой размер

Пишем тесты с умом

Бездумное написание тестов не помогает, а даже вредит проекту.

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

Не стоит относиться к тестам как к второсортному коду.

Многие начинающие разработчики ошибочно полагают, что DRY, KISS и другие подходы, да даже переиспользование переменных для test-id — это только для кода в продакшен, а в тестах допустимо всё.

Это не верно.

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

Какой процент покрытия?

Добиваться 100% покрытия бессмысленно, так как такой процент покрытия не гарантирует вам безотказную работу вашего приложения.

Почему бесмысленно добиваться 100% покрытия

Хорошие практики

  • На баг всегда должен быть написан unit-тест
  • Писать unit-тесты на негативные сценарии
  • Описывать unit-тест по структуре AAA
    • Arrange (условие)
    • Act (действие)
    • Assert (утверждение)
  • Cтремиться к 80% покрытию
  • Завязываться на test-id для обращения к DOM-элементами

Плохие практики

  • Unit-тест тестирует смежные модули
  • Unit-тест тестирует данные, а не бизнес-логику
  • Unit-тест не проверяет исключительные ситуации
  • Unit-тест выводит не понятный assertion message
  • Unit-тест тестирует компонент UI-библиотеки или готовый пакет
  • Стремиться к 100% покрытию
  • Завязываться на селекторы и классы для обращения к DOM-элементами