cpp-gtest/

Как и любой уважающий себя язык программирования, 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++ в это время суток пользуетесь вы?

EnglishRussianUkrainian