Шаблоны проектирования в QA (на примерах)

Шаблоны проектирования (альтернативные названия: паттерны проектирования, паттерны программирования, паттерны дизайна, дизайн-паттерны) — готовые подходы, типичные способы решения часто встречающихся проблем при проектировании ПО, и в частности автотестов. Таких повторяемых архитектурных решений существует около 100, рассмотрим самые распространенные.

Page Object Pattern

Популярный паттерн, делающий код реюзабельным (легким для повторного использования во многих местах) и упрощающий его обслуживание в дальнейшем, путем инкапсуляции данных приложения в специальных объектах. Эти объекты представляют собой отдельные страницы или секции веб-приложения, содержащие нужные элементы и методы для работы с ними.

Далее пример JS-кода, как Page Object Pattern (POM-паттерн) работает в Playwright [прим.ред. — здесь полный гайд по Playwright, или маленький быстрый туториал для первого знакомства с этим замечательным фреймворком для веба]. Здесь мы создаем Page Object для страницы логина и используем ее для взаимодействия с элементами страницы и выполнения операции входа.

Создаем файл LoginPage.js с классом LoginPage:

class LoginPage {
  constructor(page) {
    this.page = page;
  }

  async navigate(url) {
    await this.page.goto(url);
  }

  async setUsername(username) {
    await this.page.fill('#username', username);
  }

  async setPassword(password) {
    await this.page.fill('#password', password);
  }

  async clickLoginButton() {
    await this.page.click('#login-button');
  }

  async login(username, password) {
    await this.setUsername(username);
    await this.setPassword(password);
    await this.clickLoginButton();
  }
}

module.exports = LoginPage;

Теперь создаем test.js, задействующий класс LoginPage в операции логина, используя Playwright:

const { chromium } = require('playwright');
const LoginPage = require('./LoginPage');

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  const loginPage = new LoginPage(page);

  await loginPage.navigate('https://example.com/login');
  await loginPage.login('username', 'password');

  // Add your test assertions or further actions here.

  await browser.close();
})();

В этом примере мы создали класс LoginPage, инкапсулирующий взаимодействия с элементами страницы логина. Файл test.js использовал класс LoginPage для: перехода на страницу, заполнения юзернейма и пароля, и нажатия кнопки входа.

Чем полезен паттерн Page Object Model:

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

Подробнейший гайд по этому паттерну давно лежит на сайте.

Factory Method Pattern 

Паттерн создания объектов без указания их конкретных классов. В QA он применяется для генерации тестовых объектов (тестовых данных, тест-кейсов, или даже тестовых окружений).

Далее пример на TypeScript, как этот паттерн создает тестовые данные для регистрации пользователей. Описываем абстрактный класс UserDataFactory и несколько factory-классов, имплементирующих create()-метод для генерации пользовательских данных с нужными характеристиками.

Сначала создаем файл UserData.ts с интерфейсом UserData:

interface UserData {
  username: string;
  email: string;
  password: string;
}
export default UserData;

Далее создаем файл UserDataFactory.ts, содержащий абстрактный класс UserDataFactory:

import UserData from './UserData';

abstract class UserDataFactory {
  abstract create(): UserData;
}

export default UserDataFactory;

Теперь создаем два конкретных factory-класса в отдельных файлах. Сначала создаем файл ValidUserDataFactory.ts:

import UserDataFactory from './UserDataFactory';
import UserData from './UserData';

class ValidUserDataFactory extends UserDataFactory {
  create(): UserData {
    return {
      username: 'ValidUser',
      email: 'validuser@example.com',
      password: 'ValidPassword123',
    };
  }
}

export default ValidUserDataFactory;

Теперь файл InvalidUserDataFactory.ts:

import UserDataFactory from './UserDataFactory';
import UserData from './UserData';

class InvalidUserDataFactory extends UserDataFactory {
  create(): UserData {
    return {
      username: 'InvalidUser',
      email: 'invaliduser@invalid',
      password: 'short',
    };
  }
}

export default InvalidUserDataFactory;

И в завершение создаем файл test.ts, использующий factory-классы для генерации тестовых данных:

import { chromium } from 'playwright';
import UserDataFactory from './UserDataFactory';
import ValidUserDataFactory from './ValidUserDataFactory';
import InvalidUserDataFactory from './InvalidUserDataFactory';

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  const validUserDataFactory: UserDataFactory = new ValidUserDataFactory();
  const invalidUserDataFactory: UserDataFactory = new InvalidUserDataFactory();

  const validUserData = validUserDataFactory.create();
  const invalidUserData = invalidUserDataFactory.create();

  // Use the generated test data to interact with the web application and perform test assertions.

  await browser.close();
})();

Чем полезен паттерн:

  • Упрощает создание объектов, абстрагируя создание экземпляров
  • Улучшает масштабируемость — проще добавление новых типов объектов
  • Подстановка имплементации объектов без изменения кода

Data-Driven-тестирование

Паттерн, основанный на отделении тестовых данных от тестовых сценариев. Это позволяет запускать тест-кейсы каждый раз с новыми наборами входных данных. Полезно в сценариях валидации с множеством комбинаций входных данных (то есть довольно часто в QA).

Пример на TypeScript в vitest. Проверяется метод, суммирующий два числа:

test.each([
  [1, 1, 2],
  [1, 2, 3],
  [2, 1, 3],
])('add(a %i, b %i) -> c %i', (a, b, expected) => {
  expect(a + b).toBe(expected)
})

Чем полезен такой паттерн:

  • Увеличивает тестовое покрытие, с минимальным дублированием кода
  • Упрощает обслуживание тестовых данных
  • Улучшает читаемость кода

Паттерн Singleton

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

Далее пример на TypeScript, как расшарить экземпляр браузера в Playwright на несколько тест-кейсов. Синглтон-паттерн создает только один экземпляр браузера, и передает его в тестовый набор.

Создаем файл BrowserSingleton.ts для экземпляра браузера:

import { Browser, chromium } from 'playwright';

class BrowserSingleton {
  private static instance: Browser | null = null;

  private constructor() {}

  public static async getInstance(): Promise<Browser> {
    if (!this.instance) {
      this.instance = await chromium.launch();
    }
    return this.instance;
  }
}

export default BrowserSingleton;

Теперь в тестовых файлах используем расшаренный (то есть общий для этих тестов) экземпляр браузера из BrowserSingleton.ts:

import { Page, BrowserContext } from 'playwright';
import BrowserSingleton from './BrowserSingleton';

let context: BrowserContext;
let page: Page;

beforeAll(async () => {
  const browser = await BrowserSingleton.getInstance();
  context = await browser.newContext();
  page = await context.newPage();
});

afterAll(async () => {
  await context.close();
});

// Write your tests using the shared browser instance.

Синглтон-паттерн в QA:

  • Предотвращает проблемы, связанные с общим использованием ресурсов
  • Создает глобальную точку доступа к ресурсам (данным)
  • Снижает нагрузку на железо, повышая производительность
  • Делает конфигурации реюзабельными

Strategy-паттерн

«Поведенческий» (бихевиоральный) паттерн дизайна ПО, в котором описывают группу (семейство) алгоритмов, инкапсулируя их и делая взаимозаменяемыми. В QA применяется для активации разных тестовых стратегий в процессе выполнения.

В частности, уменьшает стоимость операций непрерывной интеграции. Например, при локальном запуске теста в Playwright применяем реальный API, а при запуске в CI-окружении, например в GitHub Actions, просто переключаемся на эмулированные данные из моков. Это экономно, поскольку «время в CI» стОит достаточно дорого, а тестов может быть очень много.

Преимущества Strategy-паттерна в QA:

  • Гибкое оперативное применение разнообразных подходов к тестированию (стратегий)
  • Что улучшает возможности валидации тестов
  • Экономия времени и стоимости CI

Итак

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

Источники: 1,2

***

Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

Подписаться
Уведомить о
guest

0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

? Популярное

? Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас