← На главную

Тестирование кода на C++ с помощью Google Test

Как и любой уважающий себя язык программирования, C++ имеет фреймворки для написания модульных тестов, и даже не один, а очень много. В рамках этой заметки мы познакомимся с основами использования фреймворка Google Test. Это довольно легковесный, однако не в ущерб удобству и функциональности фреймворк, используемый в Chromium, LLVM, Protobuf, OpenCV, и других проектах. Кроме того, из IDE с ним умеет интегрироваться как минимум CLion.

Fun fact! Если вас интересует написание системных тестов, их намного удобнее писать на высокоуровневом языке вроде Python. В частности, для Python есть хороший тестовый фреймворк PyTest.

Все популярные дистрибутивы Linux имеют пакеты с Google Test. Например, в Ubuntu пакет называется libgtest-dev, а в Arch Linux – gtest.

Если вы используете CMake, то тесты, использующие Google Test, добавляются в проект очень просто:

cmake_minimum_required(VERSION 3.6) project(cpp-json-example-tests) find_package(GTest REQUIRED) find_package(Threads REQUIRED) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED on) include_directories( ../include ${GTEST_INCLUDE_DIRS} ) add_executable( TestSerialization ./TestSerialization.cpp ../src/User.cpp ../src/Date.cpp) target_link_libraries( TestSerialization ${GTEST_LIBRARIES} Threads::Threads) enable_testing() add_test(TestSerialization "./TestSerialization")

В качестве примера напишем тесты на сериализацию и десериализацию объектов Date и User из статьи Работа с JSON на C++ при помощи библиотеки RapidJSON:

#include <Date.h> #include <User.h> #include <gtest/gtest.h> class TestSerialization : public ::testing::Test { public: TestSerialization() { /* init protected members here */ } ~TestSerialization() { /* free protected members here */ } void SetUp() { /* called before every test */ } void TearDown() { /* called after every test */ } protected: /* none yet */ }; TEST_F(TestSerialization, DateJson) { Date d1(1988, 8, 5); rapidjson::Document json = d1.toJSON(); Date d2 = Date::fromJSON(json); ASSERT_EQ(d1, d2); } TEST_F(TestSerialization, UserJson) { User u1(123, "Alex", 79161234567, Date(1988, 8, 5)); rapidjson::Document json = u1.toJSON(); User u2 = User::fromJSON(json); ASSERT_EQ(u1, u2); } int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }

Заметьте, что, хотя здесь эта возможность и не используется, в общем случае Google Test позволяет заводить глобальное состояние, используемое разными тестами. Это может быть целесообразным, как минимум, для ускорения тестов. В остальном же приведенный код крайне прост, поэтому разбирать его более подробно я не вижу смысла.

Пример вывода программы:

[==========] Running 2 tests from 1 test case. [----------] Global test environment set-up. [----------] 2 tests from TestSerialization [ RUN ] TestSerialization.DateJson [ OK ] TestSerialization.DateJson (0 ms) [ RUN ] TestSerialization.UserJson [ OK ] TestSerialization.UserJson (0 ms) [----------] 2 tests from TestSerialization (0 ms total) [----------] Global test environment tear-down [==========] 2 tests from 1 test case ran. (0 ms total) [ PASSED ] 2 tests.

Спрашивается, а зачем нужен этот фреймворк, если я могу написать обычную программу с assert’ами? Во-первых, такая программа завершит свое выполнение после первого упавшего теста, а Google Test всегда выполняет все тесты. Во-вторых, Google Test предлагает кучу готовых макросов для сравнения полученного результата с ожидаемым, например, ASSERT_FLOAT_EQ, ASSERT_DOUBLE_EQ, ASSERT_THROW, ASSERT_NO_THROW, и другие (см раз и два). Думаю, польза от перечисленных макросов понятна из их названий. В-третьих, когда тест падает, Google Test сам выведет вам ожидаемое и реально полученное значение:

[ RUN ] TestSerialization.DateJson ../tests/TestSerialization.cpp:26: Failure Expected: d1 Which is: Date(year = 1988, month = 8, day = 5) To be equal to: d2 Which is: Date(year = 1988, month = 8, day = 6) [ FAILED ] TestSerialization.DateJson (0 ms)

Кроме того, Google Test позволяет запускать заданное подмножество тестов:

./TestSerialization --gtest_list_tests ./TestSerialization --gtest_filter="*.User*"

… а также запускать тесты многократно и в псевдослучайном порядке:

./TestSerialization --gtest_repeat=10 --gtest_shuffle \ --gtest_random_seed=42

Если генерировать входные данные каждого теста псевдослучайным образом, получится что-то очень похожее на property-based тесты.

Наконец, Google Test умеет генерировать отчеты в формате XML, чтобы его было легче интегрировать с системами непрерывной интеграции (простите за тавтологию), интерактивными средами разработки, и так далее:

./TestSerialization --gtest_output="xml:out.xml"

Резюмируя вышесказанное, Google Test делает всю рутину, позволяя вам сосредоточиться непосредственно на написании тестов. То есть, с ним вам придется писать намного меньше кода, чем без него.

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

А какими тестовыми фреймворками для C++ в это время суток пользуетесь вы?