Unit testing with GoogleTest in C++

GoogleTest is a test framework open sourced by Google. Using this framework, we can easily test our projects;

This article describes the basic use of GoogleTest;

Source code:

Unit testing with GoogleTest in C++

Install and configure GoogleTest

Thanks to vcpkg, we can install and configure the GoogleTest library very easily;

 vcpkg install gtest

Note:

  • The name of GoogleTest is gtest ;
  • You may need to install the x64 version;

After the installation is complete, according to the prompts, add the configuration to our CMake project and add the link library for our executable file:

 add_executable(main_test main_test.cc)find_package(GTest CONFIG REQUIRED)target_link_libraries(main_test PRIVATE GTest::gmock GTest::gtest GTest::gmock_main GTest::gtest_main)

At this point, the configuration is complete;

About how to configure vcpkg to install 64-bit by default:

GoogleTest official documentation:

The first GoogleTest example

Next we create a single test file;

main_test.cc

 #include <iostream>#include "gtest/gtest.h"TEST(HelloTest, PrintHello) { std::string str{"Hello, World!"}; ASSERT_EQ(str, "Hello, World!"); ASSERT_EQ(str.size(), 13);}int main(int argc, char **argv) { printf("Running main() from %s\n", __FILE__); ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}

The code first introduces the header file gtest/gtest.h , which contains the core macros in GoogleTest, etc.;

Then we used the TEST() macro to create a unit test case;

The TEST() macro is used as follows:

 TEST(test_suite_name, test_case_name) { // test body ...}

The first parameter is the name of the entire test group, and the second parameter is the name of a specific use case in the test group;

And finally in the main function:

First output the file location of the unit test, then initialize the test with the parameters specified when starting the test, and finally call RUN_ALL_TESTS(); start the test;

The results after starting the test are as follows:

 /Users/jasonkayzk/self-workspace/cpp-learn/cmake-build-debug/main_testRunning main() from /Users/jasonkayzk/self-workspace/cpp-learn/main_test.cc[==========] Running 1 test from 1 test suite.[----------] Global test environment set-up.[----------] 1 test from HelloTest[ RUN ] HelloTest.PrintHello[ OK ] HelloTest.PrintHello (0 ms)[----------] 1 test from HelloTest (0 ms total)[----------] Global test environment tear-down[==========] 1 test from 1 test suite ran. (0 ms total)[ PASSED ] 1 test.

execution succeed!

assertion

gtest provides a large number of test assertion functions, which are roughly divided into two categories:

  • ASSERT_* : execution fails, exit the current test function and return immediately (note: not exit the current case);
  • EXPECT_* : If the execution fails, it will not exit the current test function and continue to execute downward;

Two examples are given below:

 TEST(ExpectAndAssert, ExpectTest) { auto add = [](const int x, const int y) { return x + y; }; EXPECT_EQ(add(1, 2), 4); EXPECT_EQ(add(1, 2), 3);}TEST(ExpectAndAssert, AssertTest) { auto subtract = [](const int x, const int y) { return x - y; }; ASSERT_EQ(subtract(3, 1), 3); ASSERT_EQ(subtract(3, 1), 2);}

Output after execution:

 [==========] Running 2 tests from 1 test suite.[----------] Global test environment set-up.[----------] 2 tests from ExpectAndAssert[ RUN ] ExpectAndAssert.ExpectTest/Users/kylinkzhang/self-workspace/cpp-learn/main_test.cc:15: FailureExpected equality of these values: add(1, 2) Which is: 3 4[ FAILED ] ExpectAndAssert.ExpectTest (0 ms)[ RUN ] ExpectAndAssert.AssertTest/Users/kylinkzhang/self-workspace/cpp-learn/main_test.cc:22: FailureExpected equality of these values: subtract(3, 1) Which is: 2 3[ FAILED ] ExpectAndAssert.AssertTest (0 ms)[----------] 2 tests from ExpectAndAssert (0 ms total)[----------] Global test environment tear-down[==========] 2 tests from 1 test suite ran. (0 ms total)[ PASSED ] 0 tests.[ FAILED ] 2 tests, listed below:[ FAILED ] ExpectAndAssert.ExpectTest[ FAILED ] ExpectAndAssert.AssertTest 2 FAILED TESTS

Some of the more commonly used assertions are:

1. Boolean value check:

Fatal assertion Nonfatal assertion Verifies
ASSERT_TRUE( condition ); EXPECT_TRUE( condition ); condition is true
ASSERT_FALSE( condition ); EXPECT_FALSE( condition ); condition is false

2. Numerical data check:

Fatal assertion Nonfatal assertion Verifies
ASSERT_EQ( expected , actual ); EXPECT_EQ( expected , actual ); expected == actual
ASSERT_NE( val1 , val2 ); EXPECT_NE( val1 , val2 ); val1 != val2
ASSERT_LT( val1 , val2 ); EXPECT_LT( val1 , val2 ); val1 < val2
ASSERT_LE( val1 , val2 ); EXPECT_LE( val1 , val2 ); val1 <= val2
ASSERT_GT( val1 , val2 ); EXPECT_GT( val1 , val2 ); val1 > val2
ASSERT_GE( val1 , val2 ); EXPECT_GE( val1 , val2 ); val1 >= val2

3. String comparison:

Fatal assertion Nonfatal assertion Verifies
ASSERT_STREQ( expected_str , actual_str ); EXPECT_STREQ( expected_str , actual_str ); Two C strings have the same content
ASSERT_STRNE( str1 , str2 ); EXPECT_STRNE( str1 , str2 ); Two C strings have different contents
ASSERT_STRCASEEQ( expected_str , actual_str ); EXPECT_STRCASEEQ( expected_str , actual_str ); Two C strings have the same content, ignoring case
ASSERT_STRCASENE( str1 , str2 ); EXPECT_STRCASENE( str1 , str2 ); Two C strings have different contents, ignoring case

4. Exception check:

Fatal assertion Nonfatal assertion Verifies
ASSERT_THROW( statement , exception_type ); EXPECT_THROW( statement , exception_type ); statement throws an exception of the given type
ASSERT_ANY_THROW( statement ); EXPECT_ANY_THROW( statement ); statement throws an exception of any type
ASSERT_NO_THROW( statement ); EXPECT_NO_THROW( statement ); statement doesn’t throw any exception

5. Floating point check:

Fatal assertion Nonfatal assertion Verifies
ASSERT_FLOAT_EQ( expected, actual ); EXPECT_FLOAT_EQ( expected, actual ); the two float values ​​are almost equal
ASSERT_DOUBLE_EQ( expected, actual ); EXPECT_DOUBLE_EQ( expected, actual ); the two double values ​​are almost equal

Compare two numbers that are close:

Fatal assertion Nonfatal assertion Verifies
ASSERT_NEAR( val1, val2, abs_error ); EXPECT_NEAR (val1, val2, abs_error ); the difference between val1 and val2 doesn’t exceed the given absolute error

6. Type comparison assertion:

This class asserts only one ::testing::StaticAssertTypeEq<T, T>() :

  • It doesn’t do anything when the types are the same;
  • If it is different, it will cause a compilation error;

It should be noted that: make the code trigger the compiler to deduce the type, otherwise a compilation error will also occur;

Such as:

 template <typename T> class Foo { public: void Bar() { ::testing::StaticAssertTypeEq<int, T>(); }};

The following code will not cause compilation conflicts:

 void Test1() { Foo<bool> foo; }

But the following code triggers a compilation error because it triggers type deduction by the compiler:

 void Test2() { Foo<bool> foo; foo.Bar(); }

7. Several special assertions:

  • SUCCEED() macro: Directly mark the assertion success;
  • FAIL() macro: mark a fatal error (same as ASSERT_* );
  • ADD_FAILURE() macro: mark non-fatal errors (same as EXPECT_* );

See the official documentation for more assertions:

custom error message

Sometimes, we may not be satisfied with the wrong output by default:

E.g:

 FailureExpected equality of these values: 1+2 Which is: 3 4

At this point, we can also use the << operator to customize the output information;

 TEST(TestMessage, Message) { int result = 4; EXPECT_EQ(1 + 2, result) << "1+2 should equals to: " << result;}

At this point the output:

 FailureExpected equality of these values: 1 + 2 Which is: 3 result Which is: 41+2 should equals to: 4

From the output, we can clearly see that the result is 4 at this time!

Event mechanism and TEST_F

Friends who have used JUnit should be familiar with @Before and @After annotations;

They allow us to perform some operations before starting the use case and at the end of the use case;

gtest also provides such events, and they are divided into various types:

  • global events;
  • TestSuite event;
  • TestCase event;

Let’s take a look at them one by one;

global event

To implement global events, you must write a class to inherit the testing::Environment class and implement the SetUp and TearDown methods inside;

After this:

  • SetUp()方法: executed before all cases are executed;
  • TearDown()方法: Executed after all cases are executed;

At the same time, you also need to tell gtest to add this global event:

The event needs to be hooked in through the testing::AddGlobalTestEnvironment method in the main function;

This also means that we can write many such classes, and then hang their events;

TestSuite events

We need to write a class that extends testing::Test and implements two static methods:

  • SetUpTestCase()方法: executed before the first TestCase;
  • TearDownTestCase()方法: executed after the last TestCase;

When writing test cases, we need to use the TEST_F macro, the first parameter must be the name of our class above, representing a TestSuite;

TestCase events

The TestCase event is hung before and after each case is executed. The implementation is almost the same as the above, but the SetUp method and the TearDown method need to be implemented:

  • SetUp()方法: executed before each TestCase;
  • TearDown()方法: executed after each TestCase;

The event mechanism can help us simplify testing, for example:

We can use the event mechanism to share data between test functions;

An example of using various events is provided below:

 class GlobalEvent : public testing::Environment {public: void SetUp() override { std::cout << "Before any case, Global" << std::endl; } void TearDown() override { std::cout << "After all cases done, Global" << std::endl; }};class VectorTest : public ::testing::Test {protected: // set resources before test void SetUp() override { vec.push_back(1); vec.push_back(2); vec.push_back(3); } // clean up resources after test void TearDown() override { vec.clear(); } static void SetUpTestCase() { std::cout << "SetUpTestCase()" << std::endl; } static void TearDownTestCase() { std::cout << "TearDownTestCase()" << std::endl; } std::vector<int> vec;};// Here we are using TEST_F, not TESTTEST_F(VectorTest, PushBack) { // We changed vec here, but this is invisible to other test cases vec.push_back(4); EXPECT_EQ(vec.size(), 4); EXPECT_EQ(vec.back(), 4);}TEST_F(VectorTest, Size) { ASSERT_EQ(vec.size(), 3);}int main(int argc, char **argv) { printf("Running main() from %s\n", __FILE__); ::testing::AddGlobalTestEnvironment(new GlobalEvent); // add env ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}

First, we define the GlobalEvent class, which inherits testing::Environment to define the operations before and after the start of the entire test;

Then, we define the VectorTest class, which extends testing::Test to define the operations before and after the start of this test group and individual test collections;

Next, we use TEST_F define two sets of test cases;

Finally, we register the environment variables we defined earlier in the main function: ::testing::AddGlobalTestEnvironment(new GlobalEvent); ;

Executing the use case, we get the following output:

 Running main() from /Users/jasonkayzk/self-workspace/cpp-learn/main_test.cc[==========] Running 2 tests from 1 test suite.[----------] Global test environment set-up.Before any case, Global[----------] 2 tests from VectorTestSetUpTestCase()[ RUN ] VectorTest.PushBack[ OK ] VectorTest.PushBack (0 ms)[ RUN ] VectorTest.Size[ OK ] VectorTest.Size (0 ms)TearDownTestCase()[----------] 2 tests from VectorTest (0 ms total)[----------] Global test environment tear-downAfter all cases done, Global[==========] 2 tests from 1 test suite ran. (0 ms total)[ PASSED ] 2 tests.

Note: In the two tests above:

 // Here we are using TEST_F, not TESTTEST_F(VectorTest, PushBack) { // We changed vec here, but this is invisible to other test cases vec.push_back(4); EXPECT_EQ(vec.size(), 4); EXPECT_EQ(vec.back(), 4);}TEST_F(VectorTest, Size) { ASSERT_EQ(vec.size(), 3);}

Modifying data in one test function will not affect other test functions;

This is because, each individual test case calls our overloaded SetUp and TearDown functions individually!

Appendix

Reference article:

Source code:

This article is reprinted from: https://jasonkayzk.github.io/2022/05/09/C++%E4%B8%AD%E4%BD%BF%E7%94%A8GoogleTest%E8%BF%9B%E8%A1%8C %E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95/
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment