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

В Visual Studio можно создать модульные тесты для неуправляемого кода, написанного на языке C++.Неуправляемый код иногда называют машинным кодом.

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

Чтобы написать модульные тесты для библиотеки DLL неуправляемого кода

  1. Используйте шаблон Тестовый проект для машинного кода, чтобы создать отдельный проект Visual Studio для тестов.

    Проект содержит некоторый пример кода теста.

  2. Сделайте библиотеку DLL доступной тестовому проекту:

    • #include файл .h, содержащий объявления внешне доступных функций DLL.

      Файл .h должен содержать объявления функций, отмеченных _declspec(dllimport).Кроме того, можно экспортировать методы, используя DEF-файл.Для получения дополнительной информации см. Импортирование и экспортирование.

      Модульные тесты могут получить доступ только к функциям, экспортированным из библиотеки DLL с тестом.

    • Добавьте проект DLL к Ссылке на тестовый проект:

      В Свойства тестового проекта разверните Общие свойства, .NET Framework и ссылки и выберите Добавить ссылку.

  3. В тестовом проекте создайте тестовые классы и методы теста с помощью макросов ТЕСТА и утвердите класс следующим образом:

    #include "stdafx.h"
    #include <CppUnitTest.h>
    #include "..\MyProjectUnderTest\MyCodeUnderTest.h"
    using namespace Microsoft::VisualStudio::CppUnitTestFramework;
    TEST_CLASS(TestClassName)
    {
    public:
      TEST_METHOD(TestMethodName)
      {
        // Run a function under test here.
        Assert::AreEqual(expectedValue, actualValue, L"message", LINE_INFO());
      }
    }
    
    • Assert содержит несколько статических функций, которые можно использовать для проверки результата теста.

    • Параметр LINE_INFO() является необязательным.В тех случаях, когда нет PDB-файла, он позволяет средству Test Runner индентифицировать местоположение ошибки.

    • Можно также написать методы установки и очистки теста.Для получения дополнительных сведений откройте определение макроса TEST_METHOD и прочитайте комментарии в CppUnitTest.h

    • Нельзя вкладывать тестовые классы.

  4. Используйте обозреватель тестов для выполнения тестов:

    1. В меню Вид выберите Другие окна, Обозреватель тестов.

    2. Создайте решение Visual Studio.

    3. В Обозревателе тестов выберите Выполнить все.

    4. Чтобы более подробно исследовать любой тест в обозревателе тестов:

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

      2. Открытие имя теста (например, дважды щелкнув), чтобы перейти к расположению ошибки или к коду теста.

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

Пошаговое руководство. Разработка неуправляемой библиотеки DLL с обозревателем тестов

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

  1. СоздайтеТестовый Проект для машинного кода.Тесты созданы в отдельном проекте из библиотеки DLL, разработка которой ведется.

  2. Создайте проект библиотеки DLL.В этом пошаговом руководстве создается новая библиотека DLL, но процедура для тестирования существующего библиотеки DLL аналогична.

  3. Сделайте функции DLL видимыми тестам.

  4. Итеративно увеличивайте тесты.Рекомендуется цикл "красный-зеленый-рефакторинг", в котором разработка кода поддерживается тестами.

  5. Выполните отладку непройденных тестов.Тесты можно выполнять в режиме отладки.

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

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

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

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

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

    В диалоговом окне разверните Установлено, Шаблоны, Visual C++, Тест.

    Выберите шаблон Тестовый проект для машинного кода.

    В этом пошаговом руководстве тестовый проект называется NativeRooterTest.

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

  2. В новом проекте изучите unittest1.cpp.

    Проект теста с TEST_CLASS и TEST_METHOD

    Обратите внимание, что:

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

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

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

      Во время выполнения тестов создается экземпляр каждого тестового класса.Тестовые методы вызываются в неопределенном порядке.Можно задать особые методы, которые вызываются до и после каждого модуля, класса или метода.Дополнительные сведения см. в разделе Organizing C++ Tests.

  3. Убедитесь, что тесты выполняются в обозревателе тестов:

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

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

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

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

      Произойдет построение и запуск теста.

      Появится обозреватель тестов.

      Тест отображается под Пройденные тесты.

      Обозреватель модульных тестов с одним пройденным тестом

Создайте проект неуправляемой библиотеки DLL.

  1. Создайте проект Visual C++ с помощью шаблона Проект Win32.

    В этом пошаговом руководстве проект называется RootFinder.

    Создание проекта Win32 C++

  2. Выберите DLL и Экспорт символов в Мастере приложений Win32.

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

    Мастер проектов C++, настроенный для DLL и символов экспорта

  3. Объявите экспортированную функцию в .h файле субъекта:

    Новый проект кода DLL и H-файл с макросами API

    Декларатор __declspec(dllexport) делает открытые и защищенные члены класса видимыми вне библиотеки DLL.Для получения дополнительной информации см. Использование dllimport и dllexport в классах C++.

  4. В .cpp-файле субъекта добавьте минимальное тело для функции:

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

Соедините тестовый проект и проект библиотеки DLL

  1. Добавьте проект DLL к ссылке проекта на тестовый проект:

    1. Откройте свойства тестового проекта и выберите Общие свойства, .NET Framework и ссылки.

      Свойства проекта C++ — .NET Framework и ссылки

    2. Выберите Добавить новую ссылку.

      В диалоговом окне Добавить ссылку выберите проект библиотеки DLL и выберите Добавить.

      Свойства проекта C++ — добавление новой ссылки

  2. В .cpp-файле модульного теста субъекта включите .h-файл кода библиотеки DLL:

    #include "..\RootFinder\RootFinder.h"
    
  3. Добавьте простой тест, который использует экспортированную функцию:

    TEST_METHOD(BasicTest)
    {
    CRootFinder 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)
    {
      CRootFinder rooter;
      for (double v = 1e-6; v < 1e6; v = v * 3.2)
      {
        double actual = rooter.SquareRoot(v*v);
        Assert::AreEqual(v, actual, v/1000);
      }
    }
    
    СоветСовет

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

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

  2. Выполните построение решения, а затем в обозревателе тестов выберите Выполнить все.

    Новый тест не пройдет проверку.

    Сбой теста RangeTest

    СоветСовет

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

  3. Улучшите тестируемый код так, чтобы новый тест был успешно пройден:

    #include <math.h>
    ...
    double CRootFinder::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;
    }
    
  4. Выполните построение решения, а затем в обозревателе тестов выберите Выполнить все.

    Оба теста пройдут проверку успешно.

    Обозреватель модульных тестов — пройден тест диапазона

    СоветСовет

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

Выполните отладку непройденного теста

  1. Добавьте еще один тест:

    #include <stdexcept>
    ...
    // Verify that negative inputs throw an exception.
    TEST_METHOD(NegativeRangeTest)
    {
      wchar_t message[200];
      CRootFinder rooter;
      for (double v = -0.1; v > -3.0; v = v - 0.5)
      {
        try 
        {
          // Should raise an exception:
          double result = rooter.SquareRoot(v);
    
          _swprintf(message, L"No exception for input %g", v);
          Assert::Fail(message, LINE_INFO());
        }
        catch (std::out_of_range ex)
        {
          continue; // Correct exception.
        }
        catch (...)
        {
          _swprintf(message, L"Incorrect exception for %g", v);
          Assert::Fail(message, LINE_INFO());
        }
      }
    }
    
  2. Выполните построения решения и выберите Выполнить все.

  3. Откройте (или дважды щелкните) непройденный тест.

    Ошибочное утверждение будет выделено.Сообщение об ошибке отображается в области сведений обозревателя тестов.

    Сбой тестов NegativeRangeTests

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

    1. Установите точку останова в начале функции SquareRoot.

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

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

  5. Вставьте код в функцию, которую вы разрабатываете:

    #include <stdexcept>
    ...
    double CRootFinder::SquareRoot(double v)
    {
        // Validate parameter:
        if (v < 0.0) 
        {
          throw std::out_of_range("Can't do square roots of negatives");
        }
    
  6. Теперь все тесты проходят успешно.

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

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

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

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

    СоветСовет

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

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

Следующие шаги

  • Изоляция. Большинство библиотек DLL зависит от других подсистем, например, от баз данных и других библиотек DLL.Эти другие компоненты часто разрабатываются параллельно.Чтобы модульное тестирование могло выполняться пока другие компоненты еще не доступны, нужно заменить макет или

  • Тест проверки построения (BVT) Тесты можно выполнять на сервере построения рабочей группы через заданные промежутки времени.Это гарантирует то, что во время интеграции работы, выполненной некоторыми членами рабочей группы, не появятся новые ошибки.

  • Возврат тестов. Можно потребовать, чтобы некоторые тесты выполнялись перед тем, как каждый участник команды возвратит код в систему управления версиями.Обычно это подмножество полного набора тестов проверки построения.

    Можно также требовать минимального уровня покрытия кода.

См. также

Задачи

Walkthrough: Creating and Using a Dynamic Link Library (C++)

Основные понятия

Импортирование и экспортирование

Другие ресурсы

Общие сведения о взаимодействии управляемого и неуправляемого кода

Отладка машинного кода