Модульное тестирование DLL Visual C++ приложений для Магазина Windows
В этом разделе описан один из способов создания модульных тестов для библиотек DLL С++ для приложений для Магазина Windows с помощью Visual Studio 2012 Express для Windows 8 и среды модульного тестирования Майкрософт для С++. Библиотека DLL RooterLib демонстрирует концепции теории пределов из математического анализа за счет реализации функции, которая вычисляет оценку квадратного корня из заданного числа. Библиотека DLL может затем быть включена в приложение для Магазина Windows, демонстрирующее пользователю интересные возможности математических функций.
Примечание
В подразделах этого раздела описываются функциональные возможности Visual Studio 2012 Express для Windows 8. Visual Studio Ultimate, VS Premium и VS Professional предоставляют дополнительные функции для модульного тестирования.
-
В VS Ultimate, VS Premium и VS Professional можно использовать любые сторонние среды модульного тестирования или среды с открытым кодом, для которых создан адаптер надстройки для обозревателя тестов (Майкрософт). Можно также анализировать и отображать данные о покрытии кода для тестов.
-
В VS Ultimate и VS Premium можно выполнять тесты после каждого построения.
Дополнительные сведения см. в разделе Проверка кода при помощи модульных тестов в библиотеке MSDN.
В этом разделе демонстрируется использование модульного тестирования в качестве первого шага разработки. При таком подходе сначала необходимо написать метод теста, который проверяет определенное поведение тестируемой системы, а затем написать код, который проходит этот тест. Порядок описанных ниже процедур можно изменить, и сначала написать код, который требуется протестировать, а затем написать сами модульные тесты.
В этом разделе также создается одно решение Visual Studio и отдельные проекты для модульных тестов и для тестируемой библиотеки DLL. Модульные тесты можно включить непосредственно в проект библиотеки DLL или создать отдельные решения для модульных тестов и для DLL. Советы по выбору структуры см. в разделе Модульное тестирование существующих приложений C++ с использованием обозревателя тестов.
Содержание раздела
В данном разделе описано выполнение следующих задач:
Создание решения и проекта модульного теста
С помощью обозревателя тестов проверьте, что тесты выполняются
Добавьте проект DLL в решение
Свяжите проект теста с проектом DLL
Постепенно дополняйте тесты и следите за тем, чтобы они проходились.
Отладка непройденного теста
Выполнить рефакторинг кода без изменения тестов
Создание решения и проекта модульного теста
В меню Файл выберите команду Создать, затем пункт Новый проект.
В диалоговом окне "Новый проект" разверните узел Установленные, узел Visual C++ и выберите Магазин Windows. В списке шаблонов проектов выберите Библиотека модульных тестов (приложения для Магазина Windows).
Назовите проект RooterLibTests; укажите расположение; назовите решение RooterLib; проверьте, что установлен флажок Создать каталог для решения.
В новом проекте откройте файл unittest1.cpp.
Обратите внимание на следующее.
Каждый тест определен с помощью TEST_METHOD(YourTestName){...}.
Писать обычную сигнатуру функции не требуется. Сигнатура создается макросом TEST_METHOD. Макрос создает функцию экземпляра, которая возвращает значение void. Он также создает статическую функцию, которая возвращает сведения о методе теста. Эти сведения позволяют обозревателю тестов находить метод.
Методы тестов группируются в классы с помощью TEST_CLASS(YourClassName){...}.
При запуске тестов создается экземпляр каждого класса теста. Методы теста вызываются в неопределенном порядке. Можно задать особые методы, которые вызываются до и после каждого модуля, класса или метода. Дополнительные сведения см. в разделе Использование пространства имен Microsoft.VisualStudio.TestTools.CppUnitTestFramework в библиотеке MSDN.
С помощью обозревателя тестов проверьте, что тесты выполняются
Добавьте код теста:
TEST_METHOD(TestMethod1) { Assert::AreEqual(1,1); }
Обратите внимание, что класс Assert содержит несколько статических методов, которые можно использовать для проверки результатов в методах теста.
В меню Тест выберите Выполнить, а затем выберите Запустить все.
Будет построен и запущен проект теста. Появится окно обозревателя тестов, а тест будет указан в разделе Пройденные тесты. Область сводки в нижней части окна содержит дополнительные сведения о выбранном тесте.
Добавьте проект DLL в решение
В обозревателе решений выберите имя решения. В контекстном меню выберите команду Добавить, а затем щелкните Добавить новый проект.
В диалоговом окне Добавить новый проект выберите DLL (приложения из Магазина Windows).
Добавьте в файл RooterLib.h следующий код:
// The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the ROOTERLIB_EXPORTS // symbol defined on the command line. This symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // ROOTERLIB_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef ROOTERLIB_EXPORTS #define ROOTERLIB_API __declspec(dllexport) #else #define ROOTERLIB_API __declspec(dllimport) #endif //ROOTERLIB_EXPORTS class ROOTERLIB_API CRooterLib { public: CRooterLib(void); double SquareRoot(double v); };
Комментарии описывают назначение блока ifdef не только для автора DLL, но и для всех разработчиков, которые ссылаются на эту библиотеку DLL в своих проектах. С помощью свойства проекта DLL можно добавить в командную строку символ ROOTERLIB_EXPORTS.
Класс CRooterLib объявляет конструктор и метод оценки SqareRoot.
Добавьте символ ROOTERLIB_EXPORTS в командную строку.
В обозревателе решений выберите проект RooterLib, а затем в контекстном меню выберите пункт Свойства.
В диалоговом окне страницы свойств RooterLib последовательно разверните узлы Свойства конфигурации и C++, а затем выберите пункт Препроцессор.
В списке Определения препроцессора выберите <Изменить...>, а затем добавьте ROOTERLIB_EXPORTS в диалоговом окне определений препроцессора.
Добавьте минимальные реализации объявленных функций. Откройте файл RooterLib.cpp и добавьте следующий код:
// constructor CRooterLib::CRooterLib() { } // Find the square root of a number. double CRooterLib::SquareRoot(double v) { return 0.0; }
Свяжите проект теста с проектом DLL
Добавьте RooterLib в проект RooterLibTests.
В обозревателе решений выберите проект RooterLibTests и в контекстном меню выберите команду Ссылки....
В диалоговом окне свойств проекта RooterLib разверните узел Общие свойства и выберите .NET Framework и ссылки.
Щелкните Добавить новую ссылку...
В диалоговом окне Добавить ссылку разверните узел Решение и выберите Проекты. Затем выберите элемент RouterLib.
Укажите файл заголовков RooterLib в файле unittest1.cpp.
Откройте файл unittest1.cpp.
Добавьте следующий код ниже строки #include "CppUnitTest.h":
#include "..\RooterLib\RooterLib.h"
Добавьте тест, который использует импортированную функцию. Добавьте следующий код в файл unittest1.cpp:
TEST_METHOD(BasicTest) { CRooterLib rooter; Assert::AreEqual( // Expected value: 0.0, // Actual value: rooter.SquareRoot(0.0), // Tolerance: 0.01, // Message: L"Basic test failed", // Line number - used if there is no PDB file: LINE_INFO()); }
Выполните построение решения.
Новый тест появится в обозревателе тестов в узле Незапускавшиеся тесты.
В обозревателе тестов выберите Запустить все.
Вы настроили проекты теста и кода и убедились, что можно запускать тесты, которые выполняют функции в проекте кода. Теперь можно начинать разработку реальных тестов и кода.
Постепенно дополняйте тесты и следите за тем, чтобы они проходились.
Добавьте новый тест:
TEST_METHOD(RangeTest) { CRooterLib rooter; for (double v = 1e-6; v < 1e6; v = v * 3.2) { double expected = v; double actual = rooter.SquareRoot(v*v); double tolerance = expected/1000; Assert::AreEqual(expected, actual, tolerance); } };
Совет
Рекомендуется не изменять пройденные тесты. Вместо этого добавьте новый тест, обновите код так, чтобы тест успешно проходился, а затем добавьте еще один тест и т. д.
По мере изменения требований пользователей отключайте тесты, которые больше не являются верными. Создавайте новые тесты по одному и следите за тем, чтобы они работали.
В обозревателе тестов выберите Запустить все.
Тест не пройден.
Совет
Проверяйте, что тест не проходится, сразу после написания кода этого теста. Это помогает избежать простой ошибки, связанной с написанием теста, который не может быть не пройден.
Измените код теста, чтобы новый тест был пройден. Добавьте в файл RooterLib.cpp следующий код:
#include <math.h> ... // Find the square root of a number. double CRooterLib::SquareRoot(double v) { double result = v; double diff = v; while (diff > result/1000) { double oldResult = result; result = result - (result*result - v)/(2*result); diff = abs (oldResult - result); } return result; }
Постройте решение, а затем в обозревателе тестов выберите Запустить все.
Оба теста пройдены.
Совет
Разрабатывайте код, добавляя тесты по одному. После каждой итерации проверяйте, что все тесты проходятся.
Отладка непройденного теста
Добавьте в файл unittest1.cpp еще один тест:
// Verify that negative inputs throw an exception. TEST_METHOD(NegativeRangeTest) { wchar_t message[200]; CRooterLib rooter; for (double v = -0.1; v > -3.0; v = v - 0.5) { try { // Should raise an exception: double result = rooter.SquareRoot(v); swprintf_s(message, L"No exception for input %g", v); Assert::Fail(message, LINE_INFO()); } catch (std::out_of_range ex) { continue; // Correct exception. } catch (...) { swprintf_s(message, L"Incorrect exception for %g", v); Assert::Fail(message, LINE_INFO()); } } };
В обозревателе тестов выберите Запустить все.
Тест не пройден. Выберите имя теста в обозревателе тестов. Утверждение, вызвавшее сбой, будет выделено. Сообщение о сбое отображается в области сведений обозревателя тестов.
Чтобы определить причину сбоя теста, пошагово выполните функцию.
Установите точку останова перед функцией SquareRoot.
В контекстном меню непройденного теста выберите Отладить выбранные тесты.
При остановке выполнения на точке останова выполните код пошагово.
Добавьте в файл RooterLib.cpp код для перехвата исключения:
#include <stdexcept> ... double CRooterLib::SquareRoot(double v) { //Validate the input parameter: if (v < 0.0) { throw std::out_of_range("Can't do square roots of negatives"); } ...
В обозревателе тестов выберите Запустить все, чтобы протестировать исправленный метод и убедиться, что не была добавлена регрессия.
Теперь все тесты проходятся.
Выполнить рефакторинг кода без изменения тестов
Упростите основной расчет функции SquareRoot:
// old code //result = result - (result*result - v)/(2*result); // new code result = (result + v/result) / 2.0;
Выберите команду Запустить все, чтобы протестировать подвергнутый рефакторингу метод и убедиться, что не была добавлена регрессия.
Совет
Стабильный набор хороших модульных тестов придает уверенность в том, что изменение кода не привело к появлению ошибок.
Изменения в рамках рефакторинга должны быть отделены от других изменений.