← Назад к вопросам

В чем разница между скриншотными и модульными тестами?

2.3 Middle🔥 172 комментариев
#Тестирование

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разница между скриншотными (E2E) и модульными (Unit) тестами

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

1. Что они тестируют

Модульные тесты (Unit tests): Тестируют отдельные функции, компоненты, методы в изоляции.

// calculateTotal.js
export function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// calculateTotal.test.js
import { calculateTotal } from './calculateTotal';

describe('calculateTotal', () => {
  it('should sum prices of items', () => {
    const items = [
      { name: 'Book', price: 10 },
      { name: 'Pen', price: 5 }
    ];
    expect(calculateTotal(items)).toBe(15);
  });
  
  it('should return 0 for empty array', () => {
    expect(calculateTotal([])).toBe(0);
  });
  
  it('should handle negative prices', () => {
    const items = [{ price: 10 }, { price: -3 }];
    expect(calculateTotal(items)).toBe(7);
  });
});

E2E / Скриншотные тесты (End-to-End): Тестируют полную работу приложения с точки зрения пользователя через UI.

// user-checkout.e2e.test.js
import { test, expect } from '@playwright/test';

test('User can add item to cart and checkout', async ({ page }) => {
  // 1. Пользователь открывает сайт
  await page.goto('https://shop.example.com');
  
  // 2. Видит товары
  await expect(page.locator('h1')).toContainText('Products');
  
  // 3. Нажимает "Add to cart"
  await page.click('button:has-text("Add to cart")');
  
  // 4. Видит товар в корзине
  await expect(page.locator('.cart-count')).toContainText('1');
  
  // 5. Переходит в корзину
  await page.click('.cart-icon');
  
  // 6. Нажимает "Checkout"
  await page.click('button:has-text("Checkout")');
  
  // 7. Заполняет форму
  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="address"]', '123 Main St');
  
  // 8. Нажимает "Place Order"
  await page.click('button:has-text("Place Order")');
  
  // 9. Видит подтверждение
  await expect(page.locator('.success-message')).toContainText('Order placed!');
});

2. Область охвата (Coverage)

Модульные тесты: узкая область

Тестируем одну функцию:

calculateTotal() ← ТЕСТ ЗДЕСЬ
  ↓
(остаток приложения не тестируется)
// Тестируем ТОЛЬКО логику calculateTotal
// Не тестируем:
// - Интеграцию с API
// - DOM рендеринг
// - Пользовательское взаимодействие
// - Сетевые запросы
// - Переходы между страницами

E2E тесты: широкая область

Тестируем весь путь пользователя:

1. Открыть сайт
2. Найти товар
3. Добавить в корзину          ← ПОЛНАЯ ЦЕПЬ
4. Заполнить форму
5. Отправить платеж
6. Получить подтверждение

3. Скорость выполнения

Модульные тесты: очень быстрые

$ npm run test

✓ calculateTotal (5ms)
✓ formatPrice (3ms)
✓ validateEmail (2ms)
✓ parseDate (4ms)
✓ Button component renders (10ms)
✓ Input component validates (8ms)

Tests: 6 passed
Time: 45ms (0.045 seconds)

E2E тесты: медленные

$ npm run test:e2e

✓ User can add item to cart and checkout (8.5s)
  - открыть страницу: 1.2s
  - найти товар: 0.3s
  - нажать кнопку: 0.2s
  - дождаться обновления: 1.5s
  - заполнить форму: 2.1s
  - отправить: 1.8s
  - дождаться подтверждения: 1.4s

✓ User can apply discount code (5.2s)
✓ Payment processing works (9.8s)

Tests: 3 passed
Time: 24 seconds

4. Сложность написания

Модульные тесты: простые

// Просто: нужно вызвать функцию и проверить результат
test('User.getAge() returns correct age', () => {
  const user = new User('Alice', 1990);
  expect(user.getAge()).toBe(34);
});

E2E тесты: сложные

// Сложно: нужно учитывать множество переменных
test('Payment workflow', async ({ page }) => {
  // 1. Выбрать условия доставки?
  // 2. Есть ли скидки?
  // 3. Какой браузер?
  // 4. Какой экран (мобильный/десктоп)?
  // 5. Как долго загружается сеть?
  // 6. Как долго работает сервер?
  // ... много переменных ...
});

5. Надежность

Модульные тесты: очень надежные

// Функция либо работает, либо нет
function add(a, b) { return a + b; }

test('add returns sum', () => {
  expect(add(2, 3)).toBe(5);  // ВСЕГДА true или false
});

// Нет flaky тестов (случайных падений)

E2E тесты: подвержены flakiness (нестабильности)

test('Cart updates', async ({ page }) => {
  await page.click('.add-to-cart');
  
  // МОЖЕТ БЫТЬ НЕСТАБИЛЬНО:
  // 1. Сеть медленная → клик не сработал
  // 2. Элемент еще не загружен → не найден
  // 3. JavaScript выполнился медленнее → состояние не обновилось
  // 4. Server медленный → ответ не пришел
  
  // Поэтому нужны ожидания (waits):
  await page.waitForSelector('.cart-count', { state: 'visible' });
  await expect(page.locator('.cart-count')).toContainText('1');
});

// 1 из 100 запусков может упасть (flaky test)

6. Отладка при ошибке

Модульные тесты: просто отлаживаются

test('calculateTotal', () => {
  const result = calculateTotal([{ price: 10 }, { price: 5 }]);
  
  // Ошибка: expected 15 but got 10
  // Просто: прочитаем функцию и найдем баг
  expect(result).toBe(15);
});

// Стек ошибки четкий и понятный

E2E тесты: сложно отлаживаются

test('User checkout', async ({ page }) => {
  // Ошибка: "Expected to find element with text 'Order placed' but found nothing"
  // Откуда ошибка?
  // 1. API вернул ошибку?
  // 2. DOM не обновился?
  // 3. JavaScript выпал?
  // 4. Сеть упала?
  // 5. Условие неправильно?
  
  // РЕШЕНИЕ: используй screenshot и video
  await page.screenshot({ path: 'failure.png' });
});

Поэтому Playwright записывает видео и скриншоты при ошибке.

7. Покрытие примеров

Модульные тесты: много примеров нужно

function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

// Нужны тесты для ВСЕХ edge cases:
test('positive numbers', () => {
  expect(calculateTotal([{ price: 10 }])).toBe(10);
});

test('negative numbers', () => {
  expect(calculateTotal([{ price: -5 }])).toBe(-5);
});

test('zero', () => {
  expect(calculateTotal([{ price: 0 }])).toBe(0);
});

test('empty array', () => {
  expect(calculateTotal([])).toBe(0);
});

test('decimals', () => {
  expect(calculateTotal([{ price: 10.5 }, { price: 0.5 }])).toBe(11);
});

test('large numbers', () => {
  expect(calculateTotal([{ price: 999999 }])).toBe(999999);
});

// 6+ тестов для одной функции

E2E тесты: меньше тестов, но более ценные

// 3-4 теста охватывают основные пути

test('Happy path: user successfully checks out', async ({ page }) => {
  // Самый важный сценарий
});

test('User applies discount code', async ({ page }) => {
  // Вариант с бизнес-логикой
});

test('Payment fails, user sees error', async ({ page }) => {
  // Обработка ошибок
});

8. Тестовая пирамида

        △
       /│\              E2E тесты
      / │ \             (мало, дорогие, медленные)
     /  │  \
    /   │   \
   /────┼────\        Integration тесты
  /     │     \       (среднее количество, среднесложные)
 /      │      \
/───────┼───────\    Unit тесты
        │         (много, простые, быстрые)
        │
      BASE

Пирамида: 70% Unit, 20% Integration, 10% E2E

9. Когда использовать

Модульные тесты:

// ✅ Бизнес-логика
function applyTax(price) {
  return price * 1.2;  // Нужен unit test
}

// ✅ Утилиты
function formatDate(date) {
  return date.toLocaleDateString();  // Нужен unit test
}

// ✅ Компоненты с логикой
function useAuth() {
  const [user, setUser] = useState(null);
  // ...
  return user;  // Нужен unit test
}

// ✅ Парсеры, валидаторы
function validateEmail(email) {
  return email.includes('@');  // Нужен unit test
}

E2E тесты:

// ✅ Критические пути (покупка, вход, платеж)
test('User purchases item', async () => {
  // ОБЯЗАТЕЛЬНО E2E
});

// ✅ Интеграция компонентов
test('Header, navigation, и footer работают вместе', async () => {
  // НУЖНО E2E
});

// ✅ Работа с формами
test('User fills and submits form', async () => {
  // ОБЯЗАТЕЛЬНО E2E
});

// ✅ API интеграция
test('App gets data from API and shows it', async () => {
  // НУЖНО E2E (или integration)
});

10. Практический пример

// МОДУЛЬНЫЙ ТЕСТ
function UserService {
  getAge(birthYear) {
    return new Date().getFullYear() - birthYear;
  }
}

test('getAge calculates age correctly', () => {
  const service = new UserService();
  expect(service.getAge(1990)).toBe(34);
});

// E2E ТЕСТ
test('User can view their age in profile', async ({ page }) => {
  // 1. Логин
  await page.goto('/login');
  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="password"]', 'password');
  await page.click('button[type="submit"]');
  
  // 2. Перейти в профиль
  await page.goto('/profile');
  
  // 3. Увидеть возраст
  await expect(page.locator('[data-age]')).toContainText('34');
});

Итог

Модульные тесты тестируют отдельные функции и компоненты в изоляции — они быстрые (миллисекунды), легкие для написания и очень надежные. E2E тесты тестируют полный путь пользователя через UI — они медленные (секунды), сложные для написания, но проверяют реальное поведение приложения. Используйте тестовую пирамиду: большую часть unit тестов, меньше integration тестов, и самое меньшее количество E2E тестов для критических путей. Вместе они обеспечивают надежное покрытие.