TL;DR
- Автоматизация тестирования запускает тесты автоматически — быстрый фидбэк, больше покрытие, меньше багов в продакшене
- Начни с пирамиды тестов: много unit, немного интеграционных, мало E2E
- Первый инструмент: Playwright (веб), Jest (JS), pytest (Python) — выбирай по стеку
- Автоматизируй регрессионные тесты первыми — стабильные фичи, которые ломаются при изменениях
- Не автоматизируй всё — фокусируйся на высокоценных, повторяемых тестах
Идеально для: Разработчиков, QA-инженеров, всех, кто хочет автоматизировать рутинное тестирование Пропусти, если: Тестируешь одноразовые скрипты или прототипы без поддержки Время чтения: 20 минут
Твоя команда релизит каждые две недели. Ручная регрессия занимает три дня. К моменту завершения тестирования разработчики уже переключились на новые фичи. Баги, найденные в регрессии, означают переключение контекста и задержки.
Автоматизация тестирования решает эту проблему. Тесты запускаются за минуты вместо дней. Разработчики получают фидбэк, пока код свежий в памяти. Регрессия запускается на каждом коммите, а не только перед релизом.
Этот туториал научит автоматизации тестирования с нуля — базовые концепции, выбор инструментов, написание первых тестов и построение поддерживаемых тестовых наборов.
Что такое автоматизация тестирования?
Автоматизация тестирования использует программное обеспечение для выполнения тестов, сравнения фактических результатов с ожидаемыми и отчётов об ошибках. Вместо человека, кликающего по приложению, это делает скрипт автоматически.
Ручное тестирование:
1. Открыть браузер
2. Перейти на страницу логина
3. Ввести имя "testuser"
4. Ввести пароль "secret123"
5. Нажать кнопку Login
6. Проверить, что появился дашборд
7. Проверить имя пользователя в header
Автоматизированный тест:
test('пользователь может залогиниться', async ({ page }) => {
await page.goto('/login');
await page.fill('#username', 'testuser');
await page.fill('#password', 'secret123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('.header-username')).toHaveText('testuser');
});
Оба тестируют одно и то же. Автоматизированная версия запускается за секунды, каждый раз при изменении кода, без участия человека.
Зачем автоматизировать тесты?
Скорость: Ручной регрессионный набор на 3 дня запускается за 30 минут автоматизированно.
Консистентность: Автоматизированные тесты выполняют одни и те же шаги каждый раз. Люди пропускают шаги, неправильно читают данные, забывают edge cases.
Покрытие: Ты можешь запускать тысячи тестов ночью. Вручную ты бы протестировал happy path и на этом закончил.
Ранний фидбэк: Тесты запускаются на каждом коммите. Баги, пойманные в разработке, стоят в 10 раз меньше, чем баги в продакшене.
Уверенность разработчиков: Хорошее покрытие тестами означает, что разработчики рефакторят без страха. Изменил код, запустил тесты, знаешь, сломалось ли что-то.
Когда НЕ автоматизировать:
- Исследовательское тестирование — люди находят неожиданные баги
- Одноразовые тесты — затраты на автоматизацию превышают выгоду
- Быстро меняющиеся фичи — тесты ломаются быстрее, чем приносят пользу
- Визуальная/UX оценка — люди лучше оценивают эстетику
Пирамида автоматизации тестирования
Пирамида направляет, сколько тестов писать на каждом уровне.
/\
/ \ E2E тесты (мало)
/----\ UI, полные workflow
/ \
/--------\ Интеграционные тесты (немного)
/ \ API, компоненты вместе
/------------\
/ \ Unit тесты (много)
/________________\ Функции, классы
Unit тесты (Основа)
Тестируют отдельные функции или классы изолированно.
# calculator.py
def add(a, b):
return a + b
# test_calculator.py
def test_add_positive_numbers():
assert add(2, 3) == 5
def test_add_negative_numbers():
assert add(-1, -1) == -2
Характеристики:
- Быстрые (миллисекунды)
- Изолированные (без БД, сети, файловой системы)
- Много (сотни до тысяч)
- Запускаются на каждом коммите
Интеграционные тесты (Середина)
Тестируют, как компоненты работают вместе.
def test_create_user_saves_to_database():
# Использует реальное подключение к БД
user_service = UserService(db_connection)
user = user_service.create_user("john@example.com")
saved_user = db_connection.query(f"SELECT * FROM users WHERE id={user.id}")
assert saved_user.email == "john@example.com"
Характеристики:
- Медленнее (секунды)
- Тестируют реальные взаимодействия
- Среднее количество (десятки до сотен)
- Запускаются на PR/merge
E2E тесты (Верх)
Тестируют полные пользовательские сценарии через UI.
test('пользователь может завершить покупку', async ({ page }) => {
await page.goto('/products');
await page.click('[data-product="laptop"]');
await page.click('button:has-text("Добавить в корзину")');
await page.click('a:has-text("Оформить заказ")');
await page.fill('#card-number', '4111111111111111');
await page.click('button:has-text("Оплатить")');
await expect(page.locator('.confirmation')).toContainText('Заказ подтверждён');
});
Характеристики:
- Самые медленные (минуты)
- Тестируют реальные пользовательские сценарии
- Мало (десятки до низких сотен)
- Запускаются ночью или перед релизом
Выбор первого инструмента
Выбирай по своему технологическому стеку:
| Стек | Unit тесты | Интеграция | E2E |
|---|---|---|---|
| JavaScript/Node | Jest | Jest + Supertest | Playwright/Cypress |
| Python | pytest | pytest | Playwright/pytest |
| Java | JUnit 5 | TestNG | Selenium |
| React | Jest + RTL | Jest + MSW | Playwright |
| Mobile | XCTest/JUnit | XCTest/Espresso | Appium |
Для веб-тестирования: Playwright
Современный, надёжный, отличный developer experience.
# Установка
npm init playwright@latest
// tests/example.spec.js
const { test, expect } = require('@playwright/test');
test('главная страница имеет заголовок', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
# Запуск тестов
npx playwright test
Для JavaScript unit тестов: Jest
Самый популярный JavaScript фреймворк для тестирования.
npm install --save-dev jest
// math.js
function multiply(a, b) {
return a * b;
}
module.exports = { multiply };
// math.test.js
const { multiply } = require('./math');
test('умножает два числа', () => {
expect(multiply(3, 4)).toBe(12);
});
npm test
Для Python: pytest
Де-факто стандарт для тестирования Python.
pip install pytest
# test_example.py
def test_string_uppercase():
assert "hello".upper() == "HELLO"
def test_list_contains():
fruits = ["apple", "banana", "cherry"]
assert "banana" in fruits
pytest
Написание первого автоматизированного теста
Построим полный пример с нуля.
Шаг 1: Настройка простого приложения
// app.js - Express приложение
const express = require('express');
const app = express();
app.use(express.json());
let todos = [];
app.get('/todos', (req, res) => {
res.json(todos);
});
app.post('/todos', (req, res) => {
const todo = {
id: Date.now(),
text: req.body.text,
completed: false
};
todos.push(todo);
res.status(201).json(todo);
});
app.put('/todos/:id', (req, res) => {
const todo = todos.find(t => t.id === parseInt(req.params.id));
if (!todo) return res.status(404).json({ error: 'Not found' });
todo.completed = req.body.completed;
res.json(todo);
});
module.exports = app;
Шаг 2: Написание unit тестов
// tests/todo.test.js
const request = require('supertest');
const app = require('../app');
describe('Todo API', () => {
beforeEach(() => {
// Сброс todos перед каждым тестом
app.locals.todos = [];
});
test('GET /todos возвращает пустой список изначально', async () => {
const response = await request(app).get('/todos');
expect(response.status).toBe(200);
expect(response.body).toEqual([]);
});
test('POST /todos создаёт новую задачу', async () => {
const response = await request(app)
.post('/todos')
.send({ text: 'Купить продукты' });
expect(response.status).toBe(201);
expect(response.body.text).toBe('Купить продукты');
expect(response.body.completed).toBe(false);
expect(response.body.id).toBeDefined();
});
test('PUT /todos/:id обновляет статус выполнения', async () => {
// Сначала создаём задачу
const createResponse = await request(app)
.post('/todos')
.send({ text: 'Тестовая задача' });
const todoId = createResponse.body.id;
// Обновляем её
const updateResponse = await request(app)
.put(`/todos/${todoId}`)
.send({ completed: true });
expect(updateResponse.status).toBe(200);
expect(updateResponse.body.completed).toBe(true);
});
test('PUT /todos/:id возвращает 404 для несуществующей задачи', async () => {
const response = await request(app)
.put('/todos/99999')
.send({ completed: true });
expect(response.status).toBe(404);
});
});
Шаг 3: Запуск и проверка
npm test
# Вывод:
# PASS tests/todo.test.js
# Todo API
# ✓ GET /todos возвращает пустой список изначально (15ms)
# ✓ POST /todos создаёт новую задачу (8ms)
# ✓ PUT /todos/:id обновляет статус выполнения (5ms)
# ✓ PUT /todos/:id возвращает 404 для несуществующей задачи (3ms)
#
# Tests: 4 passed
Построение тестового набора
Одного файла с тестами недостаточно. Реальным проектам нужны организованные тестовые наборы.
Структура проекта
project/
├── src/
│ ├── services/
│ │ ├── user.service.js
│ │ └── order.service.js
│ └── utils/
│ └── validation.js
├── tests/
│ ├── unit/
│ │ ├── services/
│ │ │ ├── user.service.test.js
│ │ │ └── order.service.test.js
│ │ └── utils/
│ │ └── validation.test.js
│ ├── integration/
│ │ └── api.test.js
│ └── e2e/
│ └── checkout.spec.js
├── jest.config.js
└── playwright.config.js
Общая настройка (Fixtures)
// tests/fixtures/users.js
const testUsers = {
admin: {
email: 'admin@example.com',
role: 'admin',
token: 'admin-token-123'
},
regular: {
email: 'user@example.com',
role: 'user',
token: 'user-token-456'
}
};
module.exports = { testUsers };
// tests/integration/admin.test.js
const { testUsers } = require('../fixtures/users');
test('админ может получить доступ к админ-панели', async () => {
const response = await request(app)
.get('/admin')
.set('Authorization', `Bearer ${testUsers.admin.token}`);
expect(response.status).toBe(200);
});
Билдеры тестовых данных
// tests/builders/user.builder.js
class UserBuilder {
constructor() {
this.user = {
email: 'default@example.com',
name: 'Default User',
role: 'user'
};
}
withEmail(email) {
this.user.email = email;
return this;
}
withRole(role) {
this.user.role = role;
return this;
}
asAdmin() {
this.user.role = 'admin';
return this;
}
build() {
return { ...this.user };
}
}
// Использование
const admin = new UserBuilder().asAdmin().withEmail('boss@company.com').build();
Интеграция с CI/CD
Автоматизированные тесты должны запускаться автоматически при каждом изменении кода.
Пример GitHub Actions
# .github/workflows/test.yml
name: Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test
- name: Run E2E tests
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results/
Лучшие практики
1. Тестируй поведение, не реализацию
// Плохо - тестирует детали реализации
test('добавляет элемент во внутренний массив', () => {
cart.addItem(product);
expect(cart._items).toContain(product); // Доступ к приватному состоянию
});
// Хорошо - тестирует поведение
test('добавленный элемент появляется в корзине', () => {
cart.addItem(product);
expect(cart.getItems()).toContain(product);
});
2. Один assertion на тест (идеально)
// Плохо - несколько несвязанных assertions
test('создание пользователя', () => {
const user = createUser('john@example.com');
expect(user.email).toBe('john@example.com');
expect(user.createdAt).toBeDefined();
expect(user.id).toMatch(/^usr_/);
expect(user.verified).toBe(false);
});
// Хорошо - сфокусированные тесты
test('новый пользователь имеет указанный email', () => {
const user = createUser('john@example.com');
expect(user.email).toBe('john@example.com');
});
test('новый пользователь не верифицирован по умолчанию', () => {
const user = createUser('john@example.com');
expect(user.verified).toBe(false);
});
3. Делай тесты независимыми
// Плохо - тесты зависят от порядка выполнения
let user;
test('создаёт пользователя', () => {
user = createUser('test@example.com');
expect(user).toBeDefined();
});
test('получает созданного пользователя', () => {
const fetched = getUser(user.id); // Падает, если первый тест не выполнился
expect(fetched.email).toBe('test@example.com');
});
// Хорошо - каждый тест настраивает свои данные
test('получает пользователя по id', () => {
const user = createUser('test@example.com');
const fetched = getUser(user.id);
expect(fetched.email).toBe('test@example.com');
});
4. Используй описательные имена
// Плохо
test('test1', () => { ... });
test('login', () => { ... });
// Хорошо
test('пользователь с валидными credentials может залогиниться', () => { ... });
test('логин не проходит с неправильным паролем', () => { ... });
test('заблокированный аккаунт не может залогиниться даже с правильным паролем', () => { ... });
Частые ошибки, которых стоит избегать
1. Flaky тесты
Тесты, которые иногда проходят, иногда падают.
Причина: Race conditions, зависимость от тайминга, разделяемое состояние.
// Flaky - зависит от тайминга
test('показывает уведомление', async () => {
triggerNotification();
await sleep(100); // Может быть недостаточно
expect(screen.getByText('Успех')).toBeVisible();
});
// Лучше - ждём элемент
test('показывает уведомление', async () => {
triggerNotification();
await waitFor(() => {
expect(screen.getByText('Успех')).toBeVisible();
});
});
2. Тестирование стороннего кода
// Плохо - тестирует lodash
test('lodash правильно сортирует', () => {
expect(_.sortBy([3, 1, 2])).toEqual([1, 2, 3]);
});
// Хорошо - тестируй СВОЙ код, который использует lodash
test('getTopUsers возвращает пользователей отсортированных по score', () => {
const users = [
{ name: 'Alice', score: 50 },
{ name: 'Bob', score: 100 }
];
expect(getTopUsers(users)[0].name).toBe('Bob');
});
3. Чрезмерное мокирование
// Слишком замокано - не тестирует ничего реального
test('processOrder', () => {
mockDatabase.save.mockResolvedValue(true);
mockPayment.charge.mockResolvedValue(true);
mockEmail.send.mockResolvedValue(true);
mockInventory.update.mockResolvedValue(true);
// Что мы вообще тестируем?
const result = processOrder(order);
expect(result.success).toBe(true);
});
Автоматизация тестирования с помощью ИИ
ИИ-инструменты могут ускорить разработку тестов при правильном использовании.
Что ИИ делает хорошо:
- Генерирует тест-кейсы из сигнатур функций
- Создаёт вариации тестовых данных
- Пишет boilerplate для типовых паттернов
- Предлагает edge cases, которые ты мог упустить
Что всё ещё требует людей:
- Решение, что тестировать
- Проверка, что тесты тестируют правильное
- Проектирование архитектуры тестов
- Отладка flaky тестов
Полезный промпт:
Напиши Jest тесты для этой функции. Включи тесты для: валидных входных данных, невалидных входных данных, edge cases (пустые, null, граничные значения) и обработки ошибок. Используй описательные имена тестов, объясняющие ожидаемое поведение.
FAQ
Что такое автоматизация тестирования?
Автоматизация тестирования использует программное обеспечение для автоматического выполнения тестов вместо ручного. Скрипты симулируют действия пользователя, вызывают API или функции, затем проверяют, что результаты соответствуют ожиданиям. Это даёт более быстрый фидбэк, более консистентное выполнение и позволяет запускать тысячи тестов, что было бы непрактично вручную.
Какой инструмент лучше для начинающих в автоматизации тестирования?
Для веб-тестирования Playwright или Cypress предлагают современные API, отличную документацию и быстрый старт. Для Python-проектов pytest — стандартный выбор. Для JavaScript/Node.js доминирует Jest. Лучший инструмент зависит от твоего текущего технологического стека — изучай то, что использует твоя команда или что соответствует языку твоего приложения.
Сколько времени нужно, чтобы научиться автоматизации тестирования?
Базовая автоматизация тестирования — написание простых работающих тестов — занимает 2-4 недели сфокусированной практики. Написание поддерживаемых, хорошо структурированных тестовых наборов обычно занимает 3-6 месяцев реального опыта проекта. Мастерство в интеграции CI/CD, архитектуре тестов и работе со сложными сценариями занимает 1-2 года профессиональной практики.
Учить сначала Selenium или Playwright?
Для новых проектов изучай Playwright — у него более современный API, встроенные авто-ожидания, лучшие assertions и отличный developer experience. Изучай Selenium, если приходишь в команду с существующими Selenium-тестами или нужна поддержка старых браузеров. Оба навыка ценны; с Playwright легче начать.
Официальные ресурсы
Смотрите также
- Selenium Tutorial для начинающих - Начало работы с Selenium WebDriver
- Playwright Tutorial - Современное веб-тестирование с Playwright
- Jest Testing Tutorial - Unit-тестирование JavaScript с Jest
- pytest Tutorial - Основы тестирования Python
- Стратегия пирамиды тестирования - Эффективный баланс типов тестов
- Software Testing Tutorial - Основы тестирования для начинающих
