Зачем тестировщикам навыки программирования
Автоматизация тестирования означает написание кода. Вам не нужно становиться разработчиком, но необходимо достаточно знаний для написания, чтения и поддержки тестовых скриптов. Этот урок покрывает основные концепции программирования для каждого QA automation engineer.
Мы используем JavaScript для примеров, так как это самый распространённый язык в современной автоматизации (Playwright, Cypress, WebdriverIO). Концепции применимы к любому языку.
Переменные и типы данных
Переменные хранят данные, которые используют ваши тесты — учётные данные, URL, ожидаемые значения, селекторы элементов.
Объявление переменных
// Используйте const для неизменяемых значений
const BASE_URL = 'https://app.example.com';
const TIMEOUT = 30000;
// Используйте let для изменяемых значений
let loginAttempts = 0;
let currentUser = null;
Типы данных
| Тип | Пример | Применение в тестировании |
|---|---|---|
| String | 'hello', "world" | URL, селекторы, ожидаемый текст |
| Number | 42, 3.14 | Таймауты, счётчики, цены |
| Boolean | true, false | Feature-флаги, условия |
| Null | null | Пустые/отсутствующие значения |
| Undefined | undefined | Неинициализированные переменные |
| Array | [1, 2, 3] | Списки тестовых данных |
| Object | {name: 'John'} | Структурированные данные |
Строковые операции для тестирования
// Частые строковые операции в тестах
const pageTitle = 'Welcome to Dashboard - MyApp';
// Проверка на содержание ожидаемого текста
pageTitle.includes('Dashboard'); // true
// Извлечение частей строк
pageTitle.toLowerCase(); // 'welcome to dashboard - myapp'
pageTitle.trim(); // Удаление пробелов
// Шаблонные строки для динамических значений
const url = `${BASE_URL}/users/${userId}/profile`;
const errorMsg = `Ожидалось ${expected}, но получено ${actual}`;
Условные конструкции
Условия позволяют тестам принимать решения на основе условий.
// Базовый if-else
if (userRole === 'admin') {
await page.click('#admin-panel');
} else if (userRole === 'editor') {
await page.click('#editor-tools');
} else {
await page.click('#user-dashboard');
}
// Тернарный оператор для простых условий
const timeout = isCI ? 60000 : 30000;
// Проверка нескольких условий
if (statusCode >= 200 && statusCode < 300) {
console.log('Запрос успешен');
} else if (statusCode === 404) {
console.log('Ресурс не найден');
} else {
console.log(`Неожиданный статус: ${statusCode}`);
}
Операторы сравнения
| Оператор | Значение | Пример |
|---|---|---|
=== | Строгое равенство | status === 200 |
!== | Не равно | role !== 'guest' |
>, < | Больше/меньше | count > 0 |
>=, <= | Больше/меньше или равно | price >= 9.99 |
&& | И | isVisible && isEnabled |
|| | ИЛИ | isAdmin || isSuperuser |
Циклы
Циклы повторяют действия — незаменимы для data-driven тестирования и пакетных операций.
Цикл for
// Тестирование логина с несколькими пользователями
const users = ['admin', 'editor', 'viewer'];
for (let i = 0; i < users.length; i++) {
console.log(`Тестируем пользователя: ${users[i]}`);
}
// Современный цикл for...of (предпочтительный)
for (const user of users) {
console.log(`Тестируем пользователя: ${user}`);
}
forEach и map
// Обработка каждого результата теста
const results = [
{ test: 'login', status: 'pass' },
{ test: 'checkout', status: 'fail' },
{ test: 'search', status: 'pass' }
];
// forEach - выполнить действие с каждым элементом
results.forEach(result => {
console.log(`${result.test}: ${result.status}`);
});
// map - трансформировать каждый элемент
const testNames = results.map(r => r.test);
// ['login', 'checkout', 'search']
// filter - оставить элементы по условию
const failures = results.filter(r => r.status === 'fail');
// [{ test: 'checkout', status: 'fail' }]
Функции
Функции — строительные блоки чистого тест-кода. Они инкапсулируют переиспользуемую логику.
Базовые функции
// Объявление функции
function calculateTotal(price, quantity, taxRate) {
const subtotal = price * quantity;
const tax = subtotal * taxRate;
return subtotal + tax;
}
// Arrow-функция (современный синтаксис)
const calculateTotal = (price, quantity, taxRate) => {
const subtotal = price * quantity;
const tax = subtotal * taxRate;
return subtotal + tax;
};
// Использование функции
const total = calculateTotal(29.99, 3, 0.08);
Функции в автоматизации
// Переиспользуемая функция логина
async function login(page, username, password) {
await page.goto('/login');
await page.fill('#username', username);
await page.fill('#password', password);
await page.click('#submit');
await page.waitForURL('/dashboard');
}
// Переиспользуемый хелпер проверки
function assertInRange(actual, min, max) {
if (actual < min || actual > max) {
throw new Error(`${actual} не в диапазоне [${min}, ${max}]`);
}
}
// Использование в тестах
await login(page, 'admin@test.com', 'password123');
assertInRange(responseTime, 0, 3000);
Коллекции: Массивы и объекты
Массивы
// Тестовые данные как массивы
const validEmails = ['user@test.com', 'admin@company.org', 'test+tag@gmail.com'];
const invalidEmails = ['', 'not-an-email', '@missing.com', 'no-domain@'];
// Полезные методы массивов
validEmails.length; // 3
validEmails.includes('user@test.com'); // true
validEmails.push('new@test.com'); // Добавить элемент
validEmails.indexOf('admin@company.org'); // 1
Объекты
// Тестовый пользователь как объект
const testUser = {
name: 'Jane Smith',
email: 'jane@test.com',
role: 'admin',
permissions: ['read', 'write', 'delete']
};
// Доступ к свойствам
console.log(testUser.name); // 'Jane Smith'
console.log(testUser['email']); // 'jane@test.com'
// Селекторы как объект
const selectors = {
loginForm: '#login-form',
emailInput: '[data-testid="email"]',
passwordInput: '[data-testid="password"]',
submitButton: 'button[type="submit"]'
};
Async/Await
Автоматизация тестирования активно работает с асинхронностью — ожидание загрузки страниц, появления элементов, ответов API. Понимание async/await обязательно.
Проблема
// Без async/await - вложенные callbacks становятся нечитаемыми
page.goto('/login', function() {
page.fill('#email', 'test@test.com', function() {
page.click('#submit', function() {
// Это "callback hell"
});
});
});
Решение: Async/Await
// С async/await - чисто и читаемо
async function testLogin(page) {
await page.goto('/login');
await page.fill('#email', 'test@test.com');
await page.fill('#password', 'secret123');
await page.click('#submit');
await page.waitForSelector('.dashboard');
}
Ключевое слово await приостанавливает выполнение до завершения асинхронной операции. Ключевое слово async помечает функцию, содержащую вызовы await.
Распространённые асинхронные паттерны в тестировании
// Ожидание нескольких событий
const [response] = await Promise.all([
page.waitForResponse('/api/users'),
page.click('#load-users')
]);
// Ожидание с таймаутом
await page.waitForSelector('.modal', { timeout: 5000 });
// Паттерн повторных попыток
async function waitForElement(page, selector, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
await page.waitForSelector(selector, { timeout: 2000 });
return true;
} catch {
console.log(`Попытка ${i + 1}/${retries}`);
}
}
return false;
}
Обработка ошибок
Тесты падают. Хорошая обработка ошибок гарантирует понятные сообщения о сбоях и обязательную очистку.
Try-Catch
async function testCheckout(page) {
try {
await page.goto('/checkout');
await page.fill('#card', '4242424242424242');
await page.click('#pay');
await expect(page.locator('.success')).toBeVisible();
} catch (error) {
console.error(`Тест checkout упал: ${error.message}`);
await page.screenshot({ path: 'checkout-failure.png' });
throw error; // Пробросить для пометки теста как упавшего
}
}
Блок Finally
async function testWithCleanup(page) {
let createdUserId;
try {
createdUserId = await createTestUser();
await page.goto(`/users/${createdUserId}`);
await expect(page.locator('.user-name')).toHaveText('Test User');
} finally {
// Очистка выполняется всегда, даже при падении теста
if (createdUserId) {
await deleteTestUser(createdUserId);
}
}
}
Практические паттерны для тестирования
Test Data Builder
function createUser(overrides = {}) {
return {
name: 'Default User',
email: `user_${Date.now()}@test.com`,
role: 'viewer',
active: true,
...overrides
};
}
// Использование
const admin = createUser({ role: 'admin', name: 'Admin User' });
const inactive = createUser({ active: false });
Управление конфигурацией
const config = {
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
apiUrl: process.env.API_URL || 'http://localhost:8080',
timeout: parseInt(process.env.TIMEOUT) || 30000,
headless: process.env.CI === 'true'
};
Упражнение: Напишите свои первые хелпер-функции
Напишите следующие функции на JavaScript:
generateEmail()— возвращает уникальный email для тестовых данныхformatPrice(amount, currency)— форматирует цену как “$29.99” или “EUR 29.99”retryAction(action, maxRetries)— повторяет async-действие до maxRetries разcompareArrays(arr1, arr2)— возвращает true, если массивы содержат одинаковые элементы
Эти паттерны постоянно встречаются в реальных проектах автоматизации.
Ключевые выводы
- Освойте переменные, функции, условия и циклы — это 80% тест-кода
- Изучите async/await досконально — все современные фреймворки его используют
- Используйте try-catch-finally для надёжной обработки ошибок и очистки
- Объекты и массивы — основные структуры данных для тестовых данных
- Функции должны быть маленькими, сфокусированными и переиспользуемыми