Анализ классов эквивалентности

При получении каждой новой рабочей задачи сталкиваюсь с вопросами: как выполнить работу не «в лоб», а с наименьшими усилиями и с наибольшими покрытием? Как охватить группы входных условий, покрыть их проверками и построить тесты? Как спроектировать тесты с помощью эквивалентного разбиения? Как отказаться от интуитивного процесса подготовки тестов, похожего на процесс угадывания в пользу методичного проектирования проверок?

На перечисленные вопросы уже дали ответ, и не единожды. Ряд таких ответов я привел в публикации «Анализ классов эквивалентности». На днях я обновил указанную публикацию: добавив оглавление со ссылками, выделил в отдельную заметку материалы по анализу граничных значений.

В этой публикации хочу еще раз обратить внимание на метод описанный Гленфордом Майерсом в 1982 году. Метод признан другими авторами книг по тестированию, например Канером, Фолком и Нгуеном. Но мне не приходилось пока встречать тестировщиков-практиков, которые бы пользовались плодами трудов Майерса и использовали в работе подробно описанный, последовательный и структурированный подходе к разбиению на классы эквивалентности.

«Искусство тестирования программ», Глендфорд Майерс. Москва, Финансы и статистика, 1982 год. 63-74 страницы:

Эквивалентное разбиение

В главе 2 отмечалось, что хороший тест имеет приемлемую вероятность обнаружения ошибки и что исчерпывающее входное тестирование программы невозможно. Следовательно, тестирование программы ограничивается использованием небольшого подмножества всех возможных входных данных. Тогда, конечно, хотелось бы выбрать для тестирования самое подходящее подмножество (то есть подмножество с наивысшей вероятностью обнаружения большинства ошибок).

Правильно выбранный тест этого подмножества должен обладать двумя свойствами:

  1. уменьшать, причем более чем на единицу, число других тестов, которые должны быть разработаны для достижения заранее определенной цели «приемлемого тестирования»;
  2. покрывать значительную часть других возможных тестов, что в некоторой степени свидетельствует о наличии или отсутствии ошибок до и после применения этого ограниченного множества значений входных данных.

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

Иными словами, если один тест класса эквивалентности обнаруживают ошибку, то следует ожидать, что и все другие тесты этого класса эквивалентности будут обнаруживать ту же самую ошибку. Наоборот, если тест не обнаруживает ошибки, то следует ожидать, что ни один тест этого класса эквивалентности не будет обнаруживать ошибки (в том случае, когда некоторое подмножество класса эквивалентности не попадает в пределы любого другого класса эквивалентности, так как классы эквивалентности могут пересекаться).

Эти два положения составляют основу методологии тестирования по принципу чёрного ящика, известной как эквивалентное разбиение. Второе положение используется для разработки набора «интересных» условий, которые должны быть протестированная, а первое – для разработки минимального набора тестов, покрывающих эти условия.

Примером класса эквивалентности для программы о треугольнике (смотри главу 1) является набор «трёх равных чисел, имеющих целые значения, большие нуля». Определяя этот набор как класс эквивалентности, устанавливают, что если ошибка не обнаружена некоторым тестом данного набора, то маловероятно, что она будет обнаружена другим тестом набора. Иными словами, в этом случае время тестирования лучше затратить на что-нибудь другое (на тестирование других классов эквивалентности).

Разработка тестов методом эквивалентного разбиения осуществляется в два этапа:

  1. выделение классов эквивалентности и
  2. построение тестов.

Выделение классов эквивалентности

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

Входные условия Правильные классы эквивалентности Неправильные классы эквивалентности

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

  1. Если входное условие описывает область значений (например, «целое данное может принимать значения от 1 до 999»), то определяются один правильный класс эквивалентности (1 <= значение целого данного <= 999) и два неправильных (значение целого данного < 1 и значение целого данного > 999).
  2. Если входное условие описывает число значений (например, «в автомобиле могут ехать от одного до шести человек»), то определяются один правильный класс эквивалентности и два неправильных (ни одного и более шести человек).
  3. Если входное условие описывает множество входных значений и есть основание полагать, что каждое значение программа трактует особо (например, «известны способы передвижения на АВТОБУСЕ, ГРУЗОВИКЕ, ТАКСИ, ПЕШКОМ или МОТОЦИКЛЕ»), то определяется правильный класс эквивалентности для каждого значения и один неправильный класс эквивалентности (например, «НА ПРИЦЕПЕ»).
  4. Если входное условие описывает ситуацию «должно быть» (например, «первым символом идентификатора должна быть буква), то определяется один правильный класс эквивалентности (первый символ – буква) и один неправильный (первый символ – не буква).
  5. Если есть любое основание считать, что различные элементы класса эквивалентности трактуются программой неодинаково, то данный класс эквивалентности разбивается на меньшие классы эквивалентности.
    Этот процесс ниже будет кратко проиллюстрирован.

Построение тестов

Второй шаг заключается в использовании классов эквивалентности для построения тестов. Этот процесс включает в себя:

  1. Назначение каждому классу эквивалентности уникального номера.
  2. Проектирование новых тестов, каждый из которых покрывает как можно большее число непокрытых правильных классов эквивалентности, до тех пор пока все правильные класс эквивалентности не будут покрыты (только не общими) тестами.
  3. Запись тестов, каждый из которых покрывает один и только один из непокрытых неправильных классов эквивалентности, до тех пор пока всех неправильные классы эквивалентности не будут покрыты тестами.

Причина покрытия неправильных классов эквивалентности индивидуальными тестами состоит в том, что определённые проверки с ошибочными входами скрывают или заменяют другие проверки с ошибочными входами.

Например спецификация устанавливает «тип книги при поиске (ВЫЧИСЛИТЕЛЬНАЯ ТЕХНИКА, ПРОГРАММИРОВАНИЕ или ОБЩИЙ») и количество (1-9999)». Тогда тест

XYZ 0

отображает два ошибочных условия (неправильный тип книги и количество) и, вероятно, не будет осуществлять проверку количества, так как программа может ответить: «XYZ — НЕСУЩЕСТВУЮЩИЙ ТИП КНИГИ» и не проверять остальную часть входных данных.

Пример

Предположим, что при разработке компилятора для подмножества языка Фортран требуется протестировать синтаксическую проверку оператора DIMENSION. Спецификация приведена ниже. (Этот оператор не является полным оператором DIMENSION Фортрана; спецификация была значительно сокращена, что позволило сделать ее «учебным примером». Не следует думать, что тестирование реальных программ так же легко, как в примерах данной книги.) В спецификации элементы, написанные латинскими буквами, обозначают синтаксические единицы, которые в реальных операторах должны быть заменены соответствующими значениями, в квадратные скобки заключены необязательные элементы, многоточие показыает, что предшествующий ему элемент может быть повторен подряд несколько раз.

Оператор DIMENSION используется для определения массивов. Форма оператора DIMENSION:

DIMENSION ad[,ad]...

где ad есть описатель массива в форме

n(d[,d]...)

n — символическое имя массива, а d — индекс массива. Символические имена могут содержать от одного до шести символов — букв или цифр, причем первой должна быть буква. Допускается от одного до семи индексов. Форма индекса

[lb:]ub

где lb и ub задают нижнюю и верхнюю границы индекса массива.

Граница может быть либо константой, принимающей значения от -65534 до 65535, либо целой переменной (без индексов). Если lb не определена, то предполагаетя, что она равна единице. Значение ub должно быть больше или равно lb. Если lb опрееделена, то она может иметь отрицательное, нулевое или положительное значение. Как и все операторы, оператор DIMENSION может быть продолжен на нескольких строках. (Конец спецификации.)

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

Входные условия Правильные классы эквивалентности Неправильные классы эквивалентности
Число описателей массивов один (1), >одного (2) ни одного (3)
Длина имени массива 1-6 (4) 0 (5), >6 (6)
Имя массива имеет в своем составе буквы (7) и цифры (8) содержит что-то еще (9)
Имя массива начинается с буквы да (10) нет (11)
Число индексов 1-7 (12) 0 (13), >7 (14)
Верхняя граница константа (15), целая переменная (16) имя элемента массива (17), что-то иное (18)
Имя целой переменной имеет в своем составе буквы (19), и цифры (20) состоит из чего-то еще (21)
Целая переменная начинается с буквы да (22) нет (23)
Константа -65534-65535 (24) < -65534 (25), >65535 (26)
Нижняя граница определена да (27), нет (28)
Верхняя граница по отношению к нижней границе больше (29), равна (30) меньше (31)
Значение нижней границы отрицательное (32), нуль (33), >0 (34)
Нижняя граница константа (35), целая переменная (36) имя элемента массива (37), что-то иное (38)
Оператор расположен на нескольких строках да (39), нет (40)

Следующий шаг — построение теста, покрывающего один или более правильных классов эквивалентности. Например, тест

DIMENSION A(2)

покрывает классы 1, 4, 7, 10, 12, 15, 24, 28, 29 и 40. Далее определяются один или более тестов, покрывающих оставшиеся правильные классы эквивалентности. Так, тест

DIMENSION A12345(I,9,J4XXXX,65535,1,KLM, X 100), BBB (-65534:100,0:1000,10:10,1:65535)

покрывает оставшиеся классы. Перечислим неправильные классы эквивалентности и соответствующие им тесты:

(3): DIMENSION
(5): DIMENSION (10)
(6): DIMENSION A234567(2)
(9): DIMENSION A.1(2)
(11): DIMENSION 1A(10)
(13): DIMENSION B
(14): DIMENSION B(4,4,4,4,4,4,4,4)
(17): DIMENSION B(4,A(2))
(18): DIMENSION B(4,,7)
(21): DIMENSION C(I.,10)
(23): DIMENSION C(10,1J)
(25): DIMENSION D(-65535:1)
(26): DIMENSION D(65536)
(31): DIMENSION D(4:3)
(37): DIMENSION D(A(2):4)
(38): DIMENSION D(.:4)

Эти классы эквивалентности покрываются 18 тестами. Читатель может при желании сравнить данные тесты с набором тестов, полученным каким-либо специальным методом.

Хотя эквивалентное разбиение значительно лучше случайного выбора тестов, оно все же имеет недостатки (то есть пропускает определенные типы высокоэффективных тестов). Следующие два метода — анализ граничных значений и использование функциональных диаграмм (диаграмм причинно-следственных связей cause-effect graphing) — свободны от многих недостатков, присущих эквивалентному разбиению.