🧪 Успешное тестирование: TDD и расширенные возможности с Jest. Часть 2
Подробное руководство по внедрению TDD в рабочий процесс и использованию продвинутых возможностей Jest. С примерами кода и пояснениями каждого шага.
Привет, друзья! Я – Кирилл Мыльников, frontend-разработчик в ГК Юзтех. Сегодня стартует вторая часть увлекательного путешествия в мире тестирования с помощью Jest. Если вы пропустили первую часть, не беда, ссылку на нее оставлю тут. Мы начнем с краткого обзора теории и сразу перейдем к конкретным примерам. Весь код с примерами будет выкладываться на GitHub для вашего удобства.
В данной статье разберем следующие темы:
- TDD – что это?
- Практический пример использования
- Расширенные возможности Jest
TDD – что это такое?
Test Driven Development – методология разработки программного обеспечения, основанная на принципах тестирования.
Основная идея TDD – Интеграция тестирования с самых ранних этапов разработки играет ключевую роль в минимизации ошибок и повышении качества программного обеспечения. Поддерживая постоянное тестирование на всех стадиях процесса разработки, мы обеспечиваем более стабильный и надежный продукт. Давайте продолжим интеграцию тестирования на каждом этапе разработки для достижения оптимальных результатов.
Процесс TDD
Выделим с вами сразу несколько положительных сторон и укажем их в виде мемов :)
Повышение продуктивности – Благодаря тому, что ошибки выявляются на ранних этапах благодаря тестированию, время, затраченное на исправление дефектов, значительно сокращается. Это позволяет сэкономить ресурсы команды разработки и ускорить выпуск продукта на рынок.
Повышение стабильности и устойчивости – Благодаря тестированию на ранних этапах разработки возможно быстрое обнаружение и устранение ошибок, что позволяет снизить риски, связанные с внедрением нового функционала в существующие системы. Этот подход повышает надежность и стабильность программного обеспечения.
Долгосрочная поддержка кода – Обширный набор тестов упрощает процесс внесения изменений в проект, что минимизирует риски нарушения работы существующего функционала. Это способствует гибкости разработки и повышению стабильности программного обеспечения.
Разобрали тему TDD в теории, давайте приступим к практике
Практический пример использования и с чего начать?
Мы начнем разработку с написания тестов. Давайте возьмем поле email в качестве примера и определим базовые требования к нему. Далее мы составим тесты, чтобы протестировать это поле. Давайте приступим!
Пример:
На текущем этапе мы только сформулировали требования к задаче в виде тестов. Если у вас возникли вопросы или нужна дополнительная информация для формирования требований, обратитесь к заказчику для уточнения деталей. После описанных требований, давай просто напишем тесты, но обратите внимание, фича с email
у нас еще не реализована
Пример с тестом:
import { validateEmail } from "./validateEmail"; describe("validateEmail", () => { it("should return true for valid email", () => { expect(validateEmail("test@example.com")).toBe(true); }); it("should return false for invalid email", () => { expect(validateEmail("")).toBe(false); expect(validateEmail("testexample.com")).toBe(false); expect(validateEmail("test@example@example.com")).toBe(false); expect(validateEmail("test@example")).toBe(false); expect(validateEmail("test@192.168.1.1")).toBe(false); }); it("should return true for email with dot at the end", () => { expect(validateEmail("test@example.com.")).toBe(true); }); it("should return true for email with plus sign", () => { expect(validateEmail("test+example@example.com")).toBe(true); }); it("should return true for email with special character in local part", () => { expect(validateEmail("test!example@example.com")).toBe(true); }); });
Пример с функцией validateEmail:
export const validateEmail = (email: string) => {};
После запуска тестов, мы видим, что они упали и это логично, потому что мы сначала написали тесты, а теперь давайте перейдем уже к функционалу.
После чего можем писать уже функционал и поэтапно запускать тесты
Пример:
export function validateEmail(email: string) { if (!email) { return false; } const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailRegex.test(email); }
Результат:
Изменение требований (рефакторинг)
Теперь, при изменении требований, необходимо не только выводить общую ошибку "Неверный email"
, но также точно подсвечивать клиенту, что именно было введено некорректно. После изменения требований потребуется провести рефакторинг, так как после запуска тестов они упадут.
Новые требования:
- Отображать текст ошибки когда пустое поле:
"Email should not be empty."
- Отображать текст ошибки если отсутствует символ
"@"
:"Invalid email format. Please include '@' symbol."
Пример:
После рефакторинга
Тесты, которые были добавлены:
it('should return "Email should not be empty" for empty email', () => { expect(validateEmail("")).toBe(validationEmailMessage.length); }); it('should return "Invalid email format. Please include "@" symbol." for invalid email', () => { expect(validateEmail("testexample.com")).toBe( validationEmailMessage.format ); });
Ну вот, мы посмотрели жизненный цикл TDD на примере Jest. Давайте поговорим о более продвинутых возможностях Jest, которые помогут вам улучшить тестирование вашего приложения, а именно:
- Несколько конфигов
- Shapshot тестирование
Несколько конфигов
Есть ситуации, когда нам нужно 2 разных jest конфига, например отдельный конфиг для unit-тестов и отдельный конфиг для интеграционных тестов. Соответственно можем их настраивать по-разному, хранить в разных папках, создавать разные расширения для файлов и т. д. Давайте перейдем сразу к практике, у нас есть уже один файл jest конфига, создаем второй под названием jest.integration.config и пропишем сразу настройки.
import type { Config } from "jest"; const config: Config = { verbose: true, preset: "ts-jest", collectCoverage: true, collectCoverageFrom: [ "<rootDir>/*.{js,ts}", "!**/node_modules/**", "!<rootDir>/*.mock*", "!<rootDir>/*.config.*", ], testMatch: ["<rootDir>/src/**/*.spec.ts"], }; export default config;
Обратите внимание на testMatch, где наши интеграционные тесты будут лежать с расширением spec. Чтобы по разному запускать тесты, нужно это указать в scripts
Пишем тесты уже с расширением spec и запускаем их. Подробно можно посмотреть тут
Пример:
Snapshot тестирование – это подход к тестированию, который фиксирует вывод или состояние приложения и сравнивает его с ранее сохраненным снимком. Этот метод тестирования опциональный, используется не всегда.
Давайте сразу перейдем к практике, создадим с вами обычную функцию, которая будет создавать с вами объект.
Пример:
type User = { name?: string; lastName?: string; age?: number; phone?: string; email?: string; }; export const createUser = ({ name, lastName, age, phone, email }: User) => { return { name, phone, email, age, lastName, }; };
Теперь давайте напишем к нему тесты и запустим
Пример:
import { createUser } from "./createUser"; describe("createUser", () => { it("should create user with firstname and lastname", () => { const user = createUser({ name: "Kirill", lastName: "Korobov" }); expect(user).toMatchSnapshot(); }); });
Здесь с вами создаем пользователя и когда запустим тесты, то сделается снимок нашего теста
Результат:
Теперь давайте просто в нашей функции удалим одно значение и заново запустим тесты. Я удалил поле name
и теперь снепшот-тесты падают, т. к. ожидалось это поле.
Пример:
А если вы вправду удалили это поле и оно больше не нужно, тогда нужно обновить снепшоты, в моем случае командой yarn start -u
Обновленный снепшот файл
Итак, мы с вами разобрали, что такое TDD и увидели все это на примере. Рассмотрели более продвинутые возможности Jest. Впереди нас ждет 3 часть статьи уже на примере React приложения.
Материалы:
GitHub —https://github.com/kirill0202/Jest
Jest — https://jestjs.io/