Uploaded by Никита Фролов

Модульное тестирование программ на языке С++ (3)

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ
ВОЛГОГРАДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ
КАФЕДРА «ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ АВТОМАТИЗИРОВАННЫХ
СИСТЕМ»
Модульное тестирование программ
на языке С++
Методические указания
Волгоград
2013
УДК 681.3.06 (075)
Рецензент
доцент кафедры САПР и ПК канд. техн. наук О. А. Шабалина
Печатается по решению редакционно-издательского совета
Волгоградского государственного технического университета
Модульное тестирование программ на языке С++ : метод. указания / сост.
О. А. Сычев, А. Ф. Ушаков ; ВолгГТУ. – Волгоград, 2013. – 32 с.
Изложены основы модульного тестирования, его роль в обеспечении надежности и качества программного обеспечения. Приводятся рекомендации по
составлению наборов модульных тестов. Подробно, с образцами экранов и
примерами программного кода, приведены примеры разработки модульных
тестов с использованием систем Visual Assert и QTestLib. Предлагаются списки
контрольных вопросов и литературы для дальнейшего изучения.
Предназначены для студентов направления 231000 «Программная инженерия» по дисциплине «Надежность и качество программного обеспечения».
© Волгоградский государственный
технический университет, 2013
Содержание
1 Понятие и назначение модульного тестирования .................................. 3
2 Цель лабораторной работы ....................................................................... 5
3 Порядок выполнения лабораторной работы ........................................... 5
4 Составление модульных тестов................................................................ 5
4.1 Составление полноценного набора тестов ........................................... 7
4.2 Оформление тестов ................................................................................. 8
5 Реализация модульных тестов .................................................................. 8
5.1 Реализация модульных тестов в среде Visual Assert ........................... 9
5.2 Реализация модульных тестов средствами библиотеки QTestLib ... 20
6 Контрольные вопросы ............................................................................. 30
7 Список литературы .................................................................................. 31
1 Понятие и назначение модульного тестирования
Модульное тестирование (unit testing) – это тестирование отдельных
элементов (модулей) программы: функций, методов, классов и т.д.
Модульное тестирование нередко позволяет локализовать ошибку в
программе, найти конкретный модуль, в котором она была сделана.
Однако главное преимущество модульного тестирования состоит в том,
что оно может быть автоматизировано, то есть модульные тесты
программируются и запускаются, как и сама программа. Впоследствии,
при отладке и дальнейшем развитии программы, модульные тесты могут
быть перезапущены с очень незначительными усилиями – обычно
нажатием 1-2 кнопок. Хотя реализация модульных тестов программным
кодом и требует больше времени, чем ввод теста вручную – но она
делается однократно, повторять в процессе отладки ее не требуется.
Поэтому на самом деле использование модульных тестов значительно
экономит время на отладку программы. Оно также увеличивает степень
контроля ее качества и надежности: при каждом запуске модульного
тестирования выполняются все когда-либо написанные модульные тесты, в
то время как при ручном тестировании мало у кого хватает терпения
запускать все тесты при каждом сеансе тестирования.
Систематическое использование модульных тестов позволяет вовремя
замечать и устранять регрессии – ситуации, когда изменение одной части
программы вызывает сбои в работе других; поскольку программа каждый
раз тестируется полностью. Более того, использование модульных тестов
увеличивает уверенность программиста и степень модифицируемости
программы. Программист, не использующий модульные тесты, обычно
избегает вносить изменения (особенно с целью улучшения работы) в
отлаженные сложные модули программы – он боится испортить их работу
в уже проверенных ситуациях. Однако при использовании модульных
3
тестов перепроверка может быть произведена очень быстро, нарушения в
работе изменяемого модуля легко отслеживаются и относительно легко
исправить их, либо вернуться к прежнему коду, если ошибка оказывается
слишком сложной для исправления. Таким образом, использование
модульных тестов позволяет программисту чувствовать себя свободнее
при развитии кода программы, увеличивает ее надежность за счет
простоты повторного тестирования и способствует улучшению ее качества
за счет снижения затрат труда и времени на изменение программы.
К сожалению, применению модульных тестов начинающими
программистами нередко мешает необходимость вложить значительные
усилия в разработку базового набора тестов до того, как начинают
сказываться их преимущества. Многие не преодолевают этот барьер
начальных вложений усилий, предпочитая вручную повторять тесты при
отладке программы (что занимает куда больше времени и сил, но не сразу).
Между тем, рекомендуется сразу составлять полный набор тестов на
разрабатываемые модули. Недостаточный набор модульных тестов может
негативно сказаться на надежности программы, так как при их
систематическом использовании программист привыкает полагаться на
них, считая, что раз модульные тесты пройдены, то и ошибок в программе
нет.
В ходе изложения материала мы будем использовать следующую
терминологию.
Модульный тест (unit test) – тест отдельной функции (метода)
программы, оформленный в виде тестирующей функции, которая подает
на вход тестируемой функции входные данные и проверяет корректность
выходных, выдавая сообщения об ошибках в случае нарушения ее работы.
Фикстура (fixture) – набор модульных тестов на одну или несколько
взаимосвязанных функций, объединенных общими процедурами
инициализации и завершения работы. Фикстура обычно реализуется в виде
класса, методами которого являются тестирующие функции.
Утверждение (assertion, assert) – условие, которое должно быть
истинно в данной точке программы. Обычно утверждения выдают
сообщения об ошибке при нарушении истинности условия и прерывают
выполнение программы. Утверждения, как правило, реализуются
макросами и могут автоматически исключаться при выпускающей
(релизной) сборке программы.
Разработка, управляемая тестами (test-driven development) –
методика разработки программы, при которой перед написанием какоголибо участка кода программы, предварительно разрабатываются
модульные тесты для него. Такой подход позволяет использовать
модульное тестирование уже при первоначальной отладке кода
программы, что значительно повышает пользу от модульных тестов при
тех же затратах на их разработку. Кроме того, разрабатывая тесты, вы
4
можете обнаружить, что вызов функции неудобен, и перепроектировать
интерфейс функции до реализации ее кода.
Тестирование, управляемое данными (data-driven testing) – вид
реализации модульного тестирования, при котором тестовые данные
сводятся в таблицу (строками которой являются разные тесты одной
функции), а код тестирующей функции работает с одной строкой таблицы;
цикл перебора тестов в этом случае берет на себя система тестирования.
Этот подход позволяет избежать дублирования кода вызова метода и
проверки результатов в нескольких тестах одного метода.
2 Цель лабораторной работы
Целью лабораторной работы по модульному тестированию программ
является практическое ознакомление студентов с составлением и
реализацией модульных тестов для консольных программ на языке С++.
3 Порядок выполнения лабораторной работы
1) Используя составленный технический проект разрабатываемой в
рамках курсового проекта программы, выделить тестируемые функции
(методы) и согласовать их перечень с преподавателем. Обычно
тестированию подвергаются все функции программы, кроме функций
ввода и вывода данных (в файлы, на экран и т.д.). В задачах повышенной
сложности с большим количеством функций для модульного тестирования
выделяются не менее 10 наиболее сложных функций.
2) Ознакомившись с разделом 4 данных методических указаний,
составить наборы модульных тестов для каждой тестируемой функции.
Наборы тестов следует оформить в удобном для восприятия человеком
виде и согласовать с преподавателем, они становятся приложением к
документу «Программа и методика испытаний» курсового проекта.
Преподаватель проверяет полноту и корректность составленных тестов.
3) Реализовать модульные тесты в виде программного кода, используя
средства автоматизации модульного тестирования Visual Assert или
QTestLib.
4) Написать заголовки тестируемых функций, скомпилировать
программу модульного тестирования и убедиться, что все тесты не
проходят. Показать программу тестирования преподавателю.
4 Составление модульных тестов
Составление качественного и полноценного набора тестов для
тестируемой функции необходимо для того, чтобы в дальнейшем при
реализации и отладке программы вы могли полагаться на них. Не следует
5
пугаться необходимости составить множество однотипных тестов:
большинство из них легко получить, копируя и исправляя предыдущие.
Ввод сложных структур данных (деревьев, графов), как правило, можно
упростить (автоматизировать) с помощью вспомогательных функций – это
нередко эффективнее, чем создавать кодом множество объектов и
заполнять их поля.
Вместе с тем простое увеличение количества тестов не всегда
помогает увеличить пользу от их применения: тесты должны быть, по
возможности, разнообразными. Не бойтесь сделать ошибку в тесте:
вероятность ее совпадения с ошибкой в программе довольно невелика,
поэтому при проведении тестирования вы ее заметите.
Составленные тесты необходимо описать в виде программы и
методики испытаний вашей программы. Описание теста должно быть
полным (входные данные, ожидаемые выходные данные или
осуществляемые проверки) и легко понятным человеку, чтобы вы или
преподаватель легко могли понять данные теста и проверить его
корректность.
Модульный тест обычно состоит из набора входных данных и либо
набора ожидаемых выходных данных (если результат работы однозначен),
либо перечня осуществляемых проверок. Как правило, ожидаемые
выходные данные разрабатываемой функции однозначны и могут быть
проверены на точное соответствие; однако это не всегда так – рассмотрите
как совершенно противоположный случай тестирование функции,
генерирующей случайные числа – предсказать точное возвращаемое
значение для нее невозможно (но при этом возможно проверить
соответствие
выбранному
распределению
тысячи
случайно
сгенерированных чисел).
Иногда встречаются случаи, когда функция может возвращать
различные данные, но настоящие требования к ним могут быть выражены
в виде различного рода проверок. Например, если цель функции –
расположить вершины графа на плоскости с минимумом пересечений, то
проверять возвращенные ей координаты каждой вершины графа и
утомительно, и вредно: малейшее изменение алгоритма расположения
вершин приведет к необходимости переписывать все тесты. Однако вместо
этого можно написать функцию проверки, подсчитывающую количество
пересечений дуг графа - его минимальное значение для каждого теста вы
можете определить, а каким именно расположением вершин оно
достигнуто в данном случае не важно. Или в простейшем случае, когда
функция ищет минимальный элемент массива (а их может быть
несколько), легче проверить значение элемента (оно будет одинаковым),
чем индекс (в этом случае возможно несколько вариантов правильных
ответов).
6
4.1 Составление полноценного набора тестов
При составлении набора тестов необходимо, с одной стороны,
избежать недостаточного тестирования; с другой – составления
чрезмерного количества однотипных тестов, которые либо все пройдут,
либо все не пройдут. Один из важных приемов сокращения излишнего
тестирования – при составлении давать тестам осмысленные названия, в
которых описывать суть проверки. Работу функции (метода) в какой
именно ситуации проверяет ваш тест? Какой возможный вид ошибки
может быть им обнаружен? Ответив на этот вопрос, вы лучше поймете
роль теста в общей системе проверки программы; бессмысленных
названий следует избегать.
При составлении тестов следует придерживаться следующих
требований и рекомендаций.
При тестировании функций необходимо иметь несколько подробных
тестов на общие ситуации, а также отдельные (часто – простые) – на
краевые, экстремальные ситуации (минимумы и максимумы значений
входных или выходных данных; расположение искомых или
обрабатываемых элементов в начале, середине и конце массива и т.д.).
Если функция может получать (возвращать) массив (контейнер)
объектов переменной длины – сделайте отдельные тесты на ситуации,
когда массив пуст (если это допустимо), содержит один объект, содержит
несколько объектов и содержит максимальное количество объектов (если
оно невелико; если количество обрабатываемых объектов ограничено
только доступной памятью, то такой тест не требуется). Это верно как для
передаваемых в функцию наборов объектов, так и для возвращаемых
(например тестировать ситуации, когда функция не нашла ответа, нашла
один ответ и нашла несколько ответов).
Если функция может получать или возвращать объекты различных
типов – например, дерево выражения, в котором участвуют различные
операции или набор ошибок, которых есть несколько видов – модульные
тесты должны проверять работу функции на всех (!) возможных типах
объектов. Даже если у вас обрабатывается дерево выражения с 15
возможными операциями, необходимо проверить каждую (!) операцию в
отдельности – обычно простыми тестами – и составить несколько
комплексных тестов, проверяющих их взаимодействие. Более того, если
эти операции могут работать с различными типами данных, следует
тестировать каждую операцию с каждыми возможными для нее типами
данных. Поскольку такие функции обычно содержат множественные
альтернативные ветвления (либо вызов полиморфной функции), то ошибка
может содержаться в любом фрагменте кода, и все они должны быть
проверены.
7
4.2 Оформление тестов
Перед описанием тестов на какую-либо функцию (метод) необходимо
привести описание самой функции с указанием входных и выходных
данных (как явных – аргументов и возвращаемых значений, так и неявных
– используемых свойств класса и/или глобальных переменных).
Описание теста состоит из названия теста (которое должно отражать
суть проводимой проверки, отличающую его от других тестов), набора
входных данных и набора ожидаемых выходных данных, либо описания
проводимых проверок.
При оформлении тестов необходимо уделить внимание легкости их
восприятия человеком – как в документации, так и в коде программы (о
читабельности тестов в коде программы см. следующий раздел), в
противном случае проверить ваши тесты будет очень сложно.
Так если ваша функция получает или возвращает индексы элементов в
массиве (контейнере) или строке (или, возможно, диапазоны этих
элементов) – то вам лучше не приводить числовые значения индексов,
которые человеку придется отсчитывать вручную, а выделить
соответствующие элементы массива (строки) цветом или нарисовать
указывающую на них стрелку с указанием имени переменной-индекса.
Если ваша функция обрабатывает сложные структуры данных –
деревья или графы – их следует изобразить в виде схемы (рисунка).
Составление большого количества таких рисунков может быть облегчено
использованием свободно распространяемой утилиты dot из пакета
Graphviz фирмы AT&T, которая может построить изображение графа по
его описанию в текстовом файле. Описание языка текстовых файлов dot на
русском языке и примеры графов легко найти в сети Интернет. Если вас не
устраивает форма графа dot (обычно она подходит) или вы предпочитаете
работать с мышью, неплохие картинки с небольшими затратами усилий
можно получить в программах рисования организационных диаграмм,
либо редакторе OpenOffice (LibreOffice) Draw.
Если вы сомневаетесь в том, как следует в удобном для восприятия
образе оформить тесты к какой-либо функции, сделайте один пример и
покажите его преподавателю на ближайшем занятии: это намного легче,
чем переделывать оформление всех тестов, если оно окажется
неудовлетворительным.
5 Реализация модульных тестов
Хотя реализация модульных тестов и является более рутинной работой,
чем реализация алгоритмов основного кода программы, и в этом случае
следует думать о том, как облегчить себе работу. В сложных программах
написание тестов может быть значительно проще, если об этом
8
позаботится – хороший программист всегда ищет пути упростить себе
работу, не снижая качества ее результата.
Так если ваша функция принимает на вход дерево (или возвращает его
и вам надо сравнить результат), то создание его вручную созданием
объектов в коде и заполнением их полей довольно трудоемко; кроме того
код теста читается плохо и повышает вероятность сделать в нем ошибку.
Однако вместо этого несложно написать функцию, которая бы создавала
дерево из строки в постфиксной (обратной польской) записи – что делает
тест и короче, и понятнее. В этом случае длинные тесты с ручной
проверкой дерева необходимы лишь для этой, вспомогательной и довольно
простой функции.
Аналогично, вы можете посчитать полезным написание функции
сравнения деревьев вместо того, чтобы проверять структуру дерева
множеством отдельных утверждений. Ввод графов, если это требуется,
может быть осуществлен из строки с использованием упрощенной версии
языка dot.
В отдельных случаях также может быть полезным написание
небольшой программы, генерирующей код тестирования, если ожидаемый
результат можно надежно получить из таблиц или системных функций. Вы
также можете использовать системные функции для проверки своих
результатов, например в случае, если ваша программа ищет совпадение с
регулярным выражением, а класс QRegExp решает аналогичную задачу.
Реализация модульных тестов на языке программирования облегчается
использованием стандартных библиотек-каркасов (framework) модульного
тестирования. В рамках данного курса вы изучаете библиотеки Visual
Assert (cfix) и QTestLib.
5.1 Реализация модульных тестов в среде Visual Assert
Среда Visual Assert, основанная на библиотеке cfix, имеет графический
интерфейс пользователя для запуска тестов и просмотра результатов
тестирования, интегрированный со средой Microsoft Visual Studio. Еще
одним ее достоинством является возможность интегрировать модульные
тесты в исполняемый файл программы, не мешая обычному ее запуску –
тесты выполняются при запуске через интерфейс Visual Assert. Однако эта
возможность основана на использовании недокументированных команд
компоновщика и не всегда работоспособна при использовании библиотеки
Qt. Visual Assert также не поддерживает тестирование, управляемое
данными.
После установки Visual Assert, в свойствах проекта Visual Studio
необходимо указать директорию, в которой находится библиотека cfix.dll.
Для этого необходимо зайти в свойства проекта (Project / Project
Properties), перейти на вкладку Configuration Properties / Debugging, для
9
свойства Environment задать значение PATH=C:\Program Files\Visual
Assert\bin\i386\. Если у вас процессор AMD, необходимо задать значение
PATH=C:\Program Files\Visual Assert\bin\amd64\. Вариант, который следует
выбрать легче всего определить по наличию данных каталогов. В случае,
если Visual Assert установлен в другую директорию, путь может
отличаться. Даная настройка добавляет указанный путь в переменную
окружения PATH каждый раз вовремя запуска программы, удаляя его
после завершения работы.
Другим вариантом может быть простое копирование библиотеки
cfix.dll в рабочую директорию проекта (она совпадает с той, где лежат
исходные коды программы при запуске из Visual Studio; если вы
запускаете исполняемый файл то библиотеку надо разместить в одной с
ним директории).
5.1.1 Создание тестирующего класса вручную
Шаг 1. Добавить класс, в котором будет осуществляться тестирование,
например, myTestingClass, используя стандартное меню VisualStudio
Проект / Добавить класс... Он должен быть унаследован от
cfixcc::TestFixture. Диалог создания класса показан на рис. 5.1.1.
Рисунок 5.1.1 Создание тестирующего класса вручную
Вы можете получить сообщение: «Базовый класс cfixcc::TestFixture не
найден в проекте. Продолжить добавление класса?» Нажмите «Да».
10
Шаг 2. Теперь в *.h файле только что созданного тестирующего класса
необходимо подключить заголовочный файл cfixcc.h, а затем файл, в
котором содержится тестируемые функции.
Шаг 3. На третьем шаге создаются тестирующие методы. Подробнее
как это делается будет рассмотрено дальше, пока можно объявить
заголовки методов. Добавим, к примеру, два метода-теста: testOne и
testTwo.
Также в класс можно включить и специальные методы,
предоставляемые cfix (подробно см. пункт 5.1.5):
Шаг 4. Следующий шаг – «регистрация» созданного тестирующего
класса. Это делается вне класса в конце файла с помощью макросов:
CFIXCC_BEGIN_CLASS(<название тестирующего класса>)
CFIXCC_METHOD(<тестирующий метод 1>)
CFIXCC_METHOD(<тестирующий метод 2>)
…
CFIXCC_END_CLASS()
В итоге получаем каркас («скелет») тестирующего класса. Его
исходный код представлен на рис. 5.1.2.
Файл myTestingClass.cpp
//Заголовочный файл, необходимый для проведения тестирования
#include<cfixcc.h>
//Файл, в котором содержится тестируемая функция
#include"myMathFunction.h"
//Класс должен быть унаследован от cfixcc::TestFixture
class myTestingClass : public cfixcc::TestFixture
{
public:
void testOne()
{}
void testTwo()
{}
};
// "Регистрация" тестирующего класса и его методов
CFIXCC_BEGIN_CLASS(myTestingClass)
CFIXCC_METHOD(testOne)
CFIXCC_METHOD(testTwo)
CFIXCC_END_CLASS()
Рисунок 5.1.2 Каркас тестового класса с двумя методами
5.1.2 Создание тестирующего класса, используя мастер «Add Unit Test
wizard»
Для создания тестовых классов существует специальный мастер,
вызываемый из Обозревателя решений (Solution Explorer): для этого
11
необходимо вызвать контекстное меню проекта или папки, входящей в
проект, выбрать пункт Добавить(Add) / Unit Test..., см. рис. 5.1.3.
Откроется мастер создания класса (рис. 5.1.4), в котором необходимо
указать только имя нового класса и, если нужно, дополнительные
специальные методы (их описание см. в пункте 5.1.5).
Рисунок 5.1.3 Вызов мастера создания тестирующего класса
Рисунок 5.1.4 Мастер создания тестирующего класса
После успешного добавления класса в проект, необходимо подключить
файл, в котором содержатся тестируемые функции. При добавлении новых
методов-тестов нужно не забывать их регистрировать с помощью
макросов, описанны в пункте 5.1.1. Полученный каркас тестового класса
представлен на рис. 5.1.2.
12
После компиляции проекта, об успешной регистрации тестов можно
судить по окну обозревателя тестов TestExplorer (вызывается из строки
меню: Visual Assert / Test Explorer Window), показанному на рис. 5.1.5.
Рисунок 5.1.5 Окно обозревателя тестов
Данное окно отображает текущий проект и все созданные методытесты.
5.1.3 Реализация методов-тестов
Пусть нам требуется протестировать функцию, получающую на входе
два числа типа int и возвращающую их сумму (см. рис. 5.1.6).
Файл myMathFunction.h
//Выполняет сложение переданных параметров
Int add(int arg1, int arg2)
{
return arg1+arg2;
}
Рисунок 5.1.6 Тестируемая функция.
Реализуем два тестирующих метода, объявленных ранее.Пусть первый
метод будет проверять тестируемую функцию на правильность
суммирования отрицательных чисел, второй – положительных. Код
методов приведен на рисунке 5.1.7.
Используемый в коде макрос утверждения CFIXCC_ASSERT_EQUALS
сравнивает значения двух переданных элементов на равенство. В случае,
если они не равны – тест считается не пройденным, при этом в протокол
тестирования выводится сообщение об ошибке.Макрос CFIX_LOG,
13
используемый в первом наборе тестов, служит для протоколирования
действий. Макрос CFIX_ASSERT служит для проверки любых условий,
которые должы быть истинны в данной точке программы.Макрос
CFIX_ASSERT_MESSAGE, как и CFIX_ASSERT проверяет переданное
выражение на истинность; его отличие состоит в том, что в случае провала
теста этот макрос может выдать пользователю сообщение, заданное
программистом. Подробно эти макросы описаны в разделе 5.1.6.
void testOne()
{
CFIX_LOG(L"Test 1.Parameters:‐5,‐3; expected:‐8");
CFIXCC_ASSERT_EQUALS(‐8, add(‐5, ‐3));
CFIX_LOG(L"Test 2.Parameters:‐5,‐5; expected:‐10");
CFIXCC_ASSERT_EQUALS(‐10, add(‐5, ‐5));
CFIX_LOG(L"Test 3.Parameters:‐1,‐2; expected:‐3");
CFIXCC_ASSERT_EQUALS(‐3, add(‐1, ‐2));
}
void testTwo()
{
CFIX_ASSERT(8 == add(5, 3));
CFIX_ASSERT(10 == add(5, 5));
CFIX_ASSERT_MESSAGE(3 == add(1, 2),
L"Fail! Expected:0,returned:%d", add(1, 2));
}
Рисунок 5.1.7 Код тестирующих методов
5.1.4 Запуск тестов
После того, как тесты написаны, можно проводить тестирование. Visual
Assert предоставляет 2 режима запуска тестов: отладочный Debug Test
(закрашенный зеленый треугольник в окне обозревателя тестов) и без
отладки Run Test Without Debugging (незакрашенный зеленый
треугольник). В первом случае, если имеются проваленные тесты, процесс
тестирования остановится на строке, вернувшей ошибку в таком тесте, и
появится окно, в котором можно либо прекратить, либо продолжить
тестирование. Режим Run Test Without Debugging запускает на выполнение
все тесты, а об успешности их выполнения сообщается после прохождения
тестов. Запустим наши тесты в режиме Run Test Without Debugging (см.
рис. 5.1.8). Открывается окно TestRun, сообщающее, что наши тесты
выполнились успешно (см. рис. 5.1.9). Изменим теперь второй тест так,
чтобы тестируемая функция не прошла его – см. рис. 5.1.10.
После запуска тестов (рис. 5.1.11) видим, что, как и ожидалось, второй
тест не пройден.
В колонке Expression появилось составленное нами ранее сообщение,
которое помогает понять, вследствие чего возникла данная ошибка.
Колонка Location сообщает о месте возникновения ошибки. Если
14
используются WinAPI функции, то также может оказаться полезной
колонка LastWin32 error – последнее значение, которое вернула функция
WinAPI.
Рисунок 5.1.8 Запуск тестов без отладки
Рисунок 5.1.9 Окно отчета о запуске тестирования с двумя пройденными
тестами
CFIX_ASSERT_MESSAGE(0 == add(1, 2),
L"Fail! Expected:0, returned:%d", add(1, 2));
Рисунок 5.1.10 Код теста, выдающего ошибку.
15
Рисунок 5.1.11 Окно отчета о запуске тестирования с проваленным тестом
Следует отметить, что из окна обозревателя тестов возможен запуск
как всех тестов сразу, так и любого конкретного теста.
5.1.5 Специальные методы тестирующего класса
Как уже отмечалось выше, библиотека cfix предоставляет специальные
методы, упрощающие тестирование:
virtual void Before(); – метод, вызывающийся перед каждым
тестом;
virtual void After(); – метод, вызывающийся после каждого
теста;
static void SetUp(); – метод, вызывающийся один раз перед
всеми тестами;
static void TearDown(); – метод, вызывающийся после работы
всех тестов.
На практике такие методы используются, например, для
инициализации переменных-членов тестирующих классов, хранящих
общие для тестов данные. Необходимо отметить, что «специальные»
методы регистрации не требуют. Пример на рисунке 5.1.12 демонстрирует
работу таких методов используя вывод сообщений о срабатывании метода.
16
Файл myTestingClass.cpp
#include"stdafx.h"
#include<cfixcc.h>
//Класс должен быть унаследован от cfixcc::TestFixture
class myTestingClass : public cfixcc::TestFixture
{
public:
void testOne()
{
CFIX_LOG(L"In TestOne()");
}
void testTwo()
{
CFIX_LOG(L"In testTwo()");
}
virtualvoid Before()
{
CFIX_LOG(L"In Before()");
}
virtualvoid After() {
CFIX_LOG(L"In After()");
}
staticvoid SetUp() {
CFIX_LOG(L"In SetUp()");
}
staticvoid TearDown()
{
CFIX_LOG(L"In TearDown()");
}
};
// "Регистрация" тестирующего класса и его методов
CFIXCC_BEGIN_CLASS(myTestingClass)
CFIXCC_METHOD(testOne)
CFIXCC_METHOD(testTwo)
CFIXCC_END_CLASS()
Рисунок 5.1.12 Демонстрационный код специальных методов
тестирующего класса
Открыв в окне вывода лог после запуска тестов, увидим результаты
(см. рис 5.1.13).
Рисунок 5.1.13 Отчет о тестировании, показывающий
порядок вызова специальных методов
17
5.1.6 Описание основных макросов библиотеки cfix
CFIXCC_ASSERT_EQUALS(<ожидаемое значение>, <реальное
значение>);
Сравнивает 2 значения на равенство. Значениями могут быть строки,
числа, объекты классов (если для них существует операция равенства),
указатели и др. (см. рис. 5.1.14).
CFIX_ASSERT(<условие>);
Макрос утверждения, используется для проверки любых условий. Если
условие ложно, то тест будет провален (см. рис. 5.1.15).
CFIX_ASSERT_MESSAGE(<условие>, <сообщение пользователю в
случае провала>);
Похож на предыдущий макрос.Если условие ложно, то тест будет
провален и пользователь увидит сообщение. Сообщение записывается в
таком же стиле, что и для функции printf (см. рис. 5.1.15).
//Сравнение чисел
CFIXCC_ASSERT_EQUALS(1, 1); //Успех
//Сравнение строк
constwchar_t* str = L"test";
CFIXCC_ASSERT_EQUALS(L"test", str); //Успех
//Сравнение указателей и значений
int* a = newint;
int* b = newint;
*a = 5;
*b = 5;
CFIXCC_ASSERT_EQUALS(*a, *b); //Успех, числа равны
CFIXCC_ASSERT_EQUALS(a, b); //Провал, адреса памяти, по
которым хранятся значения, не равны
Рисунок 5.1.14 Примеры использования макроса
CFIXCC_ASSERT_EQUALS
CFIX_LOG(<форматная строка>, …);
Данный макрос используется для протоколирования действий.
Аргумент макроса записывается в таком же стиле, что и для функции
printf. Лог можно посмотреть, нажав кнопку ShowLog в окне TestRun (см.
рис. 5.1.15).
int a=6;
int b=5;
CFIX_ASSERT(a>b); //Успех
CFIX_ASSERT(a==b); //Провал
int var = 0;
CFIX_ASSERT_MESSAGE(var==5, L"var=%d",var); //Провал,
пользователь увидит сообщение "var=0"
CFIX_LOG( L"Current number: %d", arr[i]);
Рисунок 5.1.15 Примеры использования макросов CFIX_ASSERT,
CFIX_ASSERT_MESSAGE, CFIX_LOG
18
5.1.7 Возможные проблемы при использовании Visual Assert
При попытке запуска программы или тестов я получаю сообщение
«Запуск программы невозможен, так как на компьютере отсутствует
cfix.dll. Попробуйте переустановить программу.»
Для решения этой проблемы необходимо либо к проекту, в папку с
исходными файлами добавлять cfix.dll (он находится в папке Visual
Assert\bin\i386 или Visual Assert\bin\amd64), либо прописать пути в
переменных окружения.
1. В переменных среды для текущего пользователя системы должна
присутствовать переменная CFIX_HOME, описывающая каталог, в
который установлен Visual Assert (см. рис 5.1.16). Если такой переменной
нет – ее следует добавить вручную.
Рисунок 5.1.16 Переменная среды CFIX_HOME
2. Создать переменную среды INCLUDE, присвоить ей значение
“%CFIX_HOME%\include;” (без кавычек).
3. Далее необходимо создать системную переменную LIB и добавить в
существующую переменную PATH, в зависимости от архитектуры
процессора, одно из следующих значений:
a. “%CFIX_HOME%\lib\i386;” для LIB и
“%CFIX_HOME%\bin\i386;”для PATH
b. “%CFIX_HOME%\lib\amd64;” для LIB и
“%CFIX_HOME%\bin\amd64;” для PATH
19
Вариант, который следует выбрать легче всего определить по наличию
данных каталогов. После этого следует перезагрузить Microsoft Visual
Studio чтобы изменения вступили в силу.
5.2 Реализация модульных тестов средствами библиотеки QTestLib
QTestLib является тестовым каркасом библиотеки Qt. Она хорошо с
ней интегрирована и поддерживает тестирование, управляемое данными.
Однако результаты тестирования выводятся в консоль, либо XML-файл –
визуального интерфейса запуска отдельных тестов и просмотра
результатов тестирования в библиотеке QTestLib не предусмотрено.
Помимо возможности тестирования отдельных функций и методов класса,
QTestLib позволяет проводить тестирование классов графического
интерфейса, симулировать клавиатуру и мышь.
5.2.1 Создание и запуск модульных тестов в QTestLib
На рис 5.2.1 показан класс, для метода которого будет производиться
тестирование.
Файл MyMathClass.h
class MyMathClass
{
public:
//Выполняет сложение переданных параметров
int add (int arg1, int arg2)
{
return arg1+arg2;
}
};
Рисунок 5.2.1 Тестируемый класс
При создании проекта тестирования необходимо в мастере создания Qt
Console Application на вкладке Project Settings выбрать дополнительный
модуль, который подключится к проекту – Test library (см. рис. 5.2.2).
20
Рисунок 5.2.2 Мастер создания проектов Qt
Добавим тестирующий класс, назовем его Test_MyMathClass.
Теперь можно удалить функцию main – ее заменит макрос QTEST_MAIN,
в этом заключается одна из особенностей создания тестов с помощью
QTestLib. Также необходимо подключить заголовочный файл QTest.
Тестирующий класс должен быть унаследован от класса QObject и,
для создания специальной метаинформации,
содержать в своем
определении макрос QOBJECT.
На рисунке 5.2.3 представлен реализованный тестирующий класс.
В примере используется макрос QCOMPARE, получающий на входе 2
параметра и сравнивающий их на эквивалентность. Подробнее макросы
библиотеки тестирования Qt описаны в пункте 5.2.4. Результат запуска
программы показан на рис. 5.2.4. Видно, что все тесты успешно пройдены.
21
ФайлTest_MyMathClass.cpp
#include <QtTest>
#include "MyMathClass.h"
class Test_MyMathClass: public QObject
{
Q_OBJECT
private slots: //Слоты обязательно private
void add();
};
void Test_MyMathClass::add()
{
/* Создаем объект тестируемого класса,
чтобы было откуда вызывать методы */
MyMathClass myClass;
/* Вызываем метод класса и сравниваем
полученное значение с ожидаемым
*/
QCOMPARE(myClass.add(‐7, 12), 5);
QCOMPARE(myClass.add(6, 0), 6);
QCOMPARE(myClass.add(13, ‐4), 9);
}
//Этот макрос заменяет main
QTEST_MAIN(Test_MyMathClass)
//Файл метаинформации, обязательно должен быть
#include "Test_MyMathClass.moc"
Рисунок 5.2.3 Тестирующий класс
Рисунок 5.2.4. Результат тестирования программы с пройденными тестами.
Чтобы
увидеть,
как
выводятся
сообщения
об
ошибках
тестирования,составим заранее некорректный тест – укажем для второго
теста (6+0) результат не 6, а, например, 10. Результат такого запуска
представлен на рисунке 5.2.5.
Под строкой FAIL показано, значение, которое вернула функция
(Actual),и то, которое мы ожидали от нее в тесте (Expected). Обратите
внимание на то, что после имени тестирующегокласса указано имя
тестирующего метода, в теле которого произошел провал теста.
22
Рисунок 5.2.5. Результат тестирования программы, где один тест провален.
5.2.2 Тестирование, управляемое данными
Для минимизации дублирования кода при тестировании сложных
функций QTestLib предоставляет
возможность проведения тестов,
управляемых данными (data-driven test). Для реализации тестов,
управляемых данными, данные всех тестов одной функции (метода)
необходимо объединить в таблицу.
Столбцами такой таблицы являются данные, передаваемые в
тестируемую функцию или используемые при проверке результата ее
работы, а строки – это отдельные тесты. Ячейка – переменная того типа
данных, который требуется для проведения теста (разные столбцы
таблицы могут иметь разные типы данных).
Например, для функции, суммирующей два числа, таблица данных
тестов может быть такой, как показана в табл. 5.2.1. За создание таблицы
отвечает специальный слот в классе тестирования.
Таблица 5.2.1 Данные для тестирования функции сложения целых чисел
Имя теста
int arg1
int arg2
int result
Test1
Test2
Test3
…
-7
6
13
…
12
0
-4
…
5
6
9
…
5.2.3 Создание
управляемого данными
тестирующего
класса
для
тестирования,
Рассмотрим проведение тестирования, управляемого данными, для
класса на рис. 5.2.1. Создание проекта для тестирования, управляемого
данными, не отличается от описанного в пункте 5.2.1.
Для управляемого данными тестирования необходимо в классе создать
по два слота на каждую тестируемую функцию - первый создает таблицу
данных, а второй использует эти данные и выполняет само тестирование.
Для данного класса сохраняются требования по замене функции main на
23
макрос QTEST_MAIN, наследовании от QObject и другие (см. пункт
5.2.1).
Существуют соглашения по наименованию тестирующего класса и его
методов, которые успели закрепиться и зарекомендовать себя на практике
с наилучшей стороны.
- Называйте тестирующий класс именем тестируемого с префиксом
Test_. Например: если мы тестируем класс MyMathClass, то
тестирующий класс будет называться Test_MyMathClass.
- Называйте тестирующие слоты (методы) именами тестируемых
методов. В нашем примере это add.
- Слот, в котором размещается таблица данных, должен называться так
же, как и тестирующий слот, но с суффиксом _data: add_data. Это
является обязательным требованием, в противном случае тест работать не
будет.
В слоте описания данных сначала создаются столбцы таблицы с
помощью метода addColumn класса QTest (в котором указывается тип и
имя столбца), а затем добавляются строки-тесты, за что отвечает метод
newRow (его параметром является имя теста). Оба метода – статические,
так что создавать объект класса QTest не требуется (см. рис 5.2.6).
Тестирующий слот пишется в этом случае для выполнения одного
теста. В нем мы используем макрос QFETCHдля получения данных из
таблицы, при этом типы и имена переменных при создании столбца
таблицы и указанные в QFETCH обязательно должны совпадать.
Слот add_data() задает таблицу из трех строк, соответственно, слот
add() запустится три раза.
Если запустить проект обычной командой Debug / Start Debugging (F5),
то консоль, куда выводится вся интересующая нас информация о
результатах тестирования, появится на долю секунды, и исчезнет. Поэтому
проект лучше запускать через Debug / Start Without Debugging (Ctrl+F5).
Результат запуска программы показан на рис. 5.2.7. Тесты пройдены
успешно.
24
Файл main.cpp
#include <QtTest>
#include "MyMathClass.h"
class Test_MyMathClass : public QObject //Наследование от QObject
{
Q_OBJECT //Обязательный макрос
private slots:
//В этом слоте создается таблица данных
void add_data();
/* А этот слот отвечает за изъятие данных из таблицы и передачу их в
тестируемый метод */
void add();
};
void Test_MyMathClass::add_data()
{
//Создаем колонки‐параметры метода + ожидаемый результат
QTest::addColumn<int>("arg1");
QTest::addColumn<int>("arg2");
QTest::addColumn<int>("result");
//Создаем строки‐тесты и заполняем таблицу данными
QTest::newRow("Test1") << ‐7 << 12 << 5;
QTest::newRow("Test2") << 6 << 0 << 6;
QTest::newRow("Test3") << 13 << ‐4 << 9;
}
void Test_MyMathClass::add()
{
/* Создаем объект тестируемого класса,
чтобы было откуда вызывать методы */
MyMathClass myClass;
/* Макрос QFETCH изымает из таблицы данные в указанные переменные.
Тип и имя обязательно должны совпадать с типом и именем колонки
QFETCH(int, arg1);
QFETCH(int, arg2);
QFETCH(int, result);
/* Вызываем метод класса и сравниваем
полученное значение с ожидаемым
*/
QCOMPARE(myClass.add(arg1, arg2), result);
}
//Этот макрос заменяет main
QTEST_MAIN(Test_MyMathClass)
//Файл метаинформации, обязательно должен быть
#include"main.moc"
Рисунок 5.2.6 Код программы, осуществляющей тестирование,
управляемое данными
25
*/
Рисунок 5.2.7. Результат тестирования программы с пройденными тестами.
5.2.4 Макросы процесса тестирования QTestLib
QCOMPARE (actual, expected) принимает два аргумента:
полученный и ожидаемый результаты, которые сравниваются. Если
значения не совпадают, то тогда
исполнение тестового метода
прерывается с сообщением о не пройденном тесте.
QFETCH (type,name) создает локальнуюпеременную с указанным
именем и типом и заносит в нее данные из столбца таблицы (строка
выбирается в соответствии с текущим тестом) с тем же именем. Имя и тип
переменной должны совпадать с именем и типом столбца таблицы.
QTEST
(actual,
testElement) объединяет возможности
QFETCH и QCOMPARE, сравнивая значение actual со значением в
столбце
testElement
таблицы.
Пример:
QTEST
(myClass.add(arg1, arg2), "result");
QVERIFY(condition)
проверяет
правильность
условия
(фактически реализует утверждение). Если значение истинно, то
выполнение теста продолжается. Если нет, то тест далее не исполняется и
производится отображение сообщения об ошибке.
QVERIFY2 (condition, message) аналогичен QVERIFY, но в
этом макросе указывается сообщение, которое будет выдаваться при
невыполнении условия.
5.2.5 Запуск тестов в программе, имеющей функцию main
В некоторых случаях тестирующая программа должна заниматься не
только проведением тестов. В этом случае макрос QTEST_MAIN()
использовать уже не получится, т.к. он генерирует свою функцию
main(). Вместо этого, для запуска теста из какого-либо класса
необходимо создать объект тестирующего класса. Для выполнения тестов
необходимо передать указатель на созданный объект в статическую
функцию QTest::qExec() (при наличии нескольких тестирующих
классов повторите эти шаги для каждого из них). Пример функции main
для созданного нами тестирующего класса показан на рис. 5.2.8.
26
Функция qExec перегружена. В расширенном варианте в качестве
второго аргумента можно передать параметры для запуска тестов в виде
списка строк QStringList. Наиболее важные из них представлены в
таблице 5.2.2.
Файл main.cpp (частично)
//Тут или в других файлах находится описание тестирующего
класса и его слотов
//...
//Главная функция программы
void main()
{
//Создаем объект тестирующего класса
Test_MyMathFunction test;
//Запускаем тесты на выполнение, передаем адрес объекта
тестирующего класса
QTest::qExec(&test);
//Задержка экрана
getchar();
}
Рисунок 5.2.8 Самостоятельный запуск тестирования в QTestLib
Параметр
-help
-functions
-o <filename>
-silent
-v1
-v2
-xml
-eventdelay <ms>
Таблица 5.2.2 Параметры для запуска тестов
Описание
Выводит возможные параметры для запуска тестов
Выводит все тестирующие функции, доступные в тесте
Записывает результаты теста в файл
«Тихий режим» - показ только предупреждений и ошибок
Выводит входные и выходные параметры тестовых методов
Расширяет –v1: выводит сообщения для QCOMPARE() и
QVERIFY()
Вывод результатов теста в формате XML
Делать задержку (в миллисекундах) перед каждым тестом.
Полезно для тестирования графического интерфейса
5.2.6 Возможные проблемы при использовании QTestLib
Не получается занести в таблицу данных собственный тип данных.
Если в вашу тестируемую функцию передается собственный тип
данных (объект класса, структура и т.д.), то чтобы занестиего в таблицу
данных тип нужно зарегистрировать.
Для
регистрации
типа
данных
используется
макрос
Q_DECLARE_METATYPE, который в качестве агрумента принимает
регистрируемый тип. Например: Q_DECLARE_METATYPE(MyClass).
27
Макрос этот необходимо вызывать вне кода методов (например перед
объявлением тестирующего класса – но, разумеется, после объявления
регистрируемого типа данных).
Не получается занести в таблицу данных стандартный контейнер Qt.
Контейнеры, которые в своем описании в угловых скобках имеют
только один тип данных (QList, QVector) необходимо регистрировать
описанным выше способом. А вот при попытке регистрации таких
контейнеров, как например, QPair или QHash, для которых в угловых
скобках указывается 2 типа, возникает ошибка – ограничение макросов
языка С++ не позволяет использовать запятую внутри параметра макроса.
Чтобы избежать этого, нужно сначала переименоватьтип контейнера
директивой typedef так, чтобы он не содержал запятой, а затем уже этот
идентификатор передавать в макрос Q_DECLARE_METATYPE.
Например,в
таблицу
данных
нужно
занести
контейнер
QMap<QString, int>. Регистрация показана на рисунке 5.2.9.
//Переименуем QMap<QString, int>в newQMapName
typedef QMap<QString, int> newQMapName;
//Зарегистрируем
Q_DECLARE_METATYPE(newQMapName)
Рисунок 5.2.9. Регистрация типа данных, содержащего в себе запятую.
Не получается занести в таблицу данных массив.
Если функция принимает в качестве аргумента массив, возникает
необходимость занесения массива в таблицу данных. Видоизменим
функцию add() – пусть в нее будет передаваться массив, элементами
которого являются целые числа, и количество элементов в нем. Функция
возвращает сумму всех элементов (см. рис. 5.2.10).
Файл MyMathFunction.h
//Суммирует элементы переданного массива
int sumArray(int* arr, int size)
{
int result = 0;
for(int i=0; i<size; ++i)
result += arr[i];
return result;
}
Рисунок 5.2.10. Тестируемая функция, принимающая массив
В колонках в таблицы данных теперь будут храниться массив,
количество элементов в нем и ожидаемый результат. В таблицу данных
нельзя занести массив, но можно занести указатель на его первый элемент.
Массив при этом должен быть динамическим (в противном случае он
может исчезнуть после окончания создания таблицы). Необходимо также
28
зарегистрировать используемый тип указателя (в примере int*) с
помощью макроса Q_DECLARE_METATYPE (см. рис. 5.2.11).
Файл main.cpp
#include <QtTest>
#include "MyMathFunction.h"
Q_DECLARE_METATYPE(int*)
class Test_MyMathFunction : public QObject
//Наследованиеот QObject
{
Q_OBJECT //Обязательный макрос
private slots: //В этом слоте создается таблица данных
void sumArrayt_data();
/* А этот слот отвечает за изъятие данных из таблицы
и передачу их в тестируемый метод */
void sumArrayt();
};
void Test_MyMathFunction::sumArrayt_data()
{
//Создаем колонки‐параметры метода + ожидаемый результат
QTest::addColumn<int*>("arr");
QTest::addColumn<int>("size");
QTest::addColumn<int>("result");
//Создаем 2 массива для тестов
int* arr1 = newint[5];
arr1[0] = 1;
arr1[1] = 3;
arr1[2] = 0;
arr1[3] = ‐2;
arr1[4] = 4;
int* arr2 = newint[4];
arr2[0] = 5;
arr2[1] = ‐1;
arr2[2] = 3;
arr2[3] = 6;
//Создаем строки‐тесты и заполняем таблицу данными
QTest::newRow("Test1") << arr1 << 5 << 6;
QTest::newRow("Test2") << arr2 << 4 << 13;
}
void Test_MyMathFunction::sumArrayt()
{
/* Макрос QFETCH изымает из таблицы данные в указанные переменные.
Тип и имя обязательно должны совпадать с типом и именем колонки */
QFETCH(int*, arr);
QFETCH(int, size);
QFETCH(int, result);
/* Вызываем метод класса и сравниваем
полученное значение с ожидаемым
*/
QCOMPARE(sumArray(arr, size), result);
}
//Этот макрос заменяет main
QTEST_MAIN(Test_MyMathFunction)
//Файл метаинформации обязательно должен быть
#include"main.moc"
Рисунок 5.2.11. Код программы, осуществляющий тестирование,
управляемое данными, с хранением массива в таблице
29
6 Контрольные вопросы
1. Опишите преимущества использования модульных тестов.
2. Что такое разработка, управляемая тестами?
3. Что такое тестирование, управляемое данными?
4. Сравните возможности библиотек тестирования Visual Assertи
QTestLib.
5. Какие типы данных необходимо регистрировать в QTestLib при
использовании тестирования, управляемого данными?
6. Каким образом можно запустить процесс тестирования в Visual
Assert?
7. Каким образом можно запустить процесс тестирования в QTestLib?
8. Перечислите специализированные методы классов тестирования в
Visual Assert и QTestLib.
30
7 Список литературы
1. Котляров В. П., Коликова Т. В. Основы тестирования программного обеспечения : учеб. пособие. – М.: Интернет-Университет Информационных Технологий;
БИНОМ. Лаборатория знаний, 2006. – 285 с.
2. Дастин Э., Рэшка Дж., Пол Дж. Автоматизированное тестирование программного обеспечения. – М.: Лори, 2003. – 567 с.
3. Винниченко И. В. Автоматизация процессов тестирования. – СПб.: Питер,
2005. – 203 с.
4. Тамре Л. Введение в тестирование программного обеспечения. – М.: Вильямс,
2003. – 368 с.
31
Учебное издание
Составители
Олег Александрович Сычев
Андрей Федорович Ушаков
МОДУЛЬНОЕ ТЕСТИРОВАНИЕ ПРОГРАММ НА ЯЗЫКЕ С++
Методические указания
Темплан 2013 г. (учебно-методическая литература). Поз. № 150.
Подписано в печать 06.06.2013. Формат 60x84 1/16. Бумага офсетная.
Гарнитура Times. Печать офсетная. Усл. печ. л. 1,86.
Тираж 10 экз. Заказ
Волгоградский государственный технический университет.
400005, г. Волгоград, просп. им. В. И. Ленина, 28, корп. 1.
Отпечатано в типографии ИУНЛ ВолгГТУ.
400005, г. Волгоград, просп. им. В. И. Ленина, 28, корп. 7.
32
Download