Все статьи

Тестирование Full Stack приложения: Unit, Integration и E2E тесты

Полное руководство по тестированию React, FastAPI и PostgreSQL: Vitest, React Testing Library, pytest, Playwright, Testcontainers, Mock API и автоматизация тестов в CI/CD

33 минут чтения

Представим, что вы разрабатываете SaaS-платформу для управления проектами. Frontend написан на React и TypeScript. Backend работает на FastAPI. Данные хранятся в PostgreSQL, а Redis используется для кеширования и фоновых задач. Приложение уже работает в Production. Пользователи могут регистрироваться, создавать проекты, приглашать участников и управлять задачами. Однажды команда выпускает небольшое обновление.

Разработчик изменил функцию расчёта стоимости подписки. Изменение занимает всего несколько строк. Локально всё выглядит правильно. Код проходит проверку TypeScript. Приложение успешно собирается. Но через несколько часов после деплоя служба поддержки получает сообщения: пользователи с годовой подпиской видят неправильную стоимость. Проблему исправляют. Через неделю происходит другая ситуация.

Backend-разработчик изменяет формат ошибки авторизации. API продолжает работать, но Frontend перестаёт правильно показывать сообщение о завершении сессии. Ещё через несколько дней обновление базы данных ломает создание проектов для новых пользователей. Каждый раз причина разная. Но общая проблема одна: команда проверяет отдельные изменения вручную и не имеет системы автоматического тестирования.

Именно здесь становится понятно, зачем нужны тестирование Full Stack приложения, Unit Integration E2E тесты и автоматизированная проверка всей системы. В этой статье мы построим практическую стратегию тестирования для стека React + TypeScript + FastAPI + PostgreSQL.

Разберём, какие тесты писать, какие инструменты использовать, как проверять API и базу данных, когда нужны Mock-объекты, как тестировать реальные пользовательские сценарии и как запускать проверки в GitHub Actions. Главная цель — создать систему, которая помогает находить ошибки до Production, а не после жалоб пользователей.


1. Что такое автоматическое тестирование веб-приложения

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

  • правильно ли рассчитывается стоимость подписки;
  • может ли пользователь зарегистрироваться;
  • запрещён ли доступ к чужим проектам;
  • сохраняются ли данные в PostgreSQL;
  • отображается ли ошибка при недоступности API;
  • работает ли создание задачи через браузер.

Вместо того чтобы каждый раз вручную открывать приложение и повторять действия, разработчик создаёт тесты. Тест запускает определённый сценарий и сравнивает результат с ожидаемым. Простой пример: function calculateTotal( price: number, quantity: number ): number { return price * quantity; } Тест:

import { expect, test } from "vitest";

test("calculates total", () => { expect(calculateTotal(100, 3)).toBe(300); }); Если функция начнёт возвращать неправильный результат, тест обнаружит проблему. Но такой тест проверяет только одну функцию. Чтобы убедиться, что пользователь действительно может оформить заказ, потребуются проверки других уровней.

2. Почему одного типа тестов недостаточно

Представим процесс создания проекта: Пользователь | v React Form | v POST /api/projects | v FastAPI | v Business Logic | v PostgreSQL Каждый слой может работать правильно по отдельности. Но система в целом всё равно может содержать ошибки. Например:

  • React отправляет projectName, а Backend ожидает title;
  • Backend возвращает 201, но забывает сохранить запись;
  • PostgreSQL отклоняет запрос из-за ограничения;
  • API работает, но кнопка отправки формы отключена;
  • после успешного создания проекта Frontend не обновляет список.

Поэтому стратегия тестирования Full Stack проекта должна включать проверки разных уровней. Обычно выделяют три основных типа:

  1. Unit Tests — проверка отдельных функций и компонентов.
  2. Integration Tests — проверка взаимодействия нескольких частей системы.
  3. E2E Tests — проверка пользовательских сценариев от начала до конца.

Эти уровни дополняют друг друга.

3. Unit vs Integration vs E2E: основные различия

Unit vs Integration vs E2E тестирование удобно сравнить в таблице. Не стоит пытаться проверить всю систему только через E2E. Такие тесты могут быть медленными и сложными в сопровождении. Но и одних Unit Tests недостаточно, поскольку они не гарантируют корректную работу интеграций.


4. Что такое пирамида тестирования

Пирамида тестирования Full Stack — модель распределения тестов по уровням. Классическая идея: /\ / \ / E2E\ /------\ / \ /Integration\ /------------\ / \ / Unit Tests \ /__________________\ В основании находятся многочисленные быстрые проверки отдельных функций. Выше — интеграционные тесты. На вершине — сравнительно небольшое количество E2E-сценариев. Это не строгая математическая пропорция.

Не существует универсального правила, что проект обязан иметь ровно 70% Unit, 20% Integration и 10% E2E. Правильное распределение зависит от архитектуры и рисков. Например, приложение с большим количеством SQL-запросов может нуждаться в значительном количестве интеграционных тестов.

5. Что такое Test Pyramid и Testing Trophy

Кроме пирамиды существует концепция Testing Trophy. Test Pyramid vs Testing Trophy — обсуждение того, на каком уровне проверки дают максимальную пользу. Testing Trophy делает акцент на интеграционных тестах, особенно в интерфейсных приложениях. Причина проста: многие ошибки возникают не внутри отдельных функций, а при взаимодействии компонентов.

Например, отдельно протестированный React Button может работать правильно. Но реальная форма всё равно может неправильно обрабатывать состояние, ошибки и отправку данных. Поэтому важно не следовать схеме механически, а выбирать проверки, соответствующие реальным рискам продукта.

6. Как выбрать, что тестировать первым

Если приложение уже существует и тестов почти нет, не нужно начинать с попытки покрыть каждую строку. Приоритеты тестирования веб-приложения лучше определять по последствиям ошибки. В первую очередь обычно проверяют:

  • авторизацию;
  • регистрацию;
  • платежи;
  • права доступа;
  • создание и изменение важных данных;
  • критические расчёты;
  • миграции базы данных;
  • основные пользовательские сценарии.

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


7. Как настроить Vitest для React

Vitest — инструмент тестирования, хорошо интегрирующийся с Vite. Vitest React TypeScript настройка начинается с установки зависимостей. npm install -D vitest Добавим команду в package.json: { "scripts": { "test": "vitest", "test:run": "vitest run" } } Теперь тесты можно запускать: npm run test:run Vitest найдёт файлы с подходящими именами, например: calculateTotal.test.ts И выполнит проверки.

8. Как написать первый Unit Test

Unit тесты JavaScript TypeScript особенно полезны для чистых функций. Представим функцию расчёта скидки: export function calculateDiscount( price: number, discountPercent: number ): number { return price * (1 - discountPercent / 100); } Тест:

import {

describe, expect, it } from "vitest";

import {

calculateDiscount } from "./calculateDiscount"; describe("calculateDiscount", () => { it("applies 20 percent discount", () => { expect( calculateDiscount(1000, 20) ).toBe(800); }); it("returns original price for zero discount", () => { expect( calculateDiscount(1000, 0) ).toBe(1000); }); }); Здесь проверяются два сценария. Но важно учитывать граничные случаи.

Например, что должно происходить при отрицательной цене или скидке больше 100%? Тесты помогают не только обнаруживать ошибки, но и уточнять требования к бизнес-логике.

9. Что такое Arrange Act Assert

Arrange Act Assert тестирование — распространённый способ организации теста. Он состоит из трёх этапов. Arrange — подготовка данных. Act — выполнение действия. Assert — проверка результата. Пример:

import { expect, it } from "vitest";

it("calculates subscription price", () => { // Arrange const monthlyPrice = 500; const months = 12; // Act const total = monthlyPrice * months; // Assert expect(total).toBe(6000); }); Такую структуру легко читать. Она помогает быстро понять, что именно проверяется.

10. Как тестировать граничные значения

Многие ошибки возникают на границах допустимых значений. Boundary Value Testing особенно полезно для:

  • цен;
  • скидок;
  • лимитов;
  • размеров файлов;
  • длины паролей;
  • пагинации;
  • дат.

Например, если название проекта должно содержать от 3 до 100 символов, полезно проверить длины:

2 символа → ошибка

3 символа → допустимо

100 символов → допустимо

101 символ → ошибка

Тестирование только обычного значения длиной 20 символов не обнаружит ошибки на границах.

11. Как использовать параметризованные тесты Vitest

Если одна функция должна работать с несколькими наборами данных, удобно использовать параметризованные тесты. Vitest test.each примеры:

import { expect, test } from "vitest";

function isValidAge(age: number): boolean { return age >= 18; } test.each([ [17, false], [18, true], [19, true], [0, false] ])( "isValidAge(%i) returns %s", (age, expected) => { expect(isValidAge(age)).toBe(expected); } ); Это уменьшает дублирование кода. Особенно полезно при проверке бизнес-правил.


12. Как тестировать React-компоненты

Для проверки интерфейса часто используют React Testing Library. React Testing Library Vitest позволяет тестировать компоненты через их видимое поведение. Установка: npm install -D \ @testing-library/react \ @testing-library/jest-dom \ @testing-library/user-event \ jsdom В vite.config.ts можно настроить тестовое окружение:

import {

defineConfig } from "vitest/config";

import react from "@vitejs/plugin-react";

export default defineConfig({ plugins: [react()], test: { environment: "jsdom", setupFiles: ["./src/test/setup.ts"] } }); Создадим src/test/setup.ts:

import "@testing-library/jest-dom/vitest";

Теперь можно тестировать React-компоненты.

13. Как проверить отображение React-компонента

Представим компонент: type GreetingProps = { name: string; }; export function Greeting({ name }: GreetingProps) { return <h1>Привет, {name}!</h1>; } Тест:

import {

render, screen } from "@testing-library/react";

import {

expect, it } from "vitest";

import {

Greeting } from "./Greeting"; it("renders greeting", () => { render(<Greeting name="Alex" />); expect( screen.getByRole("heading", { name: "Привет, Alex!" }) ).toBeInTheDocument(); }); Тестирование React компонентов лучше строить вокруг того, что видит и делает пользователь. Это делает тесты менее зависимыми от внутренней реализации компонента.

14. Почему лучше использовать getByRole

React Testing Library предоставляет разные способы поиска элементов. Например: screen.getByText("Сохранить"); Но часто предпочтительнее искать элемент по роли. Testing Library getByRole: screen.getByRole("button", { name: "Сохранить" }); Такой запрос ближе к тому, как интерфейс воспринимается через доступные пользователю семантические признаки. Он также помогает обнаруживать проблемы доступности.

Если кнопка не имеет понятного доступного имени, тест может подсветить эту проблему.

15. Как тестировать пользовательские действия

Проверим счётчик:

import { useState } from "react";

export function Counter() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount((value) => value + 1)} > Count: {count} </button> ); } Тест:

import {

render, screen } from "@testing-library/react";

import userEvent from "@testing-library/user-event";

import {

expect, it } from "vitest";

import { Counter } from "./Counter";

it("increments count after click", async () => { const user = userEvent.setup(); render(<Counter />); await user.click( screen.getByRole("button", { name: "Count: 0" }) ); expect( screen.getByRole("button", { name: "Count: 1" }) ).toBeInTheDocument(); }); User Event React Testing Library позволяет моделировать действия пользователя. Это полезнее, чем вручную вызывать внутренние обработчики компонента.

16. Как тестировать React-формы

Формы — одна из наиболее важных частей пользовательского интерфейса. Тестирование форм React должно проверять:

  • ввод данных;
  • обязательные поля;
  • ошибки валидации;
  • отправку формы;
  • состояние загрузки;
  • успешное завершение;
  • ошибки Backend.

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


17. Что такое Mock в тестировании

Mock — подмена реальной зависимости контролируемой реализацией. Mock в тестировании React полезен, когда компонент зависит от API, времени, внешнего сервиса или другой нестабильной системы. Например, функция: async function getProjects() { return fetch("/api/projects"); } В Unit Test не всегда нужно отправлять реальный HTTP-запрос. Можно заменить зависимость тестовой реализацией.

Но важно не злоупотреблять Mock-объектами. Если все зависимости подменены, тест может успешно проходить даже тогда, когда реальные компоненты несовместимы.

18. Чем Mock отличается от Stub и Fake

Mock Stub Fake разница заключается в назначении тестовых замен. Stub возвращает заранее подготовленные ответы. Fake предоставляет упрощённую рабочую реализацию зависимости. Mock часто используется для проверки взаимодействий и вызовов. Например:

import {

expect, it, vi } from "vitest"; it("calls notification function", () => { const notify = vi.fn(); notify("Project created"); expect(notify).toHaveBeenCalledWith( "Project created" ); }); В реальных проектах термин Mock иногда используют как общее название разных тестовых замен. Главное — понимать, какую именно проверку выполняет тест.

19. Как использовать Mock Service Worker

MSW позволяет перехватывать сетевые запросы в тестовой среде. MSW Mock API React полезен для тестирования компонентов, которые получают данные через HTTP. Установка: npm install -D msw Пример обработчика:

import {

http, HttpResponse } from "msw"; export const handlers = [ http.get("/api/projects", () => { return HttpResponse.json([ { id: 1, title: "Project Alpha" } ]); }) ]; В тестовом окружении необходимо дополнительно настроить MSW Server и его жизненный цикл. Например, запускать сервер перед тестами, сбрасывать обработчики после каждого теста и закрывать сервер после завершения набора проверок.

MSW помогает тестировать поведение Frontend без зависимости от доступности настоящего Backend. Но он не доказывает, что реальный API соответствует Mock-контракту.

20. Как тестировать состояния загрузки и ошибки

Представим компонент, который загружает проекты. Возможные состояния: Loading | +-- Success | +-- Error Тестирование loading error state React должно проверять каждую ветку. Например:

  • во время запроса отображается индикатор загрузки;
  • после успешного ответа появляется список;
  • при ошибке показывается сообщение;
  • пользователь может повторить запрос.

Особенно важно проверять ошибки. В обычной разработке успешный сценарий часто тестируют вручную, а отказ API остаётся без внимания.


21. Как настроить pytest для FastAPI

Перейдём к Backend. pytest FastAPI тестирование — один из распространённых способов проверки Python-приложений. Установка: pip install pytest httpx Создадим простое приложение: from fastapi import FastAPI app = FastAPI() @app.get("/health") def health(): return {"status": "ok"} Теперь можно написать тест.

22. Как тестировать FastAPI через TestClient

FastAPI предоставляет удобную интеграцию с HTTPX через TestClient. FastAPI TestClient pytest позволяет отправлять запросы к приложению без запуска отдельного HTTP-сервера. from fastapi.testclient import TestClient from app.main import app client = TestClient(app) def test_health(): response = client.get("/health") assert response.status_code == 200 assert response.json() == { "status": "ok" }

Это полезно для проверки:

  • HTTP-статусов;
  • JSON-ответов;
  • валидации;
  • ошибок;
  • маршрутизации;
  • зависимостей.

Но если Endpoint обращается к реальной базе данных, необходимо отдельно подготовить тестовую инфраструктуру.

23. Как тестировать бизнес-логику FastAPI

Лучше не помещать всю бизнес-логику непосредственно в Endpoint. Например: def calculate_subscription_price( monthly_price: int, months: int ) -> int: if months <= 0: raise ValueError( "Months must be positive" ) return monthly_price * months Тест:

import pytest

from app.services.billing import ( calculate_subscription_price ) def test_subscription_price(): assert calculate_subscription_price( 500, 12 ) == 6000 def test_invalid_months(): with pytest.raises(ValueError): calculate_subscription_price( 500, 0 ) Unit тесты Python FastAPI позволяют проверять бизнес-правила независимо от HTTP и базы данных. Такие тесты обычно быстрые и удобные для диагностики.

24. Как использовать pytest fixtures

Fixtures помогают повторно использовать подготовку тестового окружения. pytest fixtures примеры:

import pytest

@pytest.fixture def sample_project(): return { "id": 1, "title": "Project Alpha" } def test_project_title(sample_project): assert sample_project["title"] == ( "Project Alpha" ) Fixtures могут создавать:

  • тестовых пользователей;
  • HTTP-клиентов;
  • подключения к базе;
  • временные файлы;
  • объекты конфигурации.

Важно правильно управлять жизненным циклом ресурсов. Особенно если тесты используют PostgreSQL или другие внешние сервисы.

25. Как переопределять зависимости FastAPI

FastAPI поддерживает Dependency Injection. Это удобно для тестирования. FastAPI dependency_overrides тесты позволяют заменить зависимость приложения. Например: from fastapi import Depends, FastAPI from fastapi.testclient import TestClient app = FastAPI() def get_current_user(): raise RuntimeError( "Authentication not configured" ) @app.get("/me") def get_me(user=Depends(get_current_user)): return user

def fake_current_user(): return { "id": 1, "email": "test@example.com" } def test_me(): app.dependency_overrides[ get_current_user ] = fake_current_user try: with TestClient(app) as client: response = client.get("/me") assert response.status_code == 200 assert response.json()["id"] == 1 finally: app.dependency_overrides.clear() Очистка переопределений важна.

Иначе изменения тестового окружения могут влиять на последующие тесты.


26. Почему интеграционные тесты базы данных необходимы

Представим, что сервис создаёт проект. Внутри используются:

  • SQLAlchemy;
  • PostgreSQL;
  • ограничения таблиц;
  • транзакции;
  • индексы.

Unit Test с Mock-репозиторием может проверить, что функция вызывает метод сохранения. Но он не проверит, действительно ли PostgreSQL принимает данные. Интеграционные тесты PostgreSQL FastAPI позволяют обнаруживать:

  • ошибки SQL;
  • проблемы миграций;
  • нарушения ограничений;
  • неправильные связи;
  • ошибки транзакций;
  • различия поведения СУБД.

Особенно важно тестировать на той же СУБД, которая используется в Production.

27. Почему SQLite не всегда заменяет PostgreSQL

SQLite удобен для простых тестов. Но его поведение отличается от PostgreSQL. Например, различаться могут:

  • типы данных;
  • JSON-операции;
  • блокировки;
  • ограничения;
  • SQL-синтаксис;
  • транзакционная семантика;
  • поведение конкурентных запросов.

Поэтому PostgreSQL для интеграционных тестов часто является более надёжным вариантом, чем полная замена базы на SQLite. Это особенно важно, если приложение использует специфичные возможности PostgreSQL.

28. Как запустить тестовую PostgreSQL через Docker

Для локальных тестов можно использовать Docker Compose. Docker Compose PostgreSQL тестирование: services: test-db: image: postgres:16 environment: POSTGRES_DB: app_test POSTGRES_USER: test_user POSTGRES_PASSWORD: test_password ports: - "127.0.0.1:5433:5432" healthcheck: test: - CMD-SHELL - "pg_isready -U test_user -d app_test" interval: 5s timeout: 3s retries: 10 Запуск: docker compose up -d test-db

Подключение из тестов, запущенных на хосте: postgresql://test_user:test_password@localhost:5433/app_test Если тесты запускаются в другом контейнере той же Compose-сети, адрес подключения будет отличаться. Обычно используется имя сервиса test-db и внутренний порт 5432. Важно: тестовая база должна быть изолирована от Production.

29. Как использовать Testcontainers

Testcontainers позволяет создавать временные контейнеры для интеграционных тестов. Testcontainers Python PostgreSQL помогает автоматизировать запуск базы данных. Установка: pip install testcontainers[postgres] Пример: from sqlalchemy import ( create_engine, text ) from testcontainers.postgres import ( PostgresContainer ) def test_postgres_connection(): with PostgresContainer( "postgres:16" ) as postgres:

engine = create_engine( postgres.get_connection_url() ) with engine.connect() as connection:

result = connection.execute(

text("SELECT 1") ) assert result.scalar() == 1 Для работы потребуется доступный Docker Engine. В реальном проекте также необходимо выполнять миграции и управлять тестовыми данными.

30. Как изолировать данные между тестами

Одна из распространённых проблем — зависимость тестов друг от друга. Например: Тест A создаёт пользователя Тест B ожидает пустую таблицу Тест B падает Изоляция тестов PostgreSQL может обеспечиваться несколькими способами:

  • отдельной базой для набора тестов;
  • транзакциями с откатом;
  • очисткой таблиц;
  • созданием временных схем;
  • отдельными контейнерами.

Транзакционный откат удобен, но не всегда подходит для проверки поведения, связанного с реальными COMMIT, несколькими соединениями и фоновыми процессами. Поэтому стратегию изоляции необходимо выбирать с учётом архитектуры приложения.

31. Как тестировать миграции Alembic

Миграции могут ломать Production даже тогда, когда API-тесты проходят. Тестирование миграций Alembic должно проверять, что схема базы данных успешно обновляется. Например: alembic upgrade head В CI можно создать пустую PostgreSQL, выполнить все миграции и затем запустить интеграционные тесты. Для сложных проектов полезно дополнительно проверять миграцию с предыдущей версии схемы.

Но успешное выполнение upgrade head не гарантирует безопасную миграцию больших Production-таблиц. Необходимо учитывать блокировки, время выполнения и совместимость старого и нового кода.


32. Как тестировать REST API

Тестирование REST API FastAPI должно включать не только успешные запросы. Для Endpoint создания проекта полезно проверить: Конкретные HTTP-статусы зависят от API-контракта. Например, некоторые приложения используют другие решения для конфликтов и ошибок валидации. Главное — чтобы поведение было согласованным и проверяемым.

33. Как тестировать авторизацию и права доступа

Авторизация — один из самых критичных участков приложения. Тестирование авторизации FastAPI должно проверять:

  • отсутствие сессии;
  • недействительную сессию;
  • истёкший токен;
  • доступ с корректной ролью;
  • запрет доступа к чужим данным;
  • отзыв доступа;
  • изменение ролей.

Например:

User A → Project A → Allowed

User B → Project A → Forbidden

Особенно важно тестировать горизонтальное повышение привилегий. Это ситуация, когда пользователь получает доступ к объектам другого пользователя, изменяя идентификатор в URL.

34. Как тестировать API-контракты

Представим, что Frontend ожидает: { "id": 42, "title": "Project Alpha" } Но Backend возвращает: { "id": 42, "name": "Project Alpha" } Отдельные тесты обеих частей могут проходить. Однако интеграция сломана. Контрактное тестирование API помогает проверять соответствие согласованному формату данных. Один из подходов — OpenAPI. Backend предоставляет схему. Frontend генерирует типы. CI проверяет совместимость изменений.

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

35. Что такое Consumer Driven Contract Testing

Consumer Driven Contract Testing — подход, при котором потребитель API формулирует ожидания к взаимодействию с поставщиком. Например, React-приложение ожидает определённые поля ответа FastAPI. Эти ожидания могут быть представлены контрактами и проверены на стороне Backend. Один из инструментов для такого подхода — Pact. Но внедрять его в небольшой монолитный проект необязательно.

Контрактное тестирование особенно полезно, когда Frontend и Backend развиваются независимо и имеют отдельные циклы релизов.


36. Что такое E2E тестирование

End-to-End Testing проверяет приложение через сценарии, максимально близкие к действиям пользователя. E2E тестирование веб-приложения может включать: Открыть сайт | v Войти в аккаунт | v Создать проект | v Добавить задачу | v Проверить результат В таком тесте взаимодействуют:

  • браузер;
  • Frontend;
  • Backend;
  • база данных;
  • иногда внешние сервисы.

E2E помогает обнаруживать ошибки, которые не видны на уровне отдельных функций.

37. Как настроить Playwright

Playwright — инструмент автоматизации браузеров и E2E-тестирования. Playwright React E2E тесты можно настроить в Frontend-проекте. Установка: npm init playwright@latest Мастер настройки поможет создать конфигурацию и пример теста. После установки браузеров можно запускать проверки: npx playwright test Для CI необходимо убедиться, что нужные браузеры и системные зависимости установлены.

38. Как написать первый тест Playwright

Playwright тестирование сайта начинается с простого сценария.

import {

expect, test } from "@playwright/test"; test("home page has title", async ({ page }) => { await page.goto("/"); await expect( page.getByRole("heading", { name: "ProjectFlow" }) ).toBeVisible(); }); Для использования относительного URL нужно настроить baseURL в конфигурации Playwright. Например:

import {

defineConfig } from "@playwright/test"; export default defineConfig({ use: { baseURL: "http://127.0.0.1:5173" } }); Во время теста приложение должно быть доступно по этому адресу.

39. Как автоматически запускать Frontend для E2E

Playwright поддерживает запуск локального сервера перед тестами. Playwright webServer настройка:

import {

defineConfig } from "@playwright/test"; export default defineConfig({ webServer: { command: "npm run dev -- --host 127.0.0.1", url: "http://127.0.0.1:5173", reuseExistingServer: !process.env.CI, timeout: 120_000 }, use: { baseURL: "http://127.0.0.1:5173" } }); Для Production-подобных проверок лучше тестировать собранную версию приложения. Например, использовать npm run build и сервер предпросмотра сборки.

Но для полноценного E2E также потребуется запущенный Backend и подготовленная тестовая база данных.

40. Как тестировать форму через Playwright

Представим страницу регистрации. Playwright тестирование формы регистрации:

import {

expect, test } from "@playwright/test"; test("shows validation error", async ({ page }) => { await page.goto("/register"); await page.getByLabel("Email").fill( "invalid-email" ); await page.getByRole("button", { name: "Зарегистрироваться" }).click(); await expect( page.getByText("Некорректный email") ).toBeVisible(); }); Этот тест предполагает, что приложение действительно отображает указанное сообщение.

Тест должен соответствовать реальному пользовательскому поведению и контракту интерфейса.

41. Как тестировать создание проекта через браузер

Рассмотрим критический сценарий SaaS-приложения. Playwright E2E создание проекта:

import {

expect, test } from "@playwright/test"; test("user creates project", async ({ page }) => { await page.goto("/dashboard"); await page.getByRole("button", { name: "Создать проект" }).click(); await page.getByLabel( "Название проекта" ).fill("Project Alpha"); await page.getByRole("button", { name: "Сохранить" }).click(); await expect( page.getByText("Project Alpha") ).toBeVisible(); });

В примере предполагается, что пользователь уже авторизован. Для реального E2E необходимо подготовить состояние сессии и тестовые данные.

42. Как ускорить E2E через Storage State

Повторная авторизация в каждом тесте увеличивает время выполнения. Playwright позволяет сохранять состояние браузерного контекста. Playwright storageState авторизация помогает повторно использовать подготовленную тестовую сессию. Например, после успешного входа: await page.context().storageState({ path: "playwright/.auth/user.json" }); Затем сохранённое состояние можно подключать в тестах.

Но файл может содержать чувствительные данные авторизации. Его нельзя публиковать в репозитории. Тестовые аккаунты также должны быть изолированы от Production.


43. Почему E2E тесты бывают нестабильными

Flaky Test — тест, который иногда проходит, а иногда падает без соответствующего изменения проверяемого поведения. Flaky Tests Playwright могут возникать из-за:

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

Например, плохой тест: await page.waitForTimeout(3000); Он предполагает, что три секунды всегда достаточно. Но на медленном CI-сервере это может быть неправдой. Лучше ждать конкретное состояние интерфейса.

44. Как правильно использовать ожидания Playwright

Playwright auto waiting помогает дождаться готовности элементов к действиям. Например: await expect( page.getByRole("button", { name: "Сохранить" }) ).toBeEnabled(); Или: await expect( page.getByText("Проект создан") ).toBeVisible(); Такие ожидания обычно устойчивее фиксированных задержек. Но автоматическое ожидание не решает проблемы неправильной изоляции данных или нестабильных внешних сервисов.

45. Как использовать Page Object Model

Когда E2E-тестов становится много, действия с интерфейсом начинают повторяться. Page Object Model Playwright позволяет вынести взаимодействие со страницей в отдельный класс. Например:

import type {

Page } from "@playwright/test"; export class LoginPage { constructor( private readonly page: Page ) {} async open() { await this.page.goto("/login"); } async login( email: string, password: string ) { await this.page.getByLabel( "Email" ).fill(email); await this.page.getByLabel( "Пароль" ).fill(password); await this.page.getByRole( "button", { name: "Войти" } ).click(); } } Такой подход может уменьшить дублирование.

Но слишком сложная иерархия Page Objects иногда делает тесты менее понятными. Необходимо соблюдать баланс.

46. Как тестировать мобильные разрешения

Playwright тестирование адаптивности позволяет запускать сценарии с разными размерами экрана. Например:

import {

devices, defineConfig } from "@playwright/test"; export default defineConfig({ projects: [ { name: "desktop-chromium", use: { browserName: "chromium" } }, { name: "mobile-chromium", use: { ...devices["Pixel 7"] } } ] }); Это помогает обнаруживать проблемы, связанные с мобильной компоновкой интерфейса. Но эмуляция устройства не полностью заменяет тестирование на реальном устройстве.


47. Что такое Visual Regression Testing

Visual Regression Testing проверяет визуальные изменения интерфейса. Visual Regression Testing React может обнаруживать:

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

Playwright поддерживает сравнение скриншотов. Пример:

import {

expect, test } from "@playwright/test"; test("dashboard screenshot", async ({ page }) => { await page.goto("/dashboard"); await expect(page).toHaveScreenshot( "dashboard.png" ); }); Для стабильных результатов необходимо контролировать:

  • шрифты;
  • анимации;
  • тестовые данные;
  • размеры экрана;
  • окружение выполнения.

48. Как тестировать доступность интерфейса

Доступность важна для пользователей, использующих клавиатуру и вспомогательные технологии. Accessibility Testing React может включать:

  • проверку семантических ролей;
  • доступных имён;
  • работы клавиатуры;
  • управления фокусом;
  • контрастности;
  • ошибок форм.

Автоматические инструменты, например axe-core, помогают обнаруживать часть проблем. Но автоматические проверки не заменяют ручное тестирование доступности. Особенно важно проверять реальные пользовательские сценарии с клавиатурой.


49. Как измерять покрытие тестами

Test Coverage показывает, какая часть кода выполнялась во время тестов. Code Coverage Vitest pytest может измерять:

  • покрытие строк;
  • покрытие ветвей;
  • покрытие функций;
  • покрытие инструкций.

Для Vitest можно установить инструмент покрытия: npm install -D @vitest/coverage-v8 Затем: npx vitest run --coverage Для Python: pip install pytest-cov Запуск: pytest --cov=app --cov-report=term-missing Но высокий процент покрытия не гарантирует качественные тесты.

50. Почему 100% Coverage не означает отсутствие ошибок

Представим тест: it("calls function", () => { calculateTotal(100, 2); }); Он выполняет функцию. Но не проверяет результат. Такие тесты могут повышать покрытие, почти не увеличивая уверенность в корректности приложения. Почему 100 процентов Code Coverage недостаточно? Потому что покрытие показывает выполнение кода, а не качество проверяемых утверждений. Важно тестировать поведение, ошибки и граничные случаи.

51. Что такое Mutation Testing

Mutation Testing помогает оценивать способность тестов обнаруживать ошибки. Mutation Testing JavaScript Python основан на внесении небольших изменений в код. Например: return price * quantity; Инструмент может заменить оператор: return price + quantity; Если тесты продолжают проходить, значит они недостаточно хорошо проверяют поведение. Mutation Testing может быть полезен для критической бизнес-логики.

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


52. Как тестировать внешние API

Full Stack приложения часто интегрируются с:

  • платёжными системами;
  • email-провайдерами;
  • облачными хранилищами;
  • CRM;
  • аналитическими сервисами.

Тестирование внешних API интеграций требует разделения уровней. Unit Tests могут использовать Fake или Stub. Интеграционные тесты могут работать с Sandbox-окружением поставщика. Контрактные тесты проверяют ожидаемый формат взаимодействия. E2E-тесты обычно не должны зависеть от реальных платёжных операций. Для критических интеграций полезно иметь отдельный набор проверок с тестовым окружением провайдера.

53. Как тестировать Redis

Redis часто используется для кеша, сессий и очередей. Тестирование Redis FastAPI может включать:

  • запись и чтение кеша;
  • TTL;
  • инвалидирование;
  • отсутствие данных;
  • ошибки подключения;
  • конкурентный доступ.

Для Unit Tests можно использовать тестовые замены. Но если поведение зависит от реального Redis, нужны интеграционные проверки. Например, тест должен убедиться, что значение действительно исчезает после истечения TTL. При этом тесты, зависящие от точного времени, необходимо проектировать осторожно.

54. Как тестировать фоновые задачи

Фоновые задачи могут выполняться после завершения HTTP-запроса. Например: POST /projects | v Создание проекта | v Очередь задач | v Отправка уведомления Тестирование фоновых задач Python требует проверки нескольких аспектов:

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

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


55. Как организовать тесты в монорепозитории

Представим структуру: project/ ├── frontend/ ├── backend/ ├── tests/ │ └── e2e/ └── docker-compose.yml Структура тестов Full Stack монорепозитория может выглядеть так: project/ ├── frontend/ │ ├── src/ │ │ ├── components/ │ │ │ └── Button.test.tsx │ │ └── services/ │ │ └── pricing.test.ts │ └── package.json │ ├── backend/ │ ├── app/ │ └── tests/ │ ├── unit/ │ └── integration/ │ ├── tests/ │ └── e2e/

│ ├── auth.spec.ts │ └── projects.spec.ts │ └── docker-compose.yml Такой подход разделяет проверки по ответственности. Но не существует единственной правильной структуры. Важнее, чтобы разработчики могли быстро найти и запустить нужный набор тестов.

56. Как разделить быстрые и медленные тесты

Разделение Unit Integration E2E в CI помогает ускорить обратную связь. Например: Pull Request | +-- Lint | +-- Typecheck | +-- Unit Tests | +-- Integration Tests | +-- E2E Smoke Tests Быстрые проверки можно запускать на каждом Pull Request. Более тяжёлые сценарии — по выбранной стратегии: на Pull Request, перед релизом или по расписанию. Но критические проверки не стоит откладывать только ради скорости Pipeline.


57. Как запустить тесты в GitHub Actions

GitHub Actions тестирование React FastAPI позволяет автоматически проверять изменения. Пример Workflow: name: Full Stack Tests on: pull_request: push: branches: - main permissions: contents: read jobs: frontend: runs-on: ubuntu-latest defaults: run: working-directory: frontend steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "22" cache: npm

cache-dependency-path: frontend/package-lock.json - run: npm ci - run: npm run test:run - run: npm run build backend: runs-on: ubuntu-latest defaults: run: working-directory: backend steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install -r requirements-dev.txt - run: pytest tests/unit Этот пример предполагает, что:

  • в frontend есть package-lock.json;
  • команда test:run определена;
  • Backend-зависимости перечислены в requirements-dev.txt;
  • Unit Tests не требуют запущенной PostgreSQL.

В реальном проекте версии Actions и зависимостей следует регулярно обновлять, а для повышенной защиты цепочки поставки — фиксировать Actions по полным Commit SHA.

58. Как запускать PostgreSQL в GitHub Actions

GitHub Actions поддерживает сервисные контейнеры. GitHub Actions PostgreSQL Service позволяет запускать интеграционные тесты с настоящей базой. Пример отдельного Job: integration: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_DB: app_test POSTGRES_USER: test_user POSTGRES_PASSWORD: test_password ports: - 5432:5432 options: >- --health-cmd "pg_isready -U test_user -d app_test"

--health-interval 5s --health-timeout 5s --health-retries 10 env: DATABASE_URL: postgresql+psycopg://test_user:test_password@localhost:5432/app_test steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - run: pip install -r backend/requirements-dev.txt - run: alembic upgrade head working-directory: backend - run: pytest tests/integration working-directory: backend

Пример предполагает использование синхронного драйвера psycopg и соответствующую конфигурацию Alembic. Для асинхронного приложения строка подключения и тестовые Fixtures могут отличаться. Тестовые учётные данные здесь не являются Production-секретами.

59. Как запускать Playwright в CI/CD

Playwright GitHub Actions CI позволяет проверять пользовательские сценарии автоматически. Пример шагов: - uses: actions/setup-node@v4 with: node-version: "22" - run: npm ci working-directory: frontend - run: npx playwright install --with-deps chromium working-directory: frontend - run: npx playwright test working-directory: frontend

Этот фрагмент предполагает, что Playwright настроен и приложение доступно через webServer либо запущено отдельным шагом. Если E2E требует FastAPI и PostgreSQL, их необходимо запустить до тестов. Иначе проверки будут падать не из-за ошибок интерфейса, а из-за отсутствия тестовой инфраструктуры.

60. Как сохранять отчёты тестирования

Когда тест падает в CI, важно иметь возможность понять причину. Playwright HTML Report CI помогает анализировать ошибки браузерных сценариев. Playwright может сохранять:

  • скриншоты;
  • Trace;
  • видео;
  • HTML-отчёты.

В GitHub Actions отчёты можно публиковать как Artifacts. Например: - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: frontend/playwright-report/ retention-days: 7 Важно не включать в отчёты чувствительные данные пользователей и Production-секреты.


61. Как тестировать перед деплоем

Тестирование перед Production деплоем должно быть частью процесса релиза. Один из возможных Pipeline: Pull Request | v Lint + Typecheck | v Unit Tests | v Integration Tests | v Build | v E2E Smoke Tests | v Security Checks | v Release | v Production Smoke Tests Важно понимать: успешные тесты не гарантируют отсутствия ошибок. Но они существенно уменьшают вероятность повторного появления известных проблем.

62. Что такое Smoke Testing

Smoke Tests — небольшой набор проверок, подтверждающих, что основные функции приложения доступны. Smoke Testing после деплоя может включать:

  • открывается ли главная страница;
  • отвечает ли Health Endpoint;
  • доступна ли авторизация;
  • загружается ли Dashboard;
  • выполняется ли основной API-запрос.

Например:

import httpx

def test_health(): response = httpx.get( "https://staging.example.com/health", timeout=10 ) assert response.status_code == 200 Для таких проверок лучше использовать Staging или специально подготовленные Production-safe сценарии. Нельзя создавать реальные платежи или изменять пользовательские данные без необходимости.

63. Что такое Regression Testing

Regression Testing проверяет, что ранее работавшая функциональность не сломалась после изменений. Регрессионное тестирование Full Stack особенно важно при:

  • обновлении зависимостей;
  • рефакторинге;
  • изменении API;
  • миграциях базы;
  • изменении авторизации;
  • оптимизации производительности.

Например, если команда исправила ошибку расчёта годовой подписки, полезно добавить тест, воспроизводящий исходную проблему. Тогда будущие изменения с меньшей вероятностью вернут тот же дефект.


64. Как бороться с медленными тестами

По мере роста проекта Pipeline может занимать десятки минут. Оптимизация скорости тестов CI/CD включает:

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

Но ускорение не должно нарушать изоляцию тестов. Например, параллельные проверки, изменяющие одни и те же записи PostgreSQL, могут стать нестабильными.

65. Как тестировать конкурентные сценарии

Обычные тесты часто проверяют последовательные действия. Но реальные пользователи работают одновременно. Тестирование Race Conditions Backend помогает обнаруживать ошибки конкурентного доступа. Например: User A читает остаток: 1 User B читает остаток: 1 User A покупает товар User B покупает товар Если Backend не использует правильные механизмы синхронизации, оба запроса могут успешно списать один и тот же остаток.

Для таких сценариев нужны интеграционные проверки с настоящей базой данных и конкурентными запросами. Unit Tests с Mock-репозиторием могут не обнаружить проблему.

66. Как тестировать производительность отдельно от функциональности

Функциональные тесты отвечают на вопрос: правильно ли работает приложение? Нагрузочные тесты отвечают на другой вопрос: как приложение ведёт себя при определённой нагрузке? Нагрузочное тестирование Full Stack может использовать:

  • k6;
  • Locust;
  • Gatling;
  • специализированные инструменты мониторинга.

Например, можно проверить, как FastAPI работает при 100 одновременных пользователях. Но нагрузочные тесты необходимо выполнять в контролируемом окружении. Их нельзя без подготовки запускать против Production, поскольку они могут повлиять на реальных пользователей.


67. Типичные ошибки при тестировании Full Stack

Первая ошибка — писать только Unit Tests и игнорировать интеграции. Вторая — проверять всё исключительно через E2E. Третья — использовать Mock для каждой зависимости. Четвёртая — тестировать PostgreSQL-специфичную логику только на SQLite. Пятая — не изолировать тестовые данные. Шестая — использовать фиксированные задержки в браузерных тестах. Седьмая — проверять только успешные сценарии.

Восьмая — игнорировать авторизацию и права доступа. Девятая — считать высокий Coverage доказательством качества. Десятая — не запускать тесты в CI/CD. Одиннадцатая — хранить реальные секреты в тестовых Fixtures. Двенадцатая — запускать тесты против Production-базы. Тринадцатая — не проверять миграции. Четырнадцатая — не сохранять отчёты о падениях. Пятнадцатая — создавать тесты, зависящие от порядка выполнения.

Шестнадцатая — повторять один и тот же сценарий на всех уровнях без дополнительной пользы. Семнадцатая — считать любой Flaky Test незначительной проблемой. Восемнадцатая — не проверять негативные сценарии API. Девятнадцатая — игнорировать реальные пользовательские пути. Двадцатая — писать тесты исключительно ради метрики покрытия.

Эти ошибки автоматизированного тестирования могут сделать Pipeline дорогим, медленным и при этом недостаточно полезным.


68. Практическая стратегия тестирования React + FastAPI + PostgreSQL

Для типичного SaaS-приложения можно использовать следующую архитектуру: GitHub Actions | +--------------+--------------+ | | | v v v Frontend Backend E2E | | | v v v Vitest pytest Playwright | | | v v v React Testing FastAPI Browser Library TestClient | | | | v v v MSW PostgreSQL Full Stack Testcontainers Архитектура тестирования React FastAPI PostgreSQL может включать: Frontend Unit Tests

Проверяют расчёты, форматирование и вспомогательные функции. React Component Tests Проверяют отображение интерфейса и пользовательские действия. Backend Unit Tests Проверяют бизнес-правила без HTTP и базы данных. API Integration Tests Проверяют FastAPI, валидацию и HTTP-контракты. Database Integration Tests Проверяют PostgreSQL, транзакции и миграции. E2E Tests

Проверяют регистрацию, авторизацию и основные пользовательские сценарии. Smoke Tests Проверяют работоспособность приложения после деплоя. Такая система помогает обнаруживать ошибки на разных уровнях.

69. Чек-лист тестирования Full Stack перед Production

Чек-лист тестирования Full Stack Production:

  • Критическая бизнес-логика покрыта Unit Tests.
  • React-компоненты проверяются через пользовательское поведение.
  • Формы проверяются на корректные и некорректные данные.
  • API-тесты проверяют успешные и ошибочные ответы.
  • Права доступа проверяются на Backend.
  • Интеграционные тесты используют настоящую PostgreSQL там, где это необходимо.
  • Миграции Alembic выполняются в тестовом окружении.
  • Тестовые данные изолированы.
  • Основные пользовательские сценарии покрыты E2E.
  • E2E-тесты не используют произвольные задержки.
  • Проверяются ошибки внешних сервисов.
  • CI/CD запускает автоматические проверки.
  • Отчёты о падениях доступны разработчикам.
  • Тестовые секреты отделены от Production.
  • После деплоя выполняются безопасные Smoke Tests.

Не обязательно реализовывать все пункты в первый день проекта. Но для растущего Production-приложения такой список помогает систематически повышать надёжность.


FAQ

Unit Testing — проверка отдельных функций, классов или небольших компонентов в контролируемом окружении. Integration Testing — проверка взаимодействия нескольких частей системы, например FastAPI и PostgreSQL. End-to-End Testing — проверка пользовательского сценария через приложение от начала до конца. Unit Tests обычно проверяют небольшие части логики. E2E проверяют работу системы через пользовательские действия.

Оба инструмента подходят для JavaScript-тестирования. Vitest особенно удобен для проектов, использующих Vite. Она помогает проверять поведение React-компонентов через отображаемый интерфейс и пользовательские действия. pytest — популярный инструмент тестирования Python-приложений. Можно использовать pytest, FastAPI TestClient, HTTPX и интеграционные тесты с настоящими зависимостями.

Если приложение зависит от особенностей PostgreSQL, интеграционные тесты с настоящей базой особенно полезны. Testcontainers — инструменты для создания временных контейнеров зависимостей во время тестирования. Mock API — контролируемая тестовая замена реального API или его ответов. Mock Service Worker — библиотека для перехвата сетевых запросов и моделирования API-ответов.

Playwright — инструмент автоматизации браузеров, часто используемый для E2E-тестирования. Нет. E2E лучше использовать для важных пользовательских сценариев, а локальное поведение компонентов проверять на более быстрых уровнях. Это тест, результат которого нестабилен и может меняться без соответствующих изменений проверяемого поведения. Это метрика, показывающая, какая часть кода выполнялась во время тестов.

Не обязательно. Важнее качество проверок и покрытие критических сценариев. Это короткая проверка основных функций приложения, часто выполняемая после деплоя. Да. GitHub Actions позволяет запускать Frontend, Backend, Integration и E2E Tests. Авторизацию, права доступа, критические бизнес-операции, платежи, сохранение данных и основные пользовательские сценарии.


Заключение

Автоматическое тестирование — одна из ключевых составляющих надёжной Full Stack разработки. Но качественная система тестов не сводится к большому количеству файлов с расширением .test.ts или .py. Она должна проверять реальные риски приложения. Unit Tests помогают быстро обнаруживать ошибки бизнес-логики. React Testing Library позволяет проверять интерфейс через пользовательское поведение.

Vitest обеспечивает удобный запуск Frontend-тестов. pytest помогает тестировать Python и FastAPI. Интеграционные тесты с PostgreSQL обнаруживают проблемы, которые невозможно надёжно проверить с помощью одних Mock-объектов. Playwright проверяет критические сценарии через браузер. А GitHub Actions объединяет эти инструменты в автоматический процесс проверки изменений.

Важно помнить, что тесты не гарантируют абсолютного отсутствия ошибок. Они уменьшают риск, помогают обнаруживать регрессии и делают изменения более предсказуемыми. Хорошая стратегия начинается не с максимального Coverage, а с ответа на вопрос: Какая ошибка нанесёт продукту наибольший ущерб и как обнаружить её до релиза?

Именно этот подход помогает построить систему тестирования, которая действительно приносит пользу команде и пользователям.


Наверх