В чем разница между скриншотными и модульными тестами?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между скриншотными (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 тестов для критических путей. Вместе они обеспечивают надежное покрытие.