Главная
Архив новостей
Шаблоны
Студенту
Статьи по стандартам
Файлы
Ссылки
Форум
О проекте
История проекта

Тестирование программных средств

Авторы: Евгений Балыков, Владимир Царёв
Источник: RSDN Magazine #4-2006
1. Введение
2. Дисциплина тестирования программных средств
2.1. Стохастическое тестирование
2.2. Тестирование потоков управления
2.3. Тестирование потоков управления на примере программного обеспечения оптико-электронной системы идентификации объектов подвижного состава железнодорожного транспорта
2.4. Тестирование потока данных
2.5. Тестирование потока данных на примере ПО ОЭСИ ЖТ
2.6. Функциональное тестирование
2.7. Функциональное тестирование на примере ПО ОЭСИ ЖТ
2.8. Мутационное тестирование
2.9. Мутационное тестирование на примере ПО ОЭСИ ЖТ
2.10. Четыре уровня тестирования
2. 11. Тестирование циклических структур
3. Заключение
4. Список литературы

1. Введение

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

ПРИМЕЧАНИЕ

Под дефектом или ошибкой программного обеспечения (ПО) понимается отклонение свойства или поведения некоторого программного объекта от зафиксированного эталонного значения.

Увеличение общего числа ошибок в ПС оказывает негативное влияние на его качество.

ПРИМЕЧАНИЕ

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

На качество ПО оказывает негативное влияние ряд внутренних и внешних дестабилизирующих факторов [1,2]. К внутренним дестабилизирующим факторам можно отнести:

  • системные ошибки при постановке целей и задач создания ПС, дефекты формирования спецификации требований;
  • дефекты проектирования структуры ПС и создания проектных моделей, а также алгоритмические ошибки;
  • ошибки программирования;
  • дефекты верификации и тестирования ПО;
  • дефекты передачи информации с этапа на этап, а также ошибки несоответствия решений, принимаемых на конкретном этапе жизненного цикла (ЖЦ) ПС, выходным данным предыдущей фазы (несоответствие архитектуры и спецификации требований, несоответствие реализации и проектных моделей).

К внешним дестабилизирующим факторам относятся ошибки персонала, искажение данных, сбои аппаратуры.

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

ПРИМЕЧАНИЕ

Легенда о торпеде

Ранние конструкции торпед предполагали самоуничтожение в случае разворота торпеды на 180°, так чтобы торпеда не смогла уничтожить выпустившую её лодку. Легенда гласит, что однажды подобная торпеда застряла в пусковой камере и экипаж не смог её удалить. Капитан решил вернуть лодку в порт для проведения ремонта. Как только подводная лодка развернулась на 180°, торпеда взорвалась и потопила лодку.

Инцидент Therac-25

В 85–87 годах прошлого столетия шесть человек получили смертельную дозу облучения во время сеансов радиационной терапии с применением медицинского ускорителя Therac-25. Модель Therac-25, законченная в виде прототипа в 1976 году и поступившая в промышленную эксплуатацию в 1982 году (пять установок в США и шесть – в Канаде) была развитием ранних моделей Therac-6 и Therac-20. Инцидент вызван целым спектром причин: некорректные принципы построения человеко-машинного взаимодействия; дефекты архитектуры специализированной многозадачной системы (для синхронизации использован механизм разделяемых переменных); переход с аппаратных блокираторов излучения на программные.

Катастрофа Ariane 5

4 июня 1996 года через 40 секунд после запуска ракеты-носителя Ariane 5 произошёл автоподрыв 50-метровой ракеты в районе космодрома во Французской Гвиане. Находившееся на борту научное оборудование потянуло на полмиллиарда долларов, не говоря об астрономических цифрах “упущенной выгоды” от несостоявшихся коммерческих запусков и потере репутации надежного перевозчика. Причиной катастрофы послужил некорректный перенос спецификации программного модуля (ПМ), осуществляющего преобразование из восьмибайтового вещественного в двухбайтовый целый тип данных (double > WORD) из ПО ракеты-носителя Ariane 4 в ПО Ariane 5. Небезынтересно отметить, что предыдущая модель, ракета Ariane 4, успешно запускалась более 100 раз.

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

ПРИМЕЧАНИЕ

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

В области конструирования, обеспечения и контроля качества ПС можно выделить труды таких известных учёных, как: В.Н. Агафонова, А.П. Ершова, С.С. Лаврова, В.В. Липаева, В.А. Непомнящего, Р. Андерсона, Б. Боэма, Э. Дейкстры, Г. Майерса, Р. Флойда, Ч. Хоара.

В [1,2] выделяются два основных подхода обеспечения качества программной продукции: во-первых, использование стандартов, регламентированных технологий и систем обеспечения качества процессов ЖЦ ПС, позволяющих гарантировать высокое качество конструирования создаваемых программ; а во-вторых, использование механизма контроля качества, позволяющего выявить дефекты и ошибки разрабатываемого ПО, рисунок 1.


Рисунок 1. Комплексная модель обеспечения качества ПС.

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

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

2. Дисциплина тестирования программных средств

Процесс тестирования представляет собой интерпретационный подход, направленный на поиск и выявление ошибок в ПО. Отличие тестирования от верификации заключается в том, что процесс тестирования направлен на выявление потенциальных ошибок и не позволяет гарантировать их отсутствие в нетривиальных программах, в то время как механизм аналитической верификации способен гарантировать правильность ПС. Дисциплина тестирования программных приложений активно развивается, что подтверждает достаточно большое число монументальных изданий [3,4,6,7,9].

ПРИМЕЧАНИЕ

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

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


либо если отличаются законы распределения выходных данных, рисунок 2.


Рисунок 2. Графики зависимостей результирующих и эталонных выходных данных от входного набора.

В обоих случаях считается, что тестирование прошло успешно, поскольку выявлена как минимум одна ошибка.

Мощность тестовых наборов определяется в соответствии с выбранными критериями тестирования. Классификация основных критериев тестирования приведена на рисунке 3.


Рисунок 3. Классификация критериев тестирования.

2.1. Стохастическое тестирование

Стохастическое тестирование основано на генерации тестовых наборов, а именно множества X, случайным образом. Стохастическое тестирование выполнимо, если удаётся автоматически и независимым образом определить эталонное множество Yэт или экспертно указать распределение выходных данных. Как правило, к стохастическому критерию прибегают в случае необходимости построения тестовых наборов большой мощности. Недостаток стохастического тестирования заключается в малой вероятности получения оптимального тестового набора, то есть набора, обладающего высокой обнаруживающей способностью.

2.2. Тестирование потоков управления

К наиболее трудоёмким критериям относятся так называемые структурные критерии или критерии тестирования потоков управления.

ПРИМЕЧАНИЕ

Тестирование потоков управления – это тестирование программы в соответствии с принципом “белого ящика”, при котором известна структура программы и доступен исходный код.

Тестирование в соответствии со структурным критерием предполагает тестирование реализации (кода программы), причём тестовая оснастка реализуется в теле проверяемого элемента программной системы. Структурное тестирование производится по управляющему графу программы (УГП).

ПРИМЕЧАНИЕ

УГП называется орграф вида G = (V, U, p0, q0), где V - множество вершин (операторов), U - множество рёбер (управлений), p0, q0 – выделенные начальная и конечная вершины.

Выделяют ряд частных критериев, используемых при тестировании потоков управления. Самый слабый структурный критерий – тестирование команд (критерий C0), при котором тестовый набор должен обеспечить выполнение каждого оператора хотя бы один раз. Наилучшим критерием в соотношении “обнаруживающая способность – экономичность” является критерий тестирования ветвей (критерий C1). Данный критерий обеспечивает исполнение каждой ветви хотя бы один раз.

ПРИМЕЧАНИЕ

Под ветвью УГП программы понимают последовательность вершин v1, …, vk (# V пути, где v1 - либо первый оператор пути, либо условный оператор, vk - либо последний оператор пути, либо условный оператор, а все вершины v2, …, vk-1 - безусловные операторы.

И наиболее эффективный и трудоёмкий критерий – тестирование маршрутов (критерий C2). Тестовые наборы, построенные в соответствии с данным критерием, должны обеспечивать исполнение каждого пути программы как минимум один раз.

ПРИМЕЧАНИЕ

Путём (маршрутом) называется такая последовательность вершин v1,, vi, vi+1, …, vn, (# V, что каждая пара соседних вершин vi, vi+1 определяет дугу (ребро) в орграфе, дуги которого ориентированы в направлении v1 -> vk.

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

2.3. Тестирование потоков управления на примере программного обеспечения оптико-электронной системы идентификации объектов подвижного состава железнодорожного транспорта

Рассмотрим процесс тестирования ПО в соответствии с критериями потоков управления (критерии С0, С1, С2, условий и ветвей-условий) на примере программного модуля оптико-электронной системы идентификации (ОЭСИ) объектов подвижного состава железнодорожного транспорта (ЖТ), вычисляющего модуль разности яркостей точек фона и текущего кадра.

ПРЕДУПРЕЖДЕНИЕ

Для демонстрации методов тестирования в данной статье использованы модифицированные для этой цели примеры из процесса разработки и тестирования программной оптико-электронной системы идентификации объектов подвижного состава железнодорожного транспорта, разработанной институтом менеджмента и информационных технологий Санкт-Петербургского государственного политехнического университета [5].

Функция вычисления модуля разности яркостей используется в программе динамической корректировки фонового кадра ОЭСИ ЖТ. Назначение данной функции состоит в определении величины рассогласования между соответствующими точками фона и текущего кадра с учётом порога нечувствительности. Программная реализация в стандарте C++ и управляющий граф указанной функции имеют вид:

BYTE CClass::GetDev(BYTE bkgr,BYTE frame) const
{
   // Порог нечувствительности
   const BYTE threshold = 5;
   BYTE result; //0

   if (bkgr >= frame) //1
      result = bkgr  - frame; //2
   else
      result = frame - bkgr; //3

   if (result < threshold) //4
      result = 0; //5

   return result; //6
}


Рисунок 4. УГ ПМ ОЭСИ, осуществляющего вычисление модуля разности яркостей точек фона и текущего кадра.

Тестовый набор, сформированный в соответствии с критерием покрытия команд, имеет вид (X, Yэт) = {(9,5,0), (5,9,0)}. В тестируемой по данному набору программе исполняются все команды и выполняются следующие пути:


Тестовый набор, сформированный в соответствии с критерием покрытия ветвей, имеет вид (X, Yэт) = {(9, 5, 0), (0, 255, 255)}. В тестируемой по данному критерию программе исполняются все ветви, и задействуется следующее множество маршрутов:


Тестовый набор, сформированный в соответствии с критерием покрытия маршрутов, имеет вид (X, Yэт) = {(9, 5, 0), (0, 255, 255), (255, 0, 255), (5, 9, 0)}.

В тестируемой по данному критерию функции исполняются все маршруты:


Рассмотрим процесс тестирования модифицированного метода, вычисляющего величину рассогласования яркостей соответствующих точек фона и текущего кадра, по критериям условий и ветвей-условий. Программная реализация и управляющий граф изменённой функции, содержащей составные операторы условий, выглядят так:

BYTE CClass::GetDev(BYTE bkgr,BYTE frame) const
{
   // Порог нечувствительности
   const BYTE threshold = 5;

   BYTE result = 0; //0

   if ( (abs(frame-bkgr) >= threshold)&&(bkgr >= frame) ) //1
      result = bkgr - frame; //2

   if ( (abs(frame-bkgr) >= threshold)&&(bkgr < frame) ) //3
      result = frame - bkgr; //4

   return result; //5
}


Рисунок 5. УГ модифицированного ПМ ОЭСИ, осуществляющего вычисление модуля разности яркостей точек фона и текущего кадра.

Для тестирования условий необходимо протестировать все возможные варианты условий в данной программе, то есть условия вида:

(f2)

для оператора 1 и


для оператора 3. Тестовый набор достаточный для покрытия указанных условий имеет вид (X, Yэт) = {(15, 10, 5), (8, 10, 0)}. В тестируемой по данному критерию программе исполняются все условия, и задействуется следующее множество маршрутов:


Тестовый набор, сформированный в соответствии с критерием ветвей-условий, имеет вид (X, Yэт) = {(15, 10, 5), (10, 15, 5), (8, 10, 0)}. В тестируемой по данному критерию программе исполняются все ветви и условия, и задействуется следующее множество маршрутов:


2.4. Тестирование потока данных

Функционирование любой программы можно представить как обработку потока данных, передаваемых от входа программы к её выходу.

ПРИМЕЧАНИЕ

Более формализовано программа есть функция от входных данных, определяющая множество значений выходных данных Y = F(X), где X – множество входных параметров, Y – множество выходных параметров, а F = f1 … fn - программа, представляющая собой суперпозицию операторов fi, 1<=i<=n [8].

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

ПРИМЕЧАНИЕ

Под информационным графом понимается УГП, который дополнен потоком данных участвующих в вычислениях.

Выделяют три критерия тестирования потоков данных:

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

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

ПРИМЕЧАНИЕ

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

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

2.5. Тестирование потока данных на примере ПО ОЭСИ ЖТ

Рассмотрим процесс тестирования ПО в соответствии с критериями потоков данных (выделение областей переменных, определение и использование данных, корректность и точность результатов) на примере ПМ ОЭСИ объектов подвижного состава ЖТ, формирующего интервальную гистограмму. Метод формирования дискретной (интервальной) гистограммы межкадровой разности используется в рамках технологии обработки изображений для обнаружения объектов подвижного состава. Гистограмма межкадровой разности характеризует распределение разности яркостей точек двух кадров в заданном диапазоне. В процессе дискретизации гистограммы выделяются укрупнённые интервалы на всём диапазоне, каждый из которых содержит суммарное число элементов, принадлежащих данному интервалу. Интервальная гистограмма позволяет сократить число параметров, на основе анализа которых принимается решение об обнаружении движения, и соответственно повысить общее быстродействие. К основным этапам процесса построения интервальной гистограммы для полутоновых изображений (256 градаций серого) относятся:

  • определение границ линейных интервалов дискретизации где n - число интервалов дискретизации;
  • квадратичное уточнение границ
  • вычисление межкадровой разности со знаком, (f6)
    где m - число точек кадра, xi - яркости соответствующих пикселей первого и второго кадров.
  • построение интервальной диаграммы.

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

void CClass::MakeHistogram(BYTE x1,BYTE x2)
{

   int g1,g2,g3,g4;
   const BYTE intervals = 3;
   int dif = x1 - x2; // 0
	
   // Интервалы равной длины
   g1 = -255+510*0/intervals; 
   g2 = -255+510*1/intervals;
   g3 = -255+510*2/intervals;
   g4 = -255+510*3/intervals;   //1

   // Нелинейное уточнение границ интервалов
   g1=g1*g1/255;
   g2=g2*g2/255;
   g3=g3*g3/255;
   g4=g4*g4/255; //2

   // Определение гистограммы
   if ( (dif >= g1)&&(dif < g2) )    //3
      m_histogram[0]++;       //4

   if ( (dif >= g2)&&(dif <= g3) )  //5 
      m_histogram[1]++;        //6

   if ( (dif > g3)&&(dif <= g4) )     //7
      m_histogram[2]++;        //8
}


Рисунок.6. Управляющий и информационный графы метода формирования интегральной гистограммы.

Представленная реализация содержит ряд ошибок (выделено курсивом). На информационном графе отображается время жизни данных в программе от момента их определения до последнего момента их использования. Отметим, что переменные g1, g2, g3, g4 разделяют область определения параметра межкадровой разности и выходных значений дискретной гистограммы на три области:

(f7)

Для проверки маршрутов, связанных с данными областями, необходимо из критических точек сформировать тестовый набор вида: : (X,Yэт) = { (0,255,(1,0,0)), (0,29,(2,0,0)), (0,28,(2,1,0)), (28,0,(2,2,0)), (29,0,(2,2,1)), (255,0,(2,2,2)) } где тройка эталонных значений служит для проверки гистограммы из трёх интервалов. Прогон программы уже на первом тестовом случае выдаст результат вида (0,0,0), несовпадающий с эталонным значением, что косвенно указывает на наличие дефекта в первом условии. В результате отладки будет найдена ошибка, связанная с пропуском исходного знака значения границы в процессе квадратичного уточнения границ.

Тестирование в соответствии с критерием определения-использования предполагает формирование тестового набора осуществляющего проверку цепочек определения-использования.

ПРИМЕЧАНИЕ

Под цепочкой определения-использования переменной понимают отрезок маршрута начинающийся в операторе определения данной переменной и заканчивающийся в одном из операторов использования.

В указанном примере целесообразно выделение следующих цепочек определения-использования: [intervals,0,1], [dif,0,3], [dif,0,5], [dif,0,7], [g1-g2-g3-g4,1,2], [g1,2,3], [g2,2,3], [g2,2,5], [g3,2,5], [g3,2,7], [g4,2,7].

Для проверки указанных цепочек необходимо исполнить маршруты, содержащие данные отрезки. Тестовый набор, покрывающий маршруты и соответствующие цепочки имеет вид: (X,Yэт) = { (0,128,(1,0,0)), (0,0,(1,1,0)), (128,0,(1,1,1)) }.

Сформированный тестовый набор позволит исполнить все выделенные цепочки и проверить внешние переменные. Для проверки значений внутренних переменных в отладочных версиях ПМ удобно использовать операторы утверждений (ASSERT(predicate)):

void CClass::OnStructTest()
{
   // Тестирование определения-использования
   ::ZeroMemory(m_histogram,3*sizeof(DWORD));
   MakeHistogram(0,128); 
   ASSERT( (m_histogram[0] == 1)&&(m_histogram[1] == 0)&&(m_histogram[2] == 0) );
   MakeHistogram(0,0);
   ASSERT( (m_histogram[0] == 1)&&(m_histogram[1] == 1)&&(m_histogram[2] == 0) );
   MakeHistogram(128,0);
   ASSERT( (m_histogram[0] == 1)&&(m_histogram[1] == 1)&&(m_histogram[2] == 1) );
}

void CClass::MakeHistogram(BYTE x1,BYTE x2)
{
   int g1,g2,g3,g4; 
   const BYTE intervals = 3;
   int dif = x1 - x2; // 0

   // Интервалы равной длины
   g1 = -255+510*0/intervals;
   g2 = -255+510*1/intervals;
   g3 = -255+510*2/intervals;
   g4 = -255+510*3/intervals; //1
   ASSERT(intervals == 3); // [intervals,0,1]
   ASSERT(g1 == -255);
   ASSERT(g2 == -85);
   ASSERT(g3 == 85);
   ASSERT(g4 == 255);

   // Нелинейное уточнение границ интервалов
   g1 = Sign(g1)*g1*g1/255;
   g2 = Sign(g2)*g2*g2/255;
   g3 = Sign(g3)*g3*g3/255;
   g4 = Sign(g4)*g4*g4/255; //2

   // Определение гистограммы
   if ( (dif >= g1)&&(dif < g2) )   m_histogram[0]++;
   if ( (dif >= g2)&&(dif <= g3) )  m_histogram[1]++;
   if ( (dif > g3)&&(dif <= g4) )   m_histogram[2]++;

   // Проверка значенний межкадровой разности
   if ( (x1 == 0)&&(x2 == -128) ) ASSERT(dif == -128);
   if ( (x1 == 0)&&(x2 == 0) )    ASSERT(dif == 0);
   if ( (x1 == 128)&&(x2 == 0) )  ASSERT(dif == 128);

   // Проверка значенний границ интервалов
   ASSERT(g1 == -255);
   ASSERT(g2 == -28);
   ASSERT(g3 == 28);
   ASSERT(g4 == 255);
}

В представленном листинге исправлены ошибки нелинейного уточнения границ. Отметим, что исполнение программы на заданном тестовом наборе не выявляет ошибок, однако при совместном исполнении тестирования областей и определения-использования последний критерий позволяет более точно локализовать предыдущий дефект программы. Как видно из листинга, тестовая оснастка для проверки внутренних переменных при тестировании определения-использования реализуется непосредственно в коде проверяемого метода.

Тестирование корректности обработки и точности результатов осуществляется вслед за первыми двумя критериями тестирования. Критерий проверки корректности результатов основан на проверке особых точек: максимальные, минимальные, нулевые значения, промежуточные значения, точки резкого возрастания или разрыва производных. Тестовый набор, сформированный по указанному критерию, имеет вид: (X,Yэт) = { (0,255,(1,0,0)), (255,255,(1,1,0)), (255,0,(1,1,1)), (0,128,(2,1,1)), (128,0,(2,2,1)) }.

Ввиду достаточно полного тестирования по первым двум критериям данный набор не выявит ошибок. Однако, если бы входные параметры относились к вещественным типам, то критерий проверки корректности обработки и точности результатов позволил бы выявить потенциальные ошибки, связанные с приведением типов.

2.6. Функциональное тестирование

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

Одна из методик определения “оптимального” тестового набора заключается в разбиении входной области определения и выходной области значений на конечное число классов эквивалентности. Если один тестовый случай данного класса эквивалентности обнаруживает ошибку, то и все другие тестовые случаи этого же класса эквивалентности также будут обнаруживать ту же самую ошибку. И наоборот, если тестовый случай данного класса эквивалентности не обнаруживает ошибку, то остальные тестовые случаи данного класса также не будут обнаруживать данную ошибку. Различают два типа классов эквивалентности: правильные классы эквивалентности, включающие корректные (правильные) данные и неправильные классы эквивалентности, охватывающие ошибочные данные.

Граничные условия – это ситуации, возникающие как непосредственно на границах классов эквивалентности, так и на значениях выше или ниже данных границ. Тестирование программ, осуществляется как на граничных значениях, так и вблизи заданных границ. Как показывает практика, тестирование классов эквивалентности и тестирование граничных значений используется как единый комбинаторный критерий. Основным недостатком критериев эквивалентных классов и граничных значений является то, что они не исследуют всех возможных комбинации множества входных значений.

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

  1. Анализ спецификации. Определяются причины (классы эквивалентности) и следствия (выходное условие или преобразование системы), уточняются их отношения.
  2. Построение булевого графа по результатам анализа. Визуализация причин, следствий и множества отношений между ними.
  3. Формирование таблицы решений. Преобразование булевого графа в таблицу решений путём методического прослеживания состояний условий диаграммы.
  4. Генерация тестового набора по столбцам таблицы решений.

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

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

2.7. Функциональное тестирование на примере ПО ОЭСИ ЖТ

Продемонстрируем тестирование ПО ОЭСИ ЖТ в соответствии с рядом функциональных критериев на примере функции линейной интерполяции, позволяющей масштабировать изображения. Результат линейной интерполяции представлен на рисунке 7.


Рисунок 7. Линейная интерполяция изображения. а) Исходное изображение и интерполированное изображение, сжатое в четыре раза. б) Особые точки процесса интерполяции.

В процессе интерполяции осуществляется перебор всех точек выходного изображения и определение соответствующих им координат на исходном изображении. В простейшем случае, необходимо учитывать четыре варианта расположения искомой точки на исходном изображении (рисунок 8-б): искомая точка совпадает с некоторой точкой (узлом) исходного изображения; точка находится на абсциссе или ординате; и точка попадает в квадрат, образованный смежными точками исходного изображения. Спецификация упрощённой реализации метода интерполяции имеет вид:


Входными данными являются координаты (x_in, y_in) точки на исходном изображении, а выходные данные представляют собой значение яркости в интерполируемой точке. В данном случае, рассматривается упрощённый метод интерполяции, в котором отсутствует цикл прохода по всем точкам формируемого изображения, а также рассматривается только первая решётка сетки исходного изображения (в целях простоты изложения и демонстрации, будем считать, что исходное изображение имеет размер 2x2 точки). Сформируем тестовый набор и приведём листинг функционального тестирования функции линейной интерполяции в соответствии с тестированием классов эквивалентности. Классы эквивалентности для метода линейной интерполяции представлены в таблице.1.

Правильные классы Неправильные классы
Входные данные 0<=x, y<=1 x,y<0
x,y>1
Выходные данные 0<=z<=255 Исключение
Таблица 1. Классы эквивалентности для функции линейной интерполяции.

Тестовый набор, сформированный в соответствии с критерием разбиения на классы эквивалентности, имеет следующий вид: (X,Yэт) = { (0.5,0.5,157), (2,2,100), (-0.5,-0.5,Исключение) }. Процедура функционального тестирования, по критерию классов эквивалентности, имеет вид:

<<.h>>

class CClass 
{
   void OnFunctionalTest();
};

<<.cpp>>

void CClass::OnFunctionalTest()
{
   // Значения яркости в узлах данного квадрата сетки
   image[0][0] = 150;
   image[0][1] = 180;
   image[1][0] = 200;
   image[1][1] = 100;

   BYTE result = Linear(0.5,0.5);
   ASSERT(result == 157);
   result = Linear(2,2);
   ASSERT(result == 100);

   wstring msg;
   // Проверка неправильного класса входных и выходных данных
   try
   {
      Linear(-0.5,-0.5);
   }
   catch(CImageException)
   {
      msg = TEXT("Неверные входные данные");
   }
   ASSERT(msg == TEXT("Неверные входные данные"));
}

Следует заметить, что класс x,y > 1 относится к правильным классам, так как в этом случае считается, что точка принадлежит правой границе рассматриваемого квадрата изображения. Тестовые наборы данного примера обеспечивают одновременную проверку, как входных, так и выходных классов данных.

Тестовый набор для функции линейной интерполяции, сформированный по критерию граничных значений, имеет вид: (X,Yэт) = { (0,0,150), (-0.1, -0.1, Исключение), (0.1,0.1,156), (0.9,0.9,116), (1,1,100), (1.1,1.1,100) }.

В свою очередь, процедура функционального тестирования, в соответствии с критерием тестирования граничных значений, имеет вид:

void CClass::OnFunctionalTest()
{
   // Значения яркости в узлах данного квадрата сетки
   image[0][0] = 150;
   image[0][1] = 180;
   image[1][0] = 200;
   image[1][1] = 100;

   // Тестирование граничных значений
   wstring msg;
   BYTE result = Linear(0,0);
   ASSERT(result == 150); 
   try 
   { 
      result = Linear(-0.1,-0.1); 
   } 
   catch (CImageException)
   {
      msg = TEXT("Неверные входные данные"); 
   }
   ASSERT(msg == TEXT("Неверные входные данные"));

   result = Linear(0.1,0.1);
   ASSERT(result == 156);
   result = Linear(0.9,0.9);
   ASSERT(result == 116);
   result = Linear(1,1);
   ASSERT(result == 100);
   result = Linear(1.1,1.1);
   ASSERT(result == 100);	
}

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

Причины Следствия
1 0<=x<=1
2 x>1
3 0<=y<=1
4 y>1
5 x<0
6 y<0
P Решение
И Исключение
Таблица 2. Перечень причин и следствий, сформированных для функции линейной интерполяции

На втором этапе, на основании сформированных причин и следствий строится булевый граф, представленный на рисунке 8. Все исходные причины изображены в левом столбце, а следствия в правом. Для соединения причин и следствий вводятся промежуточные вершины графа и в качестве ветвей используются отношения булевой алгебры. Как правило, причины и следствия могут иметь два состояния 0 или 1.


Рисунок 8. Булевый граф функции линейной интерполяции.

На третьем шаге, на основе сформированного булевого графа, осуществляется построение таблицы решений, в которой каждому столбцу соответствует определённый тест. Процесс синтеза таблицы решений выглядит следующим образом:

  • выбор следствия в единичном состоянии;
  • определение всех допустимых комбинации причин, путём прокладывания обратных трасс, приводящих выбранное следствие в “сигнальное” состояние;
  • фиксация комбинаций в столбцах таблицы решений.

Построенная в соответствии с указанной методикой таблица решений для ПМ ОЭСИ ЖТ, реализующего функцию линейной интерполяции, представлена в таблице 3.

1 2 3 4 5 6
1 1 0 1 0
2 0 1 0 0
3 1 1 0 0
4 0 0 1 1
5 0 0 0 0 1 0
6 0 0 0 0 0 1
7 1 1 1 1
8 1 1 1 1
9 0 0 0 0 1 1
Р 1 1 1 1 0 0
И 0 0 0 0 1 1
Таблица 3. Таблица решений, сформированная для функции линейной интерполяции

На четвёртом и заключительном этапе, по таблице решений формируется набор входных воздействий, следующего вида: (X) = { (0.5,0.5), (2, 0.5), (0.5,2), (2,2), (-0.1, (y_in), ((x_in: x_in>=0, -0.1) }. Полученный тестовый набор входных данных дополняет функциональное тестирование по критерию классов эквивалентности и указывает на недостаточную эффективность тестового случая (-0.5,-0.5), используемого в тестировании классов эквивалентности.

Для демонстрации тестирования ПО ОЭСИ ЖТ в соответствии с критерием тестирования функций рассмотрим проверку методов класса матрицы. Класс матриц активно применяется в технологии обработки и анализа изображений, и в частности, используется для моделирования областей контроля и потока движущихся объектов контроля. Упрощённая реализация класса матриц и тестирование его экземпляров, в соответствии с критерием тестирования функций, имеет вид:

<<.h>>

class CMatrix
{
public:
   CMatrix();
   CMatrix(double* pdata); // Конструктор инициализирующий матрицу значениями
   bool SetData(double*);   // Установка значений матрицы.
   double* GetData() const; // Получение массива элементов матрицы
   int GetElementsQuantity() const; // Получение количества элементов в матрице
   void    Transpone();     // Транспонирование матрицы
   double Determinant(); // Расчёт определителя
   double Spur() const;    // След матрицы

   // Перегрузка операторов
   bool operator==(const CMatrix&) const;
   bool operator!=(const CMatrix&) const;
   CMatrix  operator+(const CMatrix&) const;
   CMatrix  operator-(const CMatrix&) const;
   CMatrix  operator*(const CMatrix&) const;
};

<<.cpp>>

void CClass::TestFunctions()
{
   int i, double result;
   double *pdata = new double[16];

   for ( i = 0 ; i < 16; ++i) 
      pdata[i] = i;
   CMatrix a(pdata); // Инициализация элементов через конструктор

   result = a.Spur();
   ASSERT(result == 30.0); // Проверка следа матрицы

   result = a.Determinant();
   ASSERT(result == 0.0); // Проверка определителя

   a.Transpone(); // Транспонирование
   // Проверочные данные метода транспонирования
   pdata[0] = 0;
   for ( i = 1; i < 4; ++i)
      pdata[i]=pdata[i-1]+4;  
   pdata[4] = 1;
   for ( i = 5; i < 8; ++i)
      pdata[i]=pdata[i-1]+4;
   pdata[8] = 2; 
   for ( i = 9; i < 12; ++i)
      pdata[i]=pdata[i-1]+4;
   pdata[12] = 3;
   for ( i = 13; i < 16; ++i)
      pdata[i]=pdata[i-1]+4;
   CMatrix b;
   b.SetData(pdata); // Инициализация элементов через метод
   ASSERT(a == b);
   ASSERT( !(a != b) ); // Проверка равенства/неравенства и транспонирования

   for (i = 15; i >= 0; --i)
      pdata[15-i] = i;
   b.SetData(pdata);
   a = a + b; // Сложение матриц
   // Проверочные данные метода сложения
   pdata[0] = 15, pdata[1] = 18, pdata[2] = 21, pdata[3] = 24; pdata[4] = 12, pdata[5] = 15, pdata[6] = 18, pdata[7] = 21;
   pdata[8] = 9,  pdata[9] = 12, pdata[10] = 15, pdata[11] = 18; pdata[12] = 6, pdata[13] = 9, pdata[14] = 12, pdata[15] = 15;
   b.SetData(pdata);
   ASSERT(a == b); // Проверка сложения

   a = a - b;    // Вычитание матриц
   // Проверочные данные для вычитания. Неявная проверка сервисных 
   // методов получения указателя на данные и определения числа элементов.
   ::ZeroMemory(b.GetData(),b.GetElementsQuantity()*sizeof(double));
   ASSERT(a == b); // Проверка вычитания

   // Проверочные данные для умножения
   for ( i = 0; i < 16; ++i) 
      pdata[i] = i;
   a.SetData(pdata);
   for (i = 15; i >= 0; --i) 
      pdata[15-i] = i;
   b.SetData(pdata);
   a = a*b;
   // Проверочные данные метода умножения
   pdata[0] = 34, pdata[1] = 28, pdata[2] = 22, pdata[3] = 16; pdata[4] = 178, pdata[5] = 156, pdata[6] = 134, pdata[7]  = 112;
   pdata[8] = 322, pdata[9] = 284, pdata[10] = 246, pdata[11] = 208; pdata[12] = 466, pdata[13] = 412, pdata[14] = 358, data[15] = 304;
   b.SetData(pdata);
   ASSERT(a == b); // Проверка умножения

   delete[] pdata;
}

Как показывает спецификация (в данном случае “.h” файл), класс реализует основные операции работы с квадратными матрицами размерностью 4x4. К таким операциям относятся методы нахождения определителя, следа матрицы, операция транспонирования, функции сложения, вычитания и умножения матриц (в виде перегруженных операторов). Кроме того, специфицированы дополнительные функции сравнения матриц и ряд методов доступа к данным. Инициализация значений элементов матриц может производиться, как в конструкторе, так и в функции установки данных. Для проверки работы специфицированных методов предварительно определяются эталонные выходные множества. Тестирование функций производится последовательно начиная с базовых методов, не включающих вызовов других методов, и заканчивая всё более сложными процедурами, зависящими от корректности базовых методов. В данном примере, для уменьшения объёма тестовой оснастки, осуществлено совмещение проверки “одноранговых” методов в едином тестовом операторе (например, в операторе “ZeroMemory” осуществляется одновременное тестирование сервисных методов, отвечающих за считывание количества элементов и получение указателя на данные матрицы). Для простоты демонстрации используются целочисленные значения, формируемые с помощью циклов. Для демонстрации тестирования ПО ОЭСИ ЖТ в соответствии с критерием тестирования спецификаций рассмотрим фильтр Робертса, используемого в ПМ ОЭСИ ЖТ для определения перепадов яркости и последующего выделения границ объектов контроля. Спецификация ПМ ОЭСИ ЖТ, осуществляющего фильтрацию Робертса, представлена в таблице 4.

Вход Выход
Нет изображения Функция возвращает false
Полутоновое изображение. Все точки одной яркости (однотонное изображение) Исходный кадр заменяется однотонным изображением, с яркостью каждой точки = 255 (полностью “белый” кадр). Функция возвращает true
Полутоновое изображение. Изображение содержит перепады яркости Исходный кадр заменяется полутоновым изображением с выделенными границами (яркость граничных значений отлична от 0). Функция возвращает true
Таблица 4. Спецификация ПМ ОЭСИ ЖТ, реализующего фильтр Робертса
ПРИМЕЧАНИЕ

Примечание: фильтрация не осуществляется для точек принадлежащих рамке кадра

Спецификация регламентирует тестирование случаев отсутствия изображения на входе ПМ; наличия однотонного изображения на входе; входного эталонного изображения, для которого заранее известен результат фильтрации. Проверка каждого пункта требований и тестовая оснастка проверки фильтра Робертса, в соответствии с критерием тестирования спецификации, имеет вид:

void CClass::OnFunctionalTest()
{
   // Проверка отсутсвия входного изображения
   CFrame frame;
   bool res = RobertsFilter(NULL);
   ASSERT(!res);

   // Установка интенсивностей всех точек кадра = 128
   memset(frame.GetData(),128,sizeof(BYTE));
   RobertsFilter(frame.GetData());
   // Проверка фильтрации серого изображения 
   for ( int i = 1; i < abs((int)frame.GetHeight()); ++i)
      for (int j = 0; j < frame.GetWidth()-1; ++j)
      {
         BYTE data = frame.GetData()[j+i*frame.GetWidth()];
         ASSERT(data == 255);
      }

    // Проверка фильтрации эталонного изображения
   memcpy(frame.GetData(),GenerateModel(),frame.GetPixelQuantity());
   RobertsFilter(frame.GetData());
   for (int i = 0; i < frame.GetPixelQuantity(); ++i)
   {
      BYTE res_data   = frame.GetData()[i];
      BYTE model_data = IsModel(i);
      ASSERT (res_data == model_data);
   }
}

В представленном листинге осуществляется проверка каждого пункта спецификации требований, выдвигаемых к фильтру Робертса. Вначале осуществляется проверка на отсутствие кадра. Затем производится фильтрация однотонного изображения M вида:


где n,m – размерность M, и выходной результат сравнивается с однотонным белым изображением, то есть изображением M вида:


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

2.8. Мутационное тестирование

Мутационный критерий основывается на искусственном внесении ошибок в программу. В мутационном критерии принимается допущение о том, что программисты изначально пишут почти корректные программы, отличающиеся от правильных незначительными ошибками в арифметических операциях, перестановками индексов, некорректными границами циклов, неверными константными значениями. Для исправления дефектов подобного рода, в программу вносятся мелкие ошибки (мутации). Программы, отличающиеся от исходных программ, искусственно внесёнными ошибками называют мутантами. Как правило, мутант отличается от исходной программы небольшим числом мутаций. В исходной программе могут подвергаться мутациям участки кода связанные с перечисленными выше дефектами (изменяются значения переменных, модифицируются индексы и границы циклов, вносятся мутации в условия). Таким образом, из первоначальной программы путём внесения n числа мутаций получают k мутантов, n>=k (как минимум одна мутация на одного мутанта). Если сформированное множество тестовых наборов выявляет все мутации во всех мутантах, то оно соответствует мутационному критерию. Если тестирование исходной программы на заданном множестве тестовых наборов не выявило ошибок, то программа объявляется корректной. В случае мутационного тестирования важно создать такое число мутантов, которое бы охватывало все возможные участки проявления ошибок.

2.9. Мутационное тестирование на примере ПО ОЭСИ ЖТ

Продемонстрируем использование мутационного критерия на примере процедуры бинаризации изображений. Процедура бинаризации активно используется в технологии обработки и анализа изображений и предназначена для отделения изображения объектов от фона. Листинг ПМ ОЭСИ ЖТ, содержащий ошибку инициализации цикла (выделено курсивом и красным цветом), имеет вид:

void CClass::Binary(BYTE* pdata) // Исходный метод
{
   const BYTE threshold = 5;

   for (DWORD i = 1; i < 4; ++i)
      if (pdata[i] >= threshold) 
         pdata[i] = 0;
      else 
         pdata[i] = 255;
}

void CClass::BinaryMutant1(BYTE* pdata) // Метод мутант 1
{
   const BYTE threshold = 5;

   for (DWORD i = 1; i < 3; ++i)
      if (pdata[i] >= threshold) 
         pdata[i] = 0;
      else 
         pdata[i] = 255;
}

void CClass::BinaryMutant2(BYTE* pdata) // Метод мутант 2
{
   const BYTE threshold = 5;

   for (DWORD i = 1; i < 4; ++i)
      if (pdata[i] > threshold) 
         pdata[i] = 0;
      else 
         pdata[i] = 255;
}

В рамках мутационного тестирования в исходный код посеяны ошибки условия цикла (мутант 1) и сравнения с порогом (мутант 2). Тестовый набор, выявляющий все посеянные ошибки, имеет следующий вид:

(f9)

где yэт - эталонные данные для первого (M1), второго (M2) мутантов и исходной функции. Тестирование всех трёх реализаций процедуры бинаризации по сформированному тестовому набору позволяет не только выявить все мутации, но и обнаружить ошибку в исходном ПМ ОЭСИ ЖТ. Листинг мутационного тестирования всех трёх методов на заданном тестовом наборе имеет вид:

void CClass::OnFunctionalTest()
{
   // Мутационное тестирование
   bool is_error = false;
   BYTE data_save[4];
   BYTE input_data[4]     = {4,5,6,7};
   BYTE output_binary[4]  = {255,0,0,0};
   BYTE output_mutant1[4] = {255,0,0,7};
   BYTE output_mutant2[4] = {255,255,0,0};

   // Тестирование первого мутанта
   memcpy(input_data, data_save,4);
   BinaryMutant1(input_data);
   // Если эталонные данные, учитывающие ошибку, не совпали с полученными,
   // то есть ещё ошибка
   if (memcmp(input_data,output_mutant1,4) != 0) 
      is_error = true;

   // Тестирование второго мутанта
   memcpy(input_data,data_save,4);
   BinaryMutant2(input_data);
   // Если эталонные данные, учитывающие ошибку, не совпали с полученными,
   // то есть ещё ошибка
   if (memcmp(input_data,output_mutant2,4) != 0) 
      is_error = true;

   // Тестирование основной реализации
   memcpy(input_data,data_save,4);
   Binary(input_data);
   // Если эталонные данные, учитывающие ошибку, не совпали с полученными,
   // то есть ещё ошибка
   if (memcmp(input_data,output_binary,4) != 0) 
      is_error = true;

   ASSERT(!is_error);
}

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


что свидетельствует о несовпадении реальных и ожидаемых значений не только на исходной реализации процедуры бинаризации, но также и непосредственно на мутантах, даже, несмотря на то, что ожидаемые выходные данные для мутантов рассчитывались с учётом посеянных ошибок. Подобное несовпадение результатов свидетельствует о дефекте в исходном ПМ ОЭСИ ЖТ, реализующего процедуру бинаризации.

2.10. Четыре уровня тестирования

Критерии тестирования применяются на систематической основе в рамках различных уровней тестирования. К существующим видам тестирования относятся: модульное, интеграционное и системное тестирование.

ПРИМЕЧАНИЕ

Модульное тестирование - это тестирование отдельно взятых программных модулей и классов.

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

ПРИМЕЧАНИЕ

Интеграционное тестирование - это тестирование взаимодействия частей ПС.

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

Монолитный способ характеризуется независимым тестированием каждого модуля или класса, после которого модули комбинируются в единую программную систему. Для тестирования каждого класса при монолитном тестировании необходимо разрабатывать драйверы и заглушки. Драйвер осуществляет управление тестируемым классом, передаёт ему тестовые наборы и фиксирует результаты, а заглушки имитируют работу классов, от которых зависит тестируемый модуль.

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

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

Продемонстрируем интеграционное тестирование на примере ПО ОЭСИ ЖТ. Для удобства демонстрации, рассмотрим небольшую часть ПО ОЭСИ ЖТ, модульная структура которой представлена на рисунке 9.


Рисунок 9. Модульная структура ПО ОЭСИ ЖТ.

Для монолитного интеграционного тестирования модулей П0, М2_1, М3 потребуется разработать драйверы и заглушки представленные на рисунке 10.


Рисунок 10. Монолитное тестирование. а) Проверка управляющего модуля П0. б) Проверка модуля 2_1. в) Проверка модуля 3.

Процесс нисходящего интеграционного тестирования ПО ОЭСИ ЖТ представлен на рисунке 11.


Рисунок 11. Нисходящее интеграционное тестирование. а) Проверка П0. б) Проверка М2 и М3. в) Проверка М2_1 и М3_1.

При нисходящем интеграционном тестировании ПО ОЭСИ ЖТ процесс вызова и подключения ПМ имеет следующий вид:


где П - компонуемая программа, З - заглушка, {X,Yэт} - тестовый набор.

С другой стороны, процесс вызова и подключения ПМ при восходящем интеграционном тестировании ПО ОЭСИ ЖТ может быть следующим:


где Д - тестовый драйвер.

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

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

Для подтверждения внесённых в программную систему изменений используется регрессионное тестирование.

ПРИМЕЧАНИЕ

Под регрессионным тестированием понимается выборочное тестирование, проверяющее, что изменённая система по-прежнему соответствует требованиям.

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

2. 11. Тестирование циклических структур

Следует отметить, что наличие циклов в программах существенно усложняет процесс тестирования. Так, например, если в программе P существует n ациклических путей, предваряющих цикл с m маршрутами и k итерациями, то для исчерпывающего тестирования по критерию С2 потребуется проверить M = n•m•k маршрутов (пути считаются различными, если они содержат разное число итераций входящего в них цикла). А так как реальные программы могут содержать кроме простых также объединённые, вложенные и неструктурированные циклы с большим числом маршрутов и итераций, то исчерпывающее тестирование подобных программ по структурным критериям не представляется возможным. Для уменьшения общего числа маршрутов необходимых для структурного тестирования программ содержащих простые и объединённые циклы используется подход минимизации числа итераций. При этом каждый цикл представляется эквивалентным ациклическим подграфом, а число итераций фиксируется на заданном уровне. Как правило, число итераций выбирается таким образом, чтобы удалось осуществить покрытие тела цикла по выбранному структурному критерию (то есть осуществить размыкание цикла), а также проверить замыкающую дугу.

Продемонстрируем тестирование циклов на примере процедуры формирования критических точек (в данном случае локальных максимумов), используемой в ПМ ОЭСИ ЖТ для поиска межсимвольных разделителей. Реализация указанной процедуры имеет вид (отметим, что в условии выхода из тела цикла содержится ошибка - выделено курсивом и красным шрифтом):

<<.h>>

class CClass 
{
   typedef vector<DWORD> V_CRITS;
   V_CRITS MakeCrits(DWORD*,int,int,int);
};

<<.cpp>>

CClass::V_CRITS CClass::MakeCrits(DWORD* proj, int max, int min, int width)
{
   V_CRITS v_crits;
   if (min < max)
      for (int i = 1; i < width; ++i) 
         if ( (proj[i]>=proj[i-1])&&(proj[i]>=proj[i+1]) ) 
            v_crits.push_back(proj[i]);
   return v_crits;
}

Представление циклической структуры в виде линейного кода или эквивалентный ациклический подграф процедуры формирования локальных максимумов имеет вид:

CClass::V_CRITS CClass::MakeCrits_NoLoop(DWORD* proj, int max, int min, int width)
{
   V_CRITS v_crits;

   if (min < max)
   {
      int i = 1;
      if (i < width)
         if ( (proj[i]>=proj[i-1])&&(proj[i]>=proj[i+1]) )
            v_crits.push_back(proj[i]);
      ++i;
      // ...
      if (i < width)
         if ( (proj[i]>=proj[i-1])&&(proj[i]>=proj[i+1]) ) 
            v_crits.push_back(proj[i]);
      ++i;
   }

   return v_crits;
}

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

void CClass::OnCycleStructTest()
{
   int max,min; 
   int width   = 5;
   DWORD *proj = new DWORD[width]; 
   ::ZeroMemory(proj,width*sizeof(DWORD));

   // Структурное тестирование С1
   min = max = 0;
   V_CRITS v_crits = MakeCrits(proj,max,min,0,width);  
   ASSERT(v_crits.empty());
   min = 0, max = 30;
   proj[1] = 20, proj[2] = 30, proj[3] = 20;
   v_crits = MakeCrits(proj,max,min,2,3);
   ASSERT ( (v_crits.size() == 1)&& (v_crits[0] == 30) );
   min = 0, max = 30; 
   proj[1] = 20, proj[2] = 10, proj[3] = 20;
   v_crits = MakeCrits(proj,max,min,2,3);
   ASSERT(v_crits.empty());

   // Замыкающая дуга - цикл не должен выполняться
   min = 0, max = 30;
   v_crits = MakeCrits(proj,max,min,4,5);
   ASSERT(v_crits.empty());
   delete[] proj;
}

CClass::V_CRITS CClass::MakeCrits(DWORD* proj, int max, int min, int begin, int end)
{
   V_CRITS v_crits;

   if (min < max)
      for (int i = begin; i < end; ++i) 
      {
         if (i == 4) 
            AfxMessageBox(TEXT("Ошибка последней итерации"));
         if ( (proj[i] >= proj[i-1])&&(proj[i] >= proj[i+1]) )
            v_crits.push_back(proj[i]);
      }

   return v_crits;
}

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


где (x1,x2,x3,x4,x5) - входные параметры, x1 - массив средних значений яркости точек изображения, x2 - максимальное значение яркости, x3 - минимальное значение яркости, x4 - начальное значение итератора цикла, x5 - конечное значение итератора, * - любые данные, empty - пустой выходной вектор. Сформированный, в соответствии с подходом минимизации числа итераций, тестовый набор покрывает все ветви циклической структуры при заданном числе итераций. Последний тестовый случай выявляет исполнение цикла на замыкающей ветке и обнаруживает ошибку выхода из тела цикла.

3. Заключение

В статье рассмотрены основные принципы и методики, используемые в рамках дисциплины тестирования программных продуктов. Дана классификация существующих критериев тестирования, детально рассмотрены структурные и функциональные критерии формирования тестовых наборов. Представлен ряд примеров в стандарте C++ облегчающих изучение и понимание процессов тестирования.

4. Список литературы

  1. Липаев В.В. Методы обеспечения качества крупномасштабных программных средств. – М.: СИНТЕГ, 2003. – 520 с.
  2. Липаев В.В. Обеспечение качества программных средств. Методы и стандарты. Серия “Информационные технологии”. М.: СИНТЕГ, 2001. - 380 с.
  3. Майерс Г. Искусство тестирования программ: Пер. с англ.- М.: Финансы и статистика, 1982. – 176 c.
  4. Макгрегор Джон, Сайкс Дэвид. Тестирование объектно-ориентированного программного обеспечения: Пер. с англ. – К.: ООО “ТИД ДС”, 2002. - 432 с.
  5. Малыгин Л.Л., Мошников В.В., Царёв В.А.. Оптоэлектронная система идентификации объектов подвижного состава ARSCIS на станции Череповец Северной железной дороги // Инновационные проекты, новые технологии и изобретения. Инновации – 2005: Сборник докладов научно-практической конференции. – Щербинка: ВНИИЖТ, 2005. – С.122-130.
  6. Калбертсон Роберт и др. Быстрое тестирование: Пер. с англ. – М.: “Вильямс”, 2002. – 384 с.
  7. Канер Сэм и др. Тестирование программного обеспечения. Фундаментальные концепции менеджмента бизнес-приложений: Пер. с англ. – К.: “ДиаСофт”, 2001. – 544 с.
  8. Котляров В.П., Коликова Т.В., Некрасов Н.А., Епифанов Н.А. Технологии программирования. Основы современного тестирования программного обеспечения, разработанного на C#: Учеб.пособие – СПб.: Издательство СПбГПУ, 2004. – 168 c.
  9. Тампре Л. Введение в тестирование программного обеспечения: Пер. с англ. – М.: “Вильямс”, 2003. – 368 с.
 Developing.ru  - клуб программистов Клуб разработчиков программных систем Rambler's Top100 Рейтинг@Mail.ru Каталог ресурсов ListTop.Ru Яндекс цитирования Rambler's Top100
© 2001-2017. Сopyright by "Отдел автоматизации и разработки ПО"
Перепечатка любых материалов без разрешения авторов сайта запрещена.
Сергей Мякишев©, o'Xana - Дизайн и разработка сайта
Kost - Программирование, Системное Администрирование