Новости программы Industrial Software Engineering
Укажите свой email для того чтобы получать полезные материалы по индустриальной разработке программного обеспечения
Новости программы Industrial Software Engineering
Укажите свой email для того чтобы получать полезные материалы по индустриальной разработке программного обеспечения

Test Driven Development & Qtestlib

Основная идея Test Driven Development (TDD). Написание Unit Test на Qt. Интерфейс Unit test.
Поговорим немного о Test Driven Development и о возможностях библиотеки QTestlib. Начнем вот с чего: обычно, когда у вас есть какой-либо проект, источником для разработки являются какие-либо требования, возможно, которые появились в результате обсуждения с заказчиком или в результате работы проектной команды - вот эти требования являются источником для дальнейшей разработки, которая ведется следующим образом. Определяется состав классов для разработки, ну или он уже определен. После этого, поскольку состав классов определен, определены и их интерфейсы, то есть разрабатываются какие-то описания, например, заголовочные файлы, и затем идет реализация. Здесь пути различных методов разработки расходятся. Традиционная разработка (не являющаяся Test Driven Development) ведется следующим образом. Сначала реализуются сами классы, затем пишутся юнит-тесты, которые дают обратную связь для того , чтобы реализацию классов как-то уточнить. Это - классическая разработка.

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

Если говорить о QTestlib, то это такой набор инструментов, который есть в составе Qt. Он позволяет разрабатывать Юнит-тесты для наших классов. Вот что следует знать о юнит-тестах и возможностях библиотеки QTestlib.

  1. один класс -- один юнит-тест, он же приложение, он же проект, (в данном случае под проектом понимается Qt-проект или .pro-файл) Соответственно, юнит тест это - некоторый исполняемый файл, обычно он носит название tst_*<имя класса>.

  2. юнит-тест содержит набор тест кейсов. Тест-кейс - это просто небольшой тест, который проверяет одну или несколько функций нашего класса.


Итак, попробуем создать какой-нибудь проект. Делать будем калькулятор, который умеет складывать и вычитать числа, а также помещать одно значение в память. Выполняем Open project->Qt->Unit-test, По-умолчанию включается модуль Qtest. Укажем имена ClassName - CaiculatorTest (это мы сейчас именуем то, как будет называться наш тест). Тест-слот - первая функция, которая будет создана по-умолчанию. В качестве первого тест-кейса, поскольку мы знаем что собираемся тестировать, определим функцию testSum. Далее, всё можем принять по-умолчанию. ВУАЛЯ! Всё. Таким образом, у нас получился проект, который сейчас состоит из одного файла tst.


CaiculatorTest, сам проект - вот он, и, собственно, внутри этого файла мы видим, что у нас есть ClassCaiculatorTest, который будет работать с нашим будущим Классом калькулятор. У него есть один тестовый слот - CaiculatorTest - вот он слот - TestSum - запустим. Да, сейчас мы видим, что работают все три теста. В данном случае - это один наш тест и два теста, которые связаны с собственно инициализацией нашего тестового класса. Это приватные слоты, не будем на них останавливаться.

Посмотрим на реализацию. Реализация такова: наш класс, в общем-то, работает неправильно,по классике Test Driven Development - первый тест должен падать. То есть, вот здесь должно быть вот так . Посмотрим. Да. Мы получили такое большое сообщение об ошибке. Значит наш тест упал, упал с надписью: FAILURE.

Вот здесь вот это можно увидеть: QVERIFY2, макрос - устроен следующим образом: есть собственно проверяемое условие и некоторый текст, который выводится в случае возникновения ошибки. В данном случае, мы ошибку форсировали, ну и, собственно, он падает, если теперь кликнуть на эту ссылку, то мы попадем в место возникновения ошибки. Теперь у нас классически неработающий класс с тестом. (Вернее класса ещё нет но, есть юнит-тест). Давайте создадим Класс Caiculator. Это будет новый класс - С++класс - выбираем, называем его Calculator, базовый класс у него будет QObject. И всё. Финиш - Отлично!

И теперь давайте, собственно, сделаем первый метод нашего класса, пускай для простоты он будет целочисленным. Он будет складывать два числа, всего-то. Давайте его реализуем. Собственно return a+b . Запустим. Естественно тест не починился, потому что мы пытались его починить. Давайте мы его починим. Нам нужно добавить калькулятор. Вот он есть. Теперь мы создадим объект этого класса и уже в реальности вызовем sum(3+5), Ну, видимо, это будет 8. Запустим. Вот у нас все отработало прекрасно.

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

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

Понятное дело, что сейчас, если мы запустим компиляцию этого всего, работать это не будет, поскольку мы должны добавить соответствующие функции внутрь нашего калькулятор теста. Давайте это сделаем. Добавляем функции sub, putMem, getMem. Далее сделаем заготовки для этих реализаций. Отлично! Мы имеем какие-то предупреждения и имеем два неработающих теста.

Давайте, сначала, разберемся вот с этим тестом. Надо реализовать разность. Разность реализуется вот так. Как это ни странно. Вот один тест мы починили. Уже четыре проходит, один не проходит. Для того чтобы, у нас проходил последний тест нам надо добавить какое-то дополнительное поле в наш класс. И, соответственно, мы не будем его инициализировать в этом методе, мы его проинициализируем в конструкторе. Проинициализируем нулем и, соответственно, здесь. Прекрасно! Есть пять тестов, которые проходят, соответственно, можно считать, что наш калькулятор уже работает, и отдавать его потребителям.

Что же мы в итоге получили после сборки нашего проекта? Получили мы следующее: вот есть наш каталог, мы можем его руками собрать, для того, чтобы стало понятно, что у нас происходит. Вот происходит следующее: генерируются некоторый файлы tst_calculatortest. Этот файл - юнит-тест для класса калькулятор. В QTestlib принята такая стратегия - один юнит-тест - это один проект - один исполняемый файл. Поэтому мы не можем собрать кучу Юнит-тестов в одном проекте, но нам никто не запрещает сделать некоторую автоматизацию. Многие существующие системы автоматического тестирования понимают интерфейс QTestlib тестов. Давайте просто запустим наш тест и посмотрим какой интерфейс он предлагает. Вот, мы видим здесь то же самое, что мы видели в среде разработки, но есть несколько опций.

Самая полезная опция -help. Здесь мы можем получить большое количество различных опций, как по запуску этого файла, так и по форматированию вывода. Давайте с этим немножко поиграем. Можно, например, перевести наш вывод в какой-нибудь читабельный формат. Это -o, например, какой-нибудь -o log.xml,xml. Вывели. Log.xml выглядит у нас следующим образом. Это может быть разобрано какими-нибудь инструментами репортинга или формирования статистики.

Ещё один полезный инструмент для того, чтобы формировать тест-сеты, то есть некоторые поднаборы тестов, (например, smoke-тестирования, или набор для тестирования какой-то части системы). Например, если мы запустим -functions у нашего теста, мы увидим, что есть три тест кейса, которые могут быть вызваны независимо друг от друга. Давайте попробуем. Напишем, что это будет testSum и testMem. Мы видим, что исполнилось два теста, не считая InitTestCase и CleanupTestCase. И все они успешно прошли. Таким образом, мы можем формировать наши наборы для испытания работоспособности тех или иных частей разрабатываемых системой и отдавать данные в нужном виде для систем анализа.

Вся информация о QTestlib находится в документации. Все классы и макросы живут в QTest namespace. Здесь, первое, на что стоит посмотреть, это набор макросов, которые есть. Часть из них мы уже использовали. Это - QTEST_APPLESS_MAIN, это не графический main нашего набора тест-кейсов, то есть для теста. Также используется QTEST_MAIN для графических возможностей.

Мы использовали QVERIFY2 для того, чтобы генерировать сообщения. Правда, мы не очень отдавали себе отчет и не правили сообщения, там было просто FAIL, но можно написать вполне себе осознанное сообщение, которое выводится в случае, если вот это условие не является true. Можно просто использовать версию упрощенную QVERIFY. Также есть возможность выводить предупреждения. И, одна из интересных возможностей, которую вы можете изучить самостоятельно - возможность построения бэнчмарков. Это тоже юнит-тесты, но, которые нацелены на то, чтобы измерять время, измерять производительность. Об этом в данном уроке речи идти не будет, поскольку глубоко изучать Qtestlib библиотеку мы не планировали.