Тестирование кода на C++ с помощью Google Test
13 ноября 2017
Как и любой уважающий себя язык программирования, 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, добавляются в проект очень просто:
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 <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 позволяет заводить глобальное состояние, используемое разными тестами. Это может быть целесообразным, как минимум, для ускорения тестов. В остальном же приведенный код крайне прост, поэтому разбирать его более подробно я не вижу смысла.
Пример вывода программы:
[----------] 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 сам выведет вам ожидаемое и реально полученное значение:
../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_filter="*.User*"
… а также запускать тесты многократно и в псевдослучайном порядке:
--gtest_random_seed=42
Если генерировать входные данные каждого теста псевдослучайным образом, получится что-то очень похожее на property-based тесты.
Наконец, Google Test умеет генерировать отчеты в формате XML, чтобы его было легче интегрировать с системами непрерывной интеграции (простите за тавтологию), интерактивными средами разработки, и так далее:
Резюмируя вышесказанное, Google Test делает всю рутину, позволяя вам сосредоточиться непосредственно на написании тестов. То есть, с ним вам придется писать намного меньше кода, чем без него.
Полную версию исходников к этому посту, как обычно, вы найдете на GitHub. Также вас может заинтересовать заметка Определение степени покрытия кода на C/C++ тестами, если вдруг вы ее пропустили.
А какими тестовыми фреймворками для C++ в это время суток пользуетесь вы?
Метки: C/C++, Тестирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.