Структурное тестирование

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

Задача построения тестов весьма сложна. При структурном подходе, о котором и идет речь, возможно построение некоторого формального метода разработки тестов и соответствующего формального аппарата (базирующегося, например, на логике предикатов). Такие методы существуют, но они очень сложны и могут быть применены только к простым алгоритмам. Реальные методы включают формальное средства, эмпирические положения и рекомендации, и, как правило, при описании опираются на примеры[3].

Критерии качества структурного тестирования

Из множества различных критериев рассмотрим наиболее представительные

Тестирование всех путей

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

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

если ((A>1) & (B=0)) то

X:=X/A;

кесли;

если ((A=2) Ú (X>1)) то

X:=X+1;

кесли;

Чтобы обозначить все пути, запишем в квадратных скобках пустую ветвь «иначе». Пути обозначим слева малыми латинскими буквами. Пусть a – путь, предшествующий данному фрагменту.

a........................

если ((A>1) & (B=0)) то

c X:=X/A

[иначе

b

]

кесли;

если ((A=2) Ú (X>1)) то

e X:=X+1

[иначе

d

]

кесли;

Рис. 4.1. Пример для иллюстрации критериев тестирования

На тесте A=2, B=0, X=6 каждый оператор выполняется. Согласно приведенному критерию, этого теста достаточно. Однако при этом тестирование по такому критерию не обнаруживает целого ряда ошибок. Так, если во втором условии записать X<1, то эта ошибка не будет обнаружена. Кроме того, здесь возможных путей четыре: abd, abe, acd, ace, тогда как тестируется только один из них.

Этот критерий ввиду его слабости обычно не используют.

Покрытие условий, или ветвевой критерий

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

если ((A=2) Ú (X>1)) то

X:=X+1

кесли;

согласно рассматриваемому критерию достаточно теста A=2, X=2. Но, как и при рассмотрении предыдущего критерия, если во втором условии записать X<1, то эта ошибка не будет обнаружена.

Комбинаторное покрытие условий

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

Например, в конструкции

если ((A=2) Ú (X>1)) то

X:=X+1

кесли;

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

Для сравнения критериев построим для примера из рис. 4.1 все возможные комбинации условий и построим соответствующие тесты (0 соответствует значению «ложь», 1 – «истина».

Подусловия 1-го усл. Подусловия 2-го условия 1-е усл. 2-е усл. Тест Путь
A>1 B=0 X/A>1 A=2 X>1 (A>1)& (B=0) (A=2)Ú (X>1) A B X
      X не меняется, т.к. (A>1)& (B=0) ложь               abd
                    abe
              Не м.б. A<=1 и A=2
              Не м.б. A<=1 и A=2
                    abd
                    abe
              Не м.б. A<=1 и A=2
              Не м.б. A<=1 и A=2
                    abd
                    abe
                    abe
                    abe
                      acd
                      ace
                      ace
                      ace

Возможно, какие-то из приведенных тестов покрывают другие.

Важно заметить, что даже при одном и том же итоговом числе тестов сложность их построения определяется как содержанием задачи, так и стилем записи алгоритма.

Так, для примера на рис. 4.1 сложность составления тестов определяется тем, что второе условие включает переменную X, которая в одних случаях меняется, а в других – нет. Если в условии учитывается измененное значение, то для теста надо восстановить исходное, т.е. подняться вверх по ветвям алгоритма. Вероятно, можно переписать этот фрагмент иначе, но представляется, что это не упростит дела.

Иная ситуация с примером нисходящей разработки из темы 3. Здесь при переходе к анализу очередного данного или к решению задачи в каждом условии явно представлены составляющие его подусловия.. Альтернативный вариант – фиксация верности/ошибочности группы проверенных данных с помощью одной логической переменной – даст простую запись условий (проверку только этой переменой), но эта переменная будет скрывать все предыдущие условия. В данном случае дело спасает нисходящая разработка с параллельным проектированием тестов. При необходимости построить тесты для уже готового алгоритма, использующего описанный прием, придется проходить вверх по ветвям, восстанавливая значения данных для теста.

Подчеркнем важный момент, касающийся организации тестирования.

Понятие «тест» относится к входным (и выходным) данным программы в целом. Поэтому вести речь о тестах для отдельного фрагмента можно только с рядом оговорок. Так, любые данные, на которых тестируется этот фрагмент, для получения теста должны быть преобразованы к входным данным, т.е. пройти процесс обратного преобразования – от имеющихся значений до исходных значений, исходя из которых они были получены (иллюстрация – см. рис. 4.2).

Рис. 4.2. Тестирование программы по частям:
а) процесс преобразования входных данных;
б) формирование тестовых данных программы на основе тестовых данных фрагмента

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

Построения тестов для базовых конструкций

Тестирование сколь угодно сложных конструкций сводится к тестированию базовых конструкций. Уточним требования к соответствующим тестам.

Следование. Вырожденный случай, никаких условий нет. Требуется по крайней мере один тест.

Развилка. Тест должен обеспечить выполнение каждого простого условия. Если условие содержит k простых подусловий, то общее число возможных комбинаций значений подусловий 2K . Реально из этих комбинаций выбрасываются невыполнимые или неинформативные. Так, например, в последнем ветвлении алгоритма из примера нисходящей разработки содержатся 4 подусловия:

1.1, 1.2 (для ветви то) 2.1, 2.2, 3.1, 4.1, 4.2, 5.1 (для ветви иначе) если (nver & mver & bver & aver) {все данные верны} то {решение задачи} {вх: n,b,m,a; вых.: a или maxmin} A0.3. <решение задачи с выводом результатов по обр 3 > иначе {хотя бы одно данное неверно, Ønver Ú Ømver Ú Øbver Ú Øaver } вывод по обр.4.5; кесли;

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

  nver mver bver aver Тестируемая ветвь Комментарий
          иначе Неинформативны
          иначе
          иначе
          иначе
          иначе
          иначе
          иначе
          иначе Тесты 2.1, 2.2
          иначе Неинформативны
          иначе
          иначе
          иначе Тесты 4.1, 4.2
          иначе Неинформативен
          иначе Тест 3.1
          иначе Тест 5.1
          то Тесты 1.1, 1.2

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

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

Основой конструирования тестов является построение таблицы истинности отдельных подусловий и условия в целом. Схема конструирования с примерами приведена в файле test_bas.doc.

Построения тестов для вложенных конструкций

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

Будем при этом говорить о вложенной и объемлющей конструкции.

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

Так, в примере нисходящей разработки из темы 3 для тестирования развилки, вложенной в развилку, на уровне 0 и развилки, вложенной в цикл, на уровне 1 оказывается достаточно подмножества функциональных тестов:

1.1, 1.2, 4.1, 4.2, 5.1 (для всей развилки) 4.1,4.2 (для вложенной развилки) если (nver & bver){предыдущие данные верны} то ввод(m); вывод(m,n)по обр 2.3; {проверка m} mver:=истина; если (m<=0 или m>50) то {m неверно} mver:=ложь; вывод по обр 4.3; кесли; кесли;
Тесты для цикла: 1.1, 1.2, 3.1, 4.1, 4.2, 5.1 Для развилки: 3.1 {проверка массива b} bver:=истина; для i от 1 до n цикл если |b(i)| >10 то вывод (i,b(i)) по обр 4.2; bver:=ложь; кесли; кц;

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


[1] Р. Гласс. Руководство по надежному программированию. – М., Финансы и статистика, 1982. – 256 с.

[2] Здесь видна связь структурного и функционального тестирования, поскольку набор операторов – это элемент структуры программы, которая, по определению функционального тестирования, игнорируется.

[3] Г. Майерс. Искусство тестирования программ. – М., Финансы и статистика, 1982. – 176 с.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: