Параметризованные тесты

Параметризованные тесты позволяют запускать один и тот же набор тестов с различными наборами данных, что значительно упрощает поддержку и расширяемость автоматизированного тестирования. В Protractor, как и в других инструментах на базе Jasmine или Mocha, этот подход реализуется с помощью массивов данных и циклического создания спецификаций.


Использование массивов данных для тестов

Основная идея параметризации заключается в хранении входных данных и ожидаемых результатов в структурах данных (массивы, объекты), а затем прохождении этих данных циклом. Пример структуры:

const testData = [
    { username: 'user1', password: 'pass1', expectedTitle: 'Dashboard' },
    { username: 'user2', password: 'pass2', expectedTitle: 'Dashboard' },
    { username: 'invalid', password: 'wrong', expectedTitle: 'Login Failed' }
];

Каждый элемент массива представляет отдельный набор параметров для теста.


Генерация тестов с forEach

Для создания отдельных тестов на основе данных массива часто используется метод forEach. Это обеспечивает независимость каждого запускаемого сценария:

testData.forEach(data => {
    it(`should login with username: ${data.username}`, async () => {
        await browser.get('https://example.com/login');
        await element(by.id('username')).sendKeys(data.username);
        await element(by.id('password')).sendKeys(data.password);
        await element(by.id('loginButton')).click();

        const title = await browser.getTitle();
        expect(title).toEqual(data.expectedTitle);
    });
});

В данном примере каждый объект массива создаёт отдельный it блок с соответствующими параметрами. Это облегчает диагностику ошибок, так как Jasmine отображает имя теста, включая параметры.


Использование data-driven подхода с jasmine-data-provider

Protractor поддерживает интеграцию с внешними библиотеками для параметризации. Одна из популярных — jasmine-data-provider. Она позволяет более структурировано определять наборы данных и автоматически генерировать тесты:

const using = require('jasmine-data-provider');

function loginData() {
    return [
        { username: 'user1', password: 'pass1', expectedTitle: 'Dashboard' },
        { username: 'user2', password: 'pass2', expectedTitle: 'Dashboard' },
        { username: 'invalid', password: 'wrong', expectedTitle: 'Login Failed' }
    ];
}

using(loginData, (data) => {
    it(`should login with username: ${data.username}`, async () => {
        await browser.get('https://example.com/login');
        await element(by.id('username')).sendKeys(data.username);
        await element(by.id('password')).sendKeys(data.password);
        await element(by.id('loginButton')).click();

        const title = await browser.getTitle();
        expect(title).toEqual(data.expectedTitle);
    });
});

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


Параметризация через beforeEach

Иногда требуется подготовка данных перед каждым тестом, особенно если входные данные зависят от состояния приложения. Для этого используется beforeEach:

let currentData;

beforeEach(() => {
    currentData = testData.shift(); // получение следующего набора данных
});

it(`should login with username: ${currentData.username}`, async () => {
    await browser.get('https://example.com/login');
    await element(by.id('username')).sendKeys(currentData.username);
    await element(by.id('password')).sendKeys(currentData.password);
    await element(by.id('loginButton')).click();

    const title = await browser.getTitle();
    expect(title).toEqual(currentData.expectedTitle);
});

Использование beforeEach позволяет избежать повторного дублирования кода подготовки данных внутри каждого it.


Параметризация сложных сценариев

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

const cartData = [
    { items: [{name: 'Apple', qty: 2}, {name: 'Banana', qty: 3}], expectedTotal: 5 },
    { items: [{name: 'Orange', qty: 1}, {name: 'Grapes', qty: 4}], expectedTotal: 5 }
];

cartData.forEach(cart => {
    it(`should calculate correct total for items: ${cart.items.map(i => i.name).join(', ')}`, async () => {
        await browser.get('https://example.com/cart');

        for (const item of cart.items) {
            const input = element(by.id(`${item.name}-qty`));
            await input.clear();
            await input.sendKeys(item.qty);
        }

        await element(by.id('calculateTotal')).click();
        const total = await element(by.id('total')).getText();
        expect(parseInt(total)).toEqual(cart.expectedTotal);
    });
});

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


Преимущества параметризованных тестов

  • Масштабируемость — легко добавлять новые наборы данных без дублирования кода.
  • Повышение читаемости — каждый тест отображает конкретный набор параметров.
  • Упрощение поддержки — изменения логики теста применяются ко всем наборам данных сразу.
  • Снижение ошибок — минимизируется повторение однотипного кода.

Рекомендации по организации данных

  1. Хранить тестовые данные отдельно от логики теста — в отдельных файлах или JSON.
  2. Использовать ясные и однозначные ключи для параметров, чтобы тесты оставались читаемыми.
  3. При больших наборах данных применять утилиты для генерации комбинаций, например, перебор всех возможных значений для входных параметров.

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