Предыдущие посты в этой серии постов:

  1. Как начать изучать тестирование программ на C++;
  2. Начало изучения системы сборки программ CMake;
  3. Интеграция модульных тестов в проект на C++.

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

Подходящий пример нашелся в книге «Modern C++ Programming with Test-Driven Development» Джеффа Лангра (вышла в 2013 году), которую я уже упоминал ранее.

Напомню, речь в этой книге идет про TDD (Test-Driven Development, по-русски «Разработка через тестирование»). По этой методике сначала пишется тест, затем под этот тест добавляется программный код. Причем мы пишем не сразу все тесты, а добавляем по одному, сразу же обеспечивая соответствующий программный код. То есть это постепенная (инкрементная) разработка.

Всего в книге 11 глав. В первой главе, которая называется «Global Setup», рассказано об инструментах, которые будут использованы, и о том, как их установить на компьютеры с разными операционными системами. Я эту главу прочел, но никак на нее не полагался. Мы использовали систему сборки CMake, компилятор MSVC, редактор кода VS Code и тестирующий фреймворк «Google Test» по-своему, я это описывал в предыдущих постах.

В плане установки инструментов стремно полагаться на книгу, написанную 13 лет назад, причем автор предпочитает Linux (хотя в книге есть инструкции и для Windows), а мы работаем на компьютерах с Windows 10.

Во второй главе, которая называется «Test-Driven Development: A First Example» (занимает 40 страниц книги из 335 всего), автор описывает, как работать по методике TDD, на конкретном примере: создание класса Soundex, реализующего алгоритм сравнения строк по звучанию. Этот алгоритм так и называется: Soundex. На входе — любое слово из английского языка, на выходе — четырехсимвольный код. Предполагается, что у слов со схожим звучанием будет похожий или идентичный четырехсимвольный код, полученный по этому алгоритму.

Мы потратили на разбор этого примера 4 занятия, каждое — по две пары (для тех, кто не в курсе: под «парой» в учебных заведениях подразумевают два академических часа). Автор очень хорошо и подробно объясняет каждый свой шаг. Получилось интересно и полезно. Тесты добавляются постепенно, в конце получается 12 тестов.

«Google Mock» влился в «Google Test»

Кое-что сначала было непонятно, но мы сумели разобраться.

Например, в вышеупомянутом примере автор включает (#include) в исходный файл с тестами библиотеку gmock/gmock.h. Насколько я понимаю, раньше (в период с 2008 года по 2019 год) существовал такой отдельный тестовый фреймворк «Google Mock». На GitHub остался архив этого фреймворка: https://github.com/google/googlemock. Там сказано следующее:

This project has been absorbed into the GoogleTest project.

В вышеуказанном примере Джефф Лангр для создания тестов использует макросы TEST (подробнее) и TEST_F (подробнее). Эти макросы есть и в современном фреймворке «Google Test».

Внутри тестов в обсуждаемом примере Джефф Лангр использует макросы-утверждения ASSERT_THAT (подробнее).

Мы же, поскольку начали работать с фреймворком «Google Test», включили в исходный файл с тестами библиотеку gtest/gtest.h, как показано в учебнике для начинающих на официальном сайте этого фреймворка, и успешно создали тесты с помощью макросов TEST и TEST_F. А вот макросы-утверждения ASSERT_THAT использовать не получилось, так как при сборке выдавалась синтаксическая ошибка: компилятор не находил определения этого макроса в библиотеке gtest/gtest.h.

Поначалу мы решили, что макрос ASSERT_THAT вышел из употребления в какой-то момент за прошедшие 13 лет с момента написания разбираемой книги. Довольно быстро мы заменили этот макрос макросами, начинающимися на последовательности ASSERT_ и EXPECT_, которые есть в библиотеке gtest/gtest.h (подробнее читайте в учебнике для начинающих). Например, утверждение из первого теста обсуждаемого примера в книге выглядит так:

    ASSERT_THAT(encoded, testing::Eq("A"));

Мы заменили это утверждение следующим:

    EXPECT_EQ(encoded, "A");

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

Позже выяснилось, что макрос-утверждение ASSERT_THAT вовсе не устарел и не вышел из употребления (таким образом получается, что и описываемую книгу тоже нельзя считать устаревшей, исходя из использования этого макроса; тесты из книги можно использовать точно в том же виде, в каком они описаны в книге). Как я понимаю, библиотека gmock/gmock.h при присоединении к фреймворку «Google Test» вовсе не влилась в библиотеку gtest/gtest.h, как можно подумать. Эти библиотеки продолжают существовать по отдельности, просто теперь они существуют в рамках общего фреймворка «Google Test».

Таким образом, для использования макроса-утверждения ASSERT_THAT достаточно в исходный файл с тестами включить еще и библиотеку gmock/gmock.h. Пример:

// tests/tests-for-lib.cpp

#include <gtest/gtest.h>    // заголовочный файл фреймворка Google Test
#include <gmock/gmock.h>    // для макроса ASSERT_THAT
#include "../modules/lib.h" // заголовочный файл тестируемого модуля

TEST(FactorialTestSuite, NegativeInputs)
{
    ASSERT_THAT(-1, testing::Eq(factorial(-5))); // из Google Mock
    EXPECT_EQ(-1, factorial(-5));                // из Google Test
}

Кроме этого, не забудьте указать подключение библиотеки gmock в скрипте системы сборки CMake. Пример:

# tests/CMakeLists.txt

project(tests)

add_executable(tests tests-for-lib.cpp)
target_link_libraries(tests
    PUBLIC
        lib
        gtest
        gmock
        gtest_main
)

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

В этой книге я оставил непрочитанными еще 9 глав, с третьей по одиннадцатую. Думаю, там еще много интересного можно найти. Но по нашему плану мы уже перешли к изучению автоматического документирования программ на языке C++. Возможно, в качестве инструмента возьмем Doxygen.