TDD в TypeScript: пиши код без страха и упрека

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Хватит гадать: почему TDD — это не мазохизм, а суперсила

Представь: ты пишешь код. Всё работает. Ты доволен. Через месяц возвращаешься к проекту, вносишь одно маленькое изменение — и всё ломается. Знакомо? А теперь представь, что у тебя есть страховочная сетка, которая ловит каждую ошибку за секунду. Это и есть TDD в TypeScript — разработка через тесты.

Многие разработчики думают, что TDD — это для перфекционистов или для проектов NASA. Спойлер: это не так. Test Driven Development — это инструмент, который экономит часы дебага, повышает качество кода и делает рефакторинг безопасным. В этой статье мы разберём TDD TypeScript на реальных примерах, без воды и абстракций.

Если ты пишешь на TypeScript и хочешь перестать бояться своих же правок — читай дальше. Будет интересно.

Что такое TDD (и почему это не про "написать тесты")

TDD (Test-Driven Development) — это методология, где тесты пишутся до кода. Звучит безумно? Только на первый взгляд. Цикл TDD состоит из трёх шагов:

  1. Красный (Red) — напиши тест, который гарантированно упадёт, потому что кода ещё нет.
  2. Зелёный (Green) — напиши минимальный код, чтобы тест прошёл.
  3. Рефакторинг (Refactor) — улучши код, не меняя его поведения.

И так — по кругу. Каждый новый кусочек функциональности начинается с теста. Это дисциплинирует, заставляет думать о дизайне API заранее и даёт уверенность, что всё работает как часы.

Кстати, юнит тесты TypeScript — это не просто "проверка функций". Это документация, спецификация и страховка в одном флаконе.

Инструменты для TDD в TypeScript: что нужно установить

Для тестирования TypeScript нам понадобится:

  • Jest — самый популярный тестовый фреймворк. Работает из коробки с TypeScript.
  • ts-jest — пресет для Jest, чтобы он понимал TypeScript без дополнительной настройки.
  • @types/jest — типы для автодополнения в IDE.

Установка в одну команду:

npm install --save-dev jest ts-jest @types/jest typescript

Добавь в package.json скрипт для запуска тестов:

"scripts": {
  "test": "jest",
  "test:watch": "jest --watch"
}

Теперь всё готово для разработки через тесты TypeScript.

Пишем первый тест по TDD: от красного к зелёному

Представим, что нам нужно написать функцию, которая проверяет, является ли строка палиндромом. По классике TDD — начинаем с теста.

Шаг 1. Красный (Red)

Создаём файл isPalindrome.test.ts:

import { isPalindrome } from './isPalindrome';

describe('isPalindrome', () => {
  it('должен возвращать true для строки "racecar"', () => {
    expect(isPalindrome('racecar')).toBe(true);
  });

  it('должен возвращать false для строки "hello"', () => {
    expect(isPalindrome('hello')).toBe(false);
  });
});

Запускаем тест:

npm test

Видим ошибку: Cannot find module './isPalindrome'. Отлично, красная фаза пройдена. Теперь нам нужно написать код, чтобы тест прошёл.

Шаг 2. Зелёный (Green)

Создаём isPalindrome.ts с минимальной реализацией:

export function isPalindrome(str: string): boolean {
  return str === str.split('').reverse().join('');
}

Запускаем тесты снова — зелёный! Оба теста проходят. Но это ещё не всё.

Шаг 3. Рефакторинг (Refactor)

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

it('должен игнорировать регистр и пробелы', () => {
  expect(isPalindrome('A man a plan a canal Panama')).toBe(true);
});

Тест падает. Мы снова в красной фазе. Теперь рефакторим код:

export function isPalindrome(str: string): boolean {
  const cleaned = str.toLowerCase().replace(/\s/g, '');
  return cleaned === cleaned.split('').reverse().join('');
}

Тесты проходят. Мы улучшили код, не сломав старую функциональность. Вот она, магия TDD в TypeScript.

Продвинутые техники: mock, stub и Dependency Injection

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

Допустим, у нас есть сервис, который получает данные из внешнего API:

export class UserService {
  constructor(private httpClient: HttpClient) {}

  async getUser(id: number): Promise<User> {
    const response = await this.httpClient.get(`/users/${id}`);
    return response.data;
  }
}

Тест с моком:

import { UserService } from './UserService';

describe('UserService', () => {
  it('должен возвращать пользователя по ID', async () => {
    const mockHttpClient = {
      get: jest.fn().mockResolvedValue({ data: { id: 1, name: 'Alice' } })
    };

    const service = new UserService(mockHttpClient);
    const user = await service.getUser(1);

    expect(user.name).toBe('Alice');
    expect(mockHttpClient.get).toHaveBeenCalledWith('/users/1');
  });
});

Обрати внимание: мы не ходим в реальное API. Мы тестируем только логику UserService. Это и есть unit тесты TypeScript в чистом виде.

Почему TDD делает код лучше (спойлер: не только тесты)

Когда ты практикуешь Test Driven Development, ты невольно начинаешь писать более модульный и гибкий код. Почему?

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

Для TypeScript это особенно актуально, потому что статическая типи

Блоги

Рекомендаций по книгам