Принципы DRY и SOLID в автоматизации

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

Принцип DRY (Don’t Repeat Yourself)

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

Применение принципа DRY в тестах Playwright:

  1. Создание вспомогательных функций и методов: В Playwright можно организовать повторяющиеся действия (например, вход в систему, клик по кнопке, ожидание элемента) в виде общих функций. Эти функции могут быть использованы во всех тестах, где требуется выполнить похожие действия.

    Пример:

    // Вспомогательная функция для авторизации
    async function login(page, username, password) {
      await page.fill('#username', username);
      await page.fill('#password', password);
      await page.click('#login-button');
      await page.waitForSelector('#dashboard');
    }
    
    // Использование в тестах
    test('Тест на доступ к дашборду после авторизации', async ({ page }) => {
      await login(page, 'user1', 'password123');
      await expect(page).toHaveURL('/dashboard');
    });
  2. Использование Page Objects: Паттерн “Page Object” помогает отделить логику взаимодействия с UI от тестов. В этом паттерне для каждого экрана или компонента создается отдельный объект, который инкапсулирует действия с этим экраном. Это позволяет избежать дублирования логики и облегчает поддержку.

    Пример использования Page Object:

    class LoginPage {
      constructor(page) {
        this.page = page;
        this.usernameField = page.locator('#username');
        this.passwordField = page.locator('#password');
        this.loginButton = page.locator('#login-button');
      }
    
      async login(username, password) {
        await this.usernameField.fill(username);
        await this.passwordField.fill(password);
        await this.loginButton.click();
      }
    }
    
    // Тест с использованием Page Object
    test('Тест на успешный вход', async ({ page }) => {
      const loginPage = new LoginPage(page);
      await loginPage.login('user1', 'password123');
      await expect(page).toHaveURL('/dashboard');
    });
  3. Параметризация тестов: Повторяющиеся тесты можно сделать более универсальными с помощью параметризации. Это позволяет запускать один тест с разными входными данными, минимизируя количество повторений кода.

    Пример:

    const testCases = [
      { username: 'user1', password: 'password123', expectedUrl: '/dashboard' },
      { username: 'user2', password: 'password456', expectedUrl: '/dashboard' }
    ];
    
    testCases.forEach(({ username, password, expectedUrl }) => {
      test(`Тест на успешный вход для ${username}`, async ({ page }) => {
        await login(page, username, password);
        await expect(page).toHaveURL(expectedUrl);
      });
    });

Принцип SOLID

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

  1. Single Responsibility Principle (Принцип единственной ответственности)

    Каждый класс или модуль должен иметь одну причину для изменения. В автоматизации тестирования это означает, что каждый тест или компонент теста должен отвечать за выполнение одной конкретной задачи. Например, страница авторизации и страница поиска должны быть реализованы в отдельных классах Page Object, а не объединяться в один.

    Пример:

    class LoginPage {
      constructor(page) {
        this.page = page;
        this.usernameField = page.locator('#username');
        this.passwordField = page.locator('#password');
        this.loginButton = page.locator('#login-button');
      }
    
      async login(username, password) {
        await this.usernameField.fill(username);
        await this.passwordField.fill(password);
        await this.loginButton.click();
      }
    }
    
    class SearchPage {
      constructor(page) {
        this.page = page;
        this.searchInput = page.locator('#search-input');
        this.searchButton = page.locator('#search-button');
      }
    
      async search(query) {
        await this.searchInput.fill(query);
        await this.searchButton.click();
      }
    }
  2. Open/Closed Principle (Принцип открытости/закрытости)

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

    Пример: Если необходимо добавить новый шаг в авторизацию, можно создать новый метод в классе LoginPage, не изменяя старые методы.

  3. Liskov Substitution Principle (Принцип подстановки Лисков)

    Объекты базового класса должны быть заменяемы объектами производного класса без нарушения корректности программы. Для тестов это подразумевает, что при расширении Page Objects или других классов, новый функционал должен работать без изменения старых тестов.

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

  4. Interface Segregation Principle (Принцип разделения интерфейса)

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

    Пример: Если тесты взаимодействуют с несколькими экранами, каждый экран должен иметь свой интерфейс, а не один общий для всех экранов, что упростит изменение и поддержку кода.

  5. Dependency Inversion Principle (Принцип инверсии зависимостей)

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

    Пример: Вместо жесткого указания зависимости между тестом и конкретной страницей, можно использовать интерфейсы или абстракции для создания более гибкой архитектуры.

Совмещение принципов DRY и SOLID

Для создания качественной архитектуры автоматизированных тестов на основе Playwright важно соблюдать оба принципа одновременно. В этом случае можно добиться значительного улучшения качества кода и его поддерживаемости. Например, при соблюдении принципа DRY можно избегать дублирования кода, а соблюдение SOLID позволяет выстроить систему, в которой каждый тест или компонент будет легко расширяем и поддерживаем без необходимости переписывать уже написанные части.

Таким образом, используя принципы DRY и SOLID, можно создавать тесты, которые не только эффективны и стабильны, но и удобны для дальнейшего сопровождения и развития.