Разделение на эквивалентные классы

Разделение на эквивалентные классы (англ. equivalence partitioning ECP[1]) — это метод тестирования программного обеспечения, при котором входные данные программного модуля разбиваются на классы эквивалентности, из которых формируются наборы тестов. В идеале, тесты разрабатываются таким образом, чтобы покрывать каждый класс хотя бы один раз. Целью этого подхода является выявление целых групп ошибок, благодаря чему уменьшается общее количество необходимых тестовых случаев. Одним из преимуществ метода является сокращение времени, затрачиваемого на тестирование, за счёт уменьшения числа тестов.

Описание

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

Основная идея ECP восходит к понятию эквивалентный класс, которое, в свою очередь, связано с эквивалентным отношением. Программная система фактически реализует вычислимую функцию с помощью некоторого алгоритма на определённом языке программирования. Для каждого входного тестового вектора часть операторов этого алгоритма выполняется, а другая часть — нет. Это создаёт определённое отношение между входными тестовыми векторами: — это эквивалентное отношение между тестовыми векторами a, b, если и только если покрытие кода этими векторами совпадает, то есть они обеспечивают выполнение одного и того же набора операторов в одних и тех же точках. Такое отношение (C) делит множество возможных тестовых векторов на несколько эквивалентных классов. Подобное разбиение и называется разделением на эквивалентные классы входных данных. Если имеется N эквивалентных классов, достаточно N векторов для полного покрытия системы.

Пример можно рассмотреть на функции, написанной на языке C:

int safe_add(int a, int b)
{
    int c = a + b;

    if (a > 0 && b > 0 && c <= 0)
    {
        fprintf(stderr, "Переполнение (положительное)!\n");
    }

    if (a < 0 && b < 0 && c >= 0)
    {
        fprintf(stderr, "Переполнение (отрицательное)!\n");
    }

    return c;
}

Анализируя этот код, можно разбить входные векторы [a,b] на блоки, которые необходимо покрыть: положительное переполнение, отрицательное переполнение и отсутствие переполнения. Это приводит к выделению трёх эквивалентных классов — непосредственно исходя из анализа кода.

undefined

Для решения задачи с входными данными используют неравенство:

Поскольку размер целых чисел ограничен, переменные z заменяют на:

INT_MIN ≤ x + y ≤ INT_MAX

и

x \in \{\mathrm{INT\_MIN}, ..., \mathrm{INT\_MAX}\} и y \in \{\mathrm{INT\_MIN}, ..., \mathrm{INT\_MAX}\}

Тестовые значения, при которых выполняются строгие границы равенства (\mathrm{INT\_MIN} = x + y и \mathrm{INT\_MAX} = x + y) называют граничными значениями; подробнее см. анализ граничных значений. На графике показан только случай переполнения при положительных значениях x и y.

Как правило, каждый вход имеет диапазоны, являющиеся допустимыми, и диапазоны, которые считаются недопустимыми. Недопустимые данные здесь не означают ошибочность самих данных — это просто значения вне определённого класса. Лучшим примером служит функция, принимающая параметр "месяц": допустимым диапазоном будут значения от 1 до 12, соответствующие январю—декабрю. Этот диапазон образует один (допустимый) класс; ещё два класса составляют недопустимые диапазоны: значения меньше либо равные 0 и значения больше либо равные 13.

        ... -2 -1  0 1 .............. 12 13  14  15 .....
      --------------|-------------------|---------------------
 недопустимый класс 1     допустимый класс    недопустимый класс 2

Теория тестирования, связанная с разбиением на эквивалентные классы, утверждает: для проверки поведения программы в каждом классе достаточно одного теста из этого класса. Иными словами, выбор одного тестового случая для каждого класса позволяет проверить соответствующее поведение, а добавление других тестов из того же класса не выявит новых ошибок. Все значения внутри одного класса эквивалентны с точки зрения теста. Благодаря этому можно существенно сократить число тестов.

Дополнительным положительным эффектом метода является выявление так называемых «грязных» тестовых случаев. Неопытный тестировщик может ограничиться входными значениями 1–12 для месяца и забыть подобрать тест из недопустимых классов, что приведёт к избыточному количеству тестов с одной стороны и отсутствию проверки нештатных ситуаций — с другой.

Чаще всего разбиение на эквивалентные классы связывают с тестированием «чёрного ящика», при котором оценивают только интерфейс программного компонента, игнорируя его внутреннее устройство. Однако метод применим и к тестированию «серого ящика». Например, если на интерфейсе компонента допустимы значения от 1 до 12 (как в приведённом выше примере), но внутри функция обрабатывает значения от 1 до 6 и отдельно — от 7 до 12, то внутренние траектории выполнения будут различаться. Во внешнем поведении это не заметно, но при тестировании «серого ящика» важно протестировать обе ветви. Для этого вводят дополнительные классы эквивалентности, которые не требуются при тестировании интерфейса. В этом примере:

        ... -2 -1  0 1 ..... 6 7 ..... 12 13  14  15 .....
      --------------|---------|----------|---------------------
недопустимый класс 1     P1         P2     недопустимый класс 2
                   допустимые классы

Для проверки ожидаемых результатов понадобятся не только внешние выходные значения, но и внутренние промежуточные данные. Не обязательно использовать несколько значений из каждого класса: в приведённом сценарии можно взять −2 из недопустимого класса 1, 6 из допустимого класса P1, 7 из P2, и 15 из недопустимого класса 2.

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

Ограничения

В случаях, когда диапазоны данных просты (например: 0–10, 11–20, 21–30) и перебор всех значений несложен, полезно рассмотреть полное покрытие тестирования — то есть использование всех значений внутри диапазонов и по их границам. Такое покрытие может выявить ошибки, не обнаруживаемые методом эквивалентных классов, если в программе есть дополнительные внутренние поддиапазоны, о которых тестировщик не знает[2]. Также в простых случаях выигрыш от сокращения числа тестов оказывается меньше, чем при больших диапазонах (например: 0–1000, 1001–2000, 2001–3000).

Примечания

  1. Burnstein, Ilene. Practical Software Testing. — Springer-Verlag, 2003. — P. 623. — ISBN 0-387-95131-8.
  2. Mathur, Aditya. Foundations of Software Testing: Fundamental Algorithms and Techniques. — Pearson India, 2007. — P. 96. — ISBN 978-81-317-0795-1.

Литература

Категории