Pairwise testing (также известное как all-pairs testing) - это комбинаторная техника тестирования, которая драматически сокращает размер тестового набора, сохраняя высокие показатели обнаружения дефектов. Тестируя все возможные комбинации пар параметров вместо всех возможных комбинаций всех параметров, pairwise testing достигает приблизительно 90% покрытия дефектов с долей тестовых случаев.

Проблема Комбинаторного Взрыва

Современные программные системы имеют многочисленные опции конфигурации и входные параметры. Тестирование всех комбинаций быстро становится невыполнимым.

Пример: Тестирование Конфигурации

Рассмотрим простой e-commerce checkout с 5 параметрами:

  • Метод Оплаты: Кредитная Карта, PayPal, Банковский Перевод (3 опции)
  • Доставка: Стандарт, Экспресс, Ночная (3 опции)
  • Подарочная Упаковка: Да, Нет (2 опции)
  • Купон: Применен, Нет (2 опции)
  • Тип Пользователя: Гость, Зарегистрированный (2 опции)

Исчерпывающее тестирование: 3 × 3 × 2 × 2 × 2 = 72 тестовых случая

Pairwise testing: Приблизительно 12-15 тестовых случаев, покрывающих все pairwise взаимодействия

По мере увеличения параметров, экономия становится драматической:

ПараметрыОпций КаждыйИсчерпывающийPairwiseСокращение
53243~1594%
10359,049~2099.97%
2021,048,576~1099.999%

Алгоритм All-Pairs

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

Математическое Обоснование

Исследование NIST показывает, что большинство дефектов программного обеспечения вызываются взаимодействиями максимум 2 параметров (приблизительно 70% дефектов). Добавление 3-way взаимодействий ловит ~90-95% дефектов.

Pairwise Покрытие: Для каждой пары (P1, P2) параметров и каждой комбинации (v1, v2), где v1 - значение P1 и v2 - значение P2, существует как минимум один тестовый случай, где P1 = v1 и P2 = v2.

Простой Пример

Параметры:

  • Browser: Chrome, Firefox (2 значения)
  • OS: Windows, Mac, Linux (3 значения)
  • Network: WiFi, 4G (2 значения)

Исчерпывающий: 2 × 3 × 2 = 12 тестов

Pairwise набор:

TestBrowserOSNetwork
1ChromeWindowsWiFi
2ChromeMac4G
3ChromeLinuxWiFi
4FirefoxWindows4G
5FirefoxMacWiFi
6FirefoxLinux4G

6 тестов покрывают все pairwise комбинации вместо 12 исчерпывающих тестов.

Инструменты Pairwise Testing

PICT (Pairwise Independent Combinatorial Testing)

PICT от Microsoft - наиболее широко используемый инструмент pairwise testing.

Базовое Использование

# Создать файл модели: config.txt
Browser: Chrome, Firefox, Edge
OS: Windows, Mac, Linux
Network: WiFi, 4G, Ethernet

# Сгенерировать pairwise тестовые случаи
pict config.txt

Вывод:

Browser	OS	Network
Chrome	Windows	WiFi
Chrome	Mac	4G
Chrome	Linux	Ethernet
Firefox	Windows	Ethernet
Firefox	Mac	WiFi
Firefox	Linux	4G
Edge	Windows	4G
Edge	Mac	Ethernet
Edge	Linux	WiFi

Продвинутые Возможности PICT

Constraints: Исключить недопустимые комбинации

# model.txt
OS: Windows, Mac, Linux
Browser: IE, Safari, Chrome, Firefox
Resolution: 1024x768, 1920x1080, 2560x1440

# Ограничения
IF [OS] = "Mac" THEN [Browser] <> "IE";
IF [OS] = "Windows" THEN [Browser] <> "Safari";
IF [Resolution] = "2560x1440" THEN [OS] <> "Windows";

Submodels: Увеличить силу взаимодействия для конкретных параметров

# Протестировать все 3-way взаимодействия для критических параметров
Payment: CreditCard, PayPal, Crypto
Currency: USD, EUR, GBP
Amount: Low, Medium, High

{ Payment, Currency, Amount } @ 3

Seeding: Включить конкретные обязательные тестовые случаи

Browser: Chrome, Firefox, Safari
OS: Windows, Mac, Linux

# Засеять конкретные комбинации
Browser	OS
Chrome	Windows
Firefox	Linux

AllPairs (Python)

Чистая Python реализация для программной генерации тестов.

from allpairspy import AllPairs

parameters = [
    ["Windows", "Mac", "Linux"],        # OS
    ["Chrome", "Firefox", "Safari"],     # Browser
    ["WiFi", "Ethernet", "4G"]          # Network
]

# Сгенерировать pairwise комбинации
for i, test in enumerate(AllPairs(parameters)):
    print(f"Test {i+1}: OS={test[0]}, Browser={test[1]}, Network={test[2]}")

Вывод:

Test 1: OS=Windows, Browser=Chrome, Network=WiFi
Test 2: OS=Windows, Browser=Firefox, Network=Ethernet
Test 3: OS=Windows, Browser=Safari, Network=4G
Test 4: OS=Mac, Browser=Chrome, Network=Ethernet
Test 5: OS=Mac, Browser=Firefox, Network=4G
Test 6: OS=Mac, Browser=Safari, Network=WiFi
Test 7: OS=Linux, Browser=Chrome, Network=4G
Test 8: OS=Linux, Browser=Firefox, Network=WiFi
Test 9: OS=Linux, Browser=Safari, Network=Ethernet

TestCoverageOptimizer (Java)

import com.pairwise.TCO;

public class PairwiseExample {
    public static void main(String[] args) {
        // Определить параметры
        String[][] parameters = {
            {"Windows", "Mac", "Linux"},
            {"Chrome", "Firefox", "Edge"},
            {"WiFi", "Ethernet"}
        };

        // Сгенерировать pairwise тесты
        TCO tco = new TCO(parameters);
        List<int[]> tests = tco.generatePairwise();

        // Вывести тестовые случаи
        for (int[] test : tests) {
            System.out.println(Arrays.toString(test));
        }
    }
}

CTE (Classification Tree Editor)

GUI инструмент для моделирования и генерации комбинаторных тестов с визуальными деревьями классификации.

Возможности:

  • Визуальное моделирование параметров на основе дерева
  • Спецификация ограничений
  • Множественные критерии покрытия (pairwise, 3-way, и т.д.)
  • Экспорт тестовых случаев в различные форматы

Ортогональные Массивы

Ортогональные массивы - это математические структуры, которые гарантируют pairwise покрытие.

Что такое Ортогональные Массивы?

Ортогональный массив OA(N, k, v, t) - это N × k матрица, где:

  • N = число тестовых случаев
  • k = число параметров
  • v = число значений на параметр
  • t = сила взаимодействия (2 для pairwise)

Свойство: Каждая N × t подматрица содержит все возможные t-кортежи одинаковое количество раз.

Пример: Массив L9(3^4)

Классический ортогональный массив для 4 параметров с 3 значениями каждый:

TestP1P2P3P4
11111
21222
31333
42123
52231
62312
73132
83213
93321

Исчерпывающий потребовал бы 3^4 = 81 теста; ортогональный массив нуждается только в 9.

Применение Ортогональных Массивов

# Отобразить значения на ортогональный массив
parameters = {
    'Browser': ['Chrome', 'Firefox', 'Edge'],
    'OS': ['Windows', 'Mac', 'Linux'],
    'Network': ['WiFi', '4G', 'Ethernet'],
    'Theme': ['Light', 'Dark', 'Auto']
}

# Использовать структуру массива L9
L9_array = [
    [0, 0, 0, 0],
    [0, 1, 1, 1],
    [0, 2, 2, 2],
    [1, 0, 1, 2],
    [1, 1, 2, 0],
    [1, 2, 0, 1],
    [2, 0, 2, 1],
    [2, 1, 0, 2],
    [2, 2, 1, 0]
]

# Отобразить на реальные значения
param_names = ['Browser', 'OS', 'Network', 'Theme']
param_values = [
    ['Chrome', 'Firefox', 'Edge'],
    ['Windows', 'Mac', 'Linux'],
    ['WiFi', '4G', 'Ethernet'],
    ['Light', 'Dark', 'Auto']
]

tests = []
for row in L9_array:
    test = {}
    for i, param in enumerate(param_names):
        test[param] = param_values[i][row[i]]
    tests.append(test)

N-Way Testing: За Пределами Pairwise

Хотя pairwise (2-way) наиболее распространен, взаимодействия более высокого порядка могут быть необходимы для критических систем.

Сравнение Силы Взаимодействия

СилаНазваниеПокрытиеТестовые СлучаиСлучай Использования
1-wayEach Choice~50%МинимумSmoke тесты
2-wayPairwise~70-90%МалыйБольшинство систем
3-wayThree-way~90-95%СреднийКритические системы
4-wayFour-way~95-99%БольшойSafety-critical
AllИсчерпывающий100%ЭкспоненциальныйРедкий

Пример 3-Way с PICT

# model.txt
Database: MySQL, PostgreSQL, Oracle
Cache: Redis, Memcached
LoadBalancer: Nginx, HAProxy

# Сгенерировать 3-way взаимодействия
pict model.txt /o:3

Результат: Все 3-параметровые комбинации покрыты (вместо только пар).

Ограничения и Недопустимые Комбинации

Реальные системы имеют недопустимые комбинации, которые должны быть исключены.

Синтаксис Constraint PICT

# E-commerce модель
PaymentMethod: CreditCard, PayPal, BankTransfer, Cash
ShippingSpeed: Standard, Express, Overnight
Country: USA, Canada, Mexico, UK
Amount: $10, $100, $1000

# Ограничения
IF [PaymentMethod] = "Cash" THEN [ShippingSpeed] = "Standard";
IF [Country] = "UK" THEN [PaymentMethod] <> "Cash";
IF [Amount] = "$1000" THEN [PaymentMethod] <> "Cash";
IF [ShippingSpeed] = "Overnight" THEN [Country] <> "Mexico";

Функция Filter AllPairs

from allpairspy import AllPairs

def is_valid_combination(values, names):
    payment, shipping, country = values

    # Наличные только со стандартной доставкой
    if payment == "Cash" and shipping != "Standard":
        return False

    # UK не принимает наличные
    if country == "UK" and payment == "Cash":
        return False

    return True

parameters = [
    ["CreditCard", "PayPal", "Cash"],
    ["Standard", "Express", "Overnight"],
    ["USA", "UK", "Mexico"]
]

for test in AllPairs(parameters, filter_func=is_valid_combination):
    print(test)

Рабочий Процесс Практической Реализации

Шаг 1: Идентифицировать Параметры и Значения

Проанализировать тестируемую систему и перечислить все переменные параметры.

# parameters.yaml
authentication:
  - username_password
  - oauth
  - saml
  - api_key

database_backend:
  - mysql
  - postgresql
  - sqlite

caching:
  - enabled
  - disabled

logging_level:
  - debug
  - info
  - warning
  - error

Шаг 2: Определить Ограничения

Документировать недопустимые комбинации на основе бизнес-правил и технических ограничений.

# constraints.py
def validate_config(auth, db, cache, log_level):
    # SQLite не поддерживает определенные методы auth
    if db == "sqlite" and auth == "saml":
        return False

    # Debug logging требует отключенного кеширования для точности
    if log_level == "debug" and cache == "enabled":
        return False

    return True

Шаг 3: Сгенерировать Тестовый Набор

Использовать PICT или AllPairs для генерации оптимизированного тестового набора.

# Преобразовать в формат PICT
authentication: username_password, oauth, saml, api_key
database_backend: mysql, postgresql, sqlite
caching: enabled, disabled
logging_level: debug, info, warning, error

IF [database_backend] = "sqlite" THEN [authentication] <> "saml";
IF [logging_level] = "debug" THEN [caching] = "disabled";

# Сгенерировать
pict config.pict > testcases.txt

Шаг 4: Выполнить Тесты

Отобразить сгенерированные комбинации на исполняемые тесты.

import pytest
from allpairspy import AllPairs

@pytest.mark.parametrize("auth,db,cache,log", [
    test for test in AllPairs([
        ["username_password", "oauth", "saml", "api_key"],
        ["mysql", "postgresql", "sqlite"],
        ["enabled", "disabled"],
        ["debug", "info", "warning", "error"]
    ])
])
def test_configuration(auth, db, cache, log):
    # Настроить систему с данной конфигурацией
    config = {
        'authentication': auth,
        'database': db,
        'caching': cache,
        'logging_level': log
    }

    system = SystemUnderTest(config)
    assert system.health_check()

Преимущества Pairwise Testing

Драматическое Сокращение Тестового Набора

Сократить сотни или тысячи тестов до десятков.

Пример из Практики: Тестирование конфигурации телекома сокращено с 1,200 исчерпывающих тестов до 87 pairwise тестов (93% сокращение) без потери в обнаружении дефектов.

Высокая Скорость Обнаружения Дефектов

Исследования показывают, что pairwise testing ловит 70-90% дефектов, которые нашло бы исчерпывающее тестирование.

Исследование NIST: Анализ реальных сбоев программного обеспечения обнаружил:

  • 70% вызвано одиночными параметрами
  • 93% вызвано 2-way взаимодействиями
  • 98% вызвано 3-way взаимодействиями

Более Быстрое Выполнение

Меньше тестов означает более быстрые циклы обратной связи.

Пример ROI: Время выполнения тестового набора конфигурации сокращено с 8 часов до 45 минут.

Лучшее Обслуживание Тестов

Меньшие тестовые наборы легче поддерживать и обновлять.

Вызовы и Ограничения

Идентификация Полных Наборов Параметров

Пропущенные параметры означают пропущенные взаимодействия.

Смягчение: Провести тщательный анализ с экспертами предметной области; использовать исследовательское тестирование для дополнения.

Сложность Ограничений

Сложные бизнес-правила делают спецификацию ограничений сложной.

Смягчение: Начать с простых ограничений; уточнять итеративно на основе сбоев тестов.

Взаимодействия Более Высокого Порядка

Pairwise не ловит дефекты, требующие взаимодействия 3+ параметров.

Смягчение: Использовать 3-way тестирование для критических модулей; комбинировать с тестированием на основе рисков.

Нефункциональные Аспекты

Pairwise фокусируется на функциональных комбинациях, не на производительности или безопасности.

Смягчение: Комбинировать с выделенным нефункциональным тестированием.

Лучшие Практики

Начинать с Критических Параметров

Фокусировать pairwise на высокорисковых, часто используемых функциях сначала.

# Критический поток платежей
PaymentGateway: Stripe, PayPal, Square
Currency: USD, EUR, GBP
PaymentType: OneTime, Subscription
3DS: Enabled, Disabled

# Некритические UI предпочтения тестируются исчерпывающе (меньше комбинаций)
Theme: Light, Dark
Language: EN, ES

Комбинировать с Equivalence Partitioning

Использовать классы эквивалентности для определения значений параметров.

# Вместо конкретных значений
amounts = [0.01, 50.00, 99.99, 100.00, 1000.00, 9999.99]

# Использовать классы эквивалентности
amounts = ["Minimum", "Standard", "High", "Maximum"]
# Отобразить на представительные значения во время выполнения

Документировать Предположения

Записывать почему существуют ограничения для будущего обслуживания.

# model.pict
Browser: Chrome, Firefox, Safari, IE
OS: Windows, Mac, Linux

# IE работает только на Windows (техническое ограничение)
IF [Browser] = "IE" THEN [OS] = "Windows";

# Safari в основном Mac (бизнес-решение о депр-и Windows Safari)
IF [Browser] = "Safari" THEN [OS] = "Mac";

Контроль Версий Тестовых Моделей

Обращаться с PICT/AllPairs моделями как с кодовыми артефактами.

# Структура Git репозитория
tests/
  ├── models/
  │   ├── authentication.pict
  │   ├── checkout.pict
  │   └── reporting.pict
  ├── generated/
  │   ├── auth_tests.csv
  │   └── checkout_tests.csv
  └── scripts/
      └── generate_all.sh

Применение в Реальном Мире

Матрица Совместимости Browser/OS

Тестирование веб-приложения через 5 браузеров × 3 OS × 3 размера экрана = 45 исчерпывающих тестов.

Pairwise: 12 тестов, покрывающих все pairwise взаимодействия.

Результаты: Обнаружено 8 проблем рендеринга, все пойманы pairwise набором.

Тестирование Конфигурации API

REST API с 8 параметрами конфигурации, каждый с 2-4 опциями (1,536 исчерпывающих комбинаций).

Pairwise: 24 теста.

Результаты: Найдены конфликты конфигурации, которые были бы упущены случайной выборкой.

Тестирование Фрагментации Устройств

Мобильное приложение, протестированное через версии Android, производители, размеры экрана, сетевые условия.

3-way тестирование: 150 тестов (против 10,000+ исчерпывающих).

Результаты: 95% скорость обнаружения дефектов с 98.5% сокращением времени выполнения тестов.

Заключение

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

Успех с pairwise testing требует тщательного выбора параметров, продуманного моделирования ограничений и комбинирования pairwise с другими стратегиями тестирования (исследовательское, основанное на рисках, и т.д.) для всестороннего обеспечения качества.