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