Модульное тестирование 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

Постепенно дополняйте тесты и следите за тем, чтобы они проходились.

Отладка непройденного теста

Выполнить рефакторинг кода без изменения тестов

Создание решения и проекта модульного теста

  1. В меню Файл выберите команду Создать, затем пункт Новый проект.

  2. В диалоговом окне "Новый проект" разверните узел Установленные, узел Visual C++ и выберите Магазин Windows. В списке шаблонов проектов выберите Библиотека модульных тестов (приложения для Магазина Windows).

    Создание библиотеки модульных тестов C++

  3. Назовите проект RooterLibTests; укажите расположение; назовите решение RooterLib; проверьте, что установлен флажок Создать каталог для решения.

    Задание решения, имени проекта и расположения

  4. В новом проекте откройте файл unittest1.cpp.

    unittest1.cpp

    Обратите внимание на следующее.

    • Каждый тест определен с помощью TEST_METHOD(YourTestName){...}.

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

    • Методы тестов группируются в классы с помощью TEST_CLASS(YourClassName){...}.

      При запуске тестов создается экземпляр каждого класса теста. Методы теста вызываются в неопределенном порядке. Можно задать особые методы, которые вызываются до и после каждого модуля, класса или метода. Дополнительные сведения см. в разделе Использование пространства имен Microsoft.VisualStudio.TestTools.CppUnitTestFramework в библиотеке MSDN.

С помощью обозревателя тестов проверьте, что тесты выполняются

  1. Добавьте код теста:

    TEST_METHOD(TestMethod1)
    {
        Assert::AreEqual(1,1);
    }
    

    Обратите внимание, что класс Assert содержит несколько статических методов, которые можно использовать для проверки результатов в методах теста.

  2. В меню Тест выберите Выполнить, а затем выберите Запустить все.

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

    Обозреватель тестов

Добавьте проект DLL в решение

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

    Создание проекта RooterLib

  2. В диалоговом окне Добавить новый проект выберите DLL (приложения из Магазина Windows).

  3. Добавьте в файл 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.

  4. Добавьте символ ROOTERLIB_EXPORTS в командную строку.

    1. В обозревателе решений выберите проект RooterLib, а затем в контекстном меню выберите пункт Свойства.

      Добавление определения символа препроцессора

    2. В диалоговом окне страницы свойств RooterLib последовательно разверните узлы Свойства конфигурации и C++, а затем выберите пункт Препроцессор.

    3. В списке Определения препроцессора выберите <Изменить...>, а затем добавьте ROOTERLIB_EXPORTS в диалоговом окне определений препроцессора.

  5. Добавьте минимальные реализации объявленных функций. Откройте файл RooterLib.cpp и добавьте следующий код:

    // constructor
    CRooterLib::CRooterLib()
    {
    }
    
    // Find the square root of a number.
    double CRooterLib::SquareRoot(double v)
    {
        return 0.0;
    }
    

Свяжите проект теста с проектом DLL

  1. Добавьте RooterLib в проект RooterLibTests.

    1. В обозревателе решений выберите проект RooterLibTests и в контекстном меню выберите команду Ссылки....

    2. В диалоговом окне свойств проекта RooterLib разверните узел Общие свойства и выберите .NET Framework и ссылки.

    3. Щелкните Добавить новую ссылку...

    4. В диалоговом окне Добавить ссылку разверните узел Решение и выберите Проекты. Затем выберите элемент RouterLib.

  2. Укажите файл заголовков RooterLib в файле unittest1.cpp.

    1. Откройте файл unittest1.cpp.

    2. Добавьте следующий код ниже строки #include "CppUnitTest.h":

      #include "..\RooterLib\RooterLib.h"
      
  3. Добавьте тест, который использует импортированную функцию. Добавьте следующий код в файл 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());
    }
    
  4. Выполните построение решения.

    Новый тест появится в обозревателе тестов в узле Незапускавшиеся тесты.

  5. В обозревателе тестов выберите Запустить все.

    Основной тест пройден

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

Постепенно дополняйте тесты и следите за тем, чтобы они проходились.

  1. Добавьте новый тест:

    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);
        }
    };
    

    Совет

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

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

  2. В обозревателе тестов выберите Запустить все.

  3. Тест не пройден.

    Сбой теста RangeTest

    Совет

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

  4. Измените код теста, чтобы новый тест был пройден. Добавьте в файл 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;
    }
    
  5. Постройте решение, а затем в обозревателе тестов выберите Запустить все.

    Оба теста пройдены.

Совет

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

Отладка непройденного теста

  1. Добавьте в файл 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());
         }
       }
    };
    
  2. В обозревателе тестов выберите Запустить все.

    Тест не пройден. Выберите имя теста в обозревателе тестов. Утверждение, вызвавшее сбой, будет выделено. Сообщение о сбое отображается в области сведений обозревателя тестов.

    Сбой тестов NegativeRangeTests

  3. Чтобы определить причину сбоя теста, пошагово выполните функцию.

    1. Установите точку останова перед функцией SquareRoot.

    2. В контекстном меню непройденного теста выберите Отладить выбранные тесты.

      При остановке выполнения на точке останова выполните код пошагово.

    3. Добавьте в файл 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");
          }
      ...
      
    1. В обозревателе тестов выберите Запустить все, чтобы протестировать исправленный метод и убедиться, что не была добавлена регрессия.

Теперь все тесты проходятся.

Все тесты пройдены

Выполнить рефакторинг кода без изменения тестов

  1. Упростите основной расчет функции SquareRoot:

    // old code
    //result = result - (result*result - v)/(2*result);
    // new code
    result = (result + v/result) / 2.0;
    
  2. Выберите команду Запустить все, чтобы протестировать подвергнутый рефакторингу метод и убедиться, что не была добавлена регрессия.

    Совет

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

    Изменения в рамках рефакторинга должны быть отделены от других изменений.