Write cpp single test with googletest

Framework overview

Google Test (also known as googletest) is a C++ unit testing framework developed by Google. Its first version was released in 2004 as Google's internal testing framework. Subsequently, Google Test became widely used in the open source community and became the C++ unit testing framework of choice in many projects and organizations.

Google Test provides a rich set of assertion functions and test macros that enable developers to write clean, readable, and maintainable unit tests. It supports functions such as test fixtures, parameterized tests, and test suites to meet various testing needs.

Google Test continues to be improved and updated over time, adding many new features and improvements. Its codebase is hosted on GitHub and is maintained and updated by the community.

In 2017, the latest version of Google Test is 1.8.1. However, the development of the Google Test framework did not stop there, and subsequent versions were developed and updated by a broad developer community to meet changing testing needs.

The success and popularity of Google Test is due to its ease of use, flexibility and scalability, and its admiration for high-quality code and unit testing. It has become a widely adopted unit testing framework in the C++ community, providing developers with powerful testing tools and practices.

The official website address of googletest: https://google.github.io/googletest/

Open source address of googletest: https://github.com/google/googletest

Sample example of googletest: https://github.com/google/googletest/tree/main/googletest/samples

bazel uses run mode

In bazel we want to use googletest, which is very simple.

There is an article on the official website that directly talks about how to use bazel to introduce googletest: https://google.github.io/googletest/quickstart-bazel.html

Introduce googletest in WORKSPACE

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
  name = "com_google_googletest",
  urls = ["https://github.com/google/googletest/archive/5ab508a01f9eb089207ee87fd547d290da39d015.zip"],
  strip_prefix = "googletest-5ab508a01f9eb089207ee87fd547d290da39d015",
)

Write BUILD's cc_test

Suppose the target file is:

18770b7573dec566e77dab425910ec05.png
image-20230710164307663
# https://docs.bazel.build/versions/master/be/c-cpp.html#cc_library
cc_library(
 name = "sample1",
 srcs = ["sample1.cc"],
 hdrs = ["sample1.h"],
)

cc_test(
  name = "sample1_unittest",
  srcs = ["sample1_unittest.cc"],
  deps = [
   "@com_google_googletest//:gtest_main",
 ":sample1",
 ],
)

run bazel test

bazel test --test_output=all media_cpp_demo/cpp_unit_test:sample1_unittest

This will output similar information:

INFO: From Testing //media_cpp_demo/cpp_unit_test:sample1_unittest:
==================== Test output for //media_cpp_demo/cpp_unit_test:sample1_unittest:
Running main() from gmock_main.cc
[==========] Running 6 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from FactorialTest
[ RUN      ] FactorialTest.Negative
[       OK ] FactorialTest.Negative (0 ms)
[ RUN      ] FactorialTest.Zero
[       OK ] FactorialTest.Zero (0 ms)
[ RUN      ] FactorialTest.Positive
media_cpp_demo/cpp_unit_test/sample1_unittest.cc:25: Failure
Expected equality of these values:
  3
  Factorial(3)
    Which is: 6
[  FAILED  ] FactorialTest.Positive (0 ms)
[----------] 3 tests from FactorialTest (0 ms total)

[----------] 3 tests from IsPrimeTest
[ RUN      ] IsPrimeTest.Negative
[       OK ] IsPrimeTest.Negative (0 ms)
[ RUN      ] IsPrimeTest.Trivial
[       OK ] IsPrimeTest.Trivial (0 ms)
[ RUN      ] IsPrimeTest.Positive
[       OK ] IsPrimeTest.Positive (0 ms)
[----------] 3 tests from IsPrimeTest (0 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 2 test suites ran. (0 ms total)
[  PASSED  ] 5 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FactorialTest.Positive

 1 FAILED TEST
================================================================================

It clearly tells us which cases have run and which cases have succeeded/failed.

Concrete writing

The best reference for writing is the sample example on the git official website of googletest.

Sample example of googletest: https://github.com/google/googletest/tree/main/googletest/samples

assert statement

In Google Test, there are a variety of assertion functions that can be used to write test assertions, including EXPECT_*the and ASSERT_*series. These two families of assertion functions are very similar in usage, but differ slightly in their behavior when an assertion fails.

Examples of commonly used assertion functions are listed below:

  • EXPECT_EQAND ASSERT_EQ: Verifies that two values ​​are equal.

  • EXPECT_NEAND ASSERT_NE: Validates that two values ​​are not equal.

  • EXPECT_TRUEAND ASSERT_TRUE: Verify that the condition is true.

  • EXPECT_FALSEAND ASSERT_FALSE: Verify that the condition is false.

  • EXPECT_LTAnd ASSERT_LT: Verifies that the first value is less than the second value.

  • EXPECT_LEAND ASSERT_LE: Verifies that the first value is less than or equal to the second value.

  • EXPECT_GTAND ASSERT_GT: Validates that the first value is greater than the second value.

  • EXPECT_GEAND ASSERT_GE: Validates that the first value is greater than or equal to the second value.

These assert functions behave slightly differently on assertion failures:

  • EXPECT_*Series: If the assertion fails, an error message will be printed, but the test function continues to execute.

  • ASSERT_*Series: If the assertion fails, an error message will be output and the execution of the current test function will be terminated.

Here is an example that demonstrates how to use these assertion functions:

#include <gtest/gtest.h>

TEST(MyTestSuite, ExampleTest) {
    int x = 5;
    int y = 10;

    // 验证相等关系
    EXPECT_EQ(x, 5);
    ASSERT_NE(x, y);

    // 验证条件
    EXPECT_TRUE(x > 0);
    ASSERT_FALSE(y < 0);

    // 验证大小关系
    EXPECT_LT(x, y);
    ASSERT_GE(y, 10);
}

int main(int argc, char* argv[]) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

In the above example, we defined a test case MyTestSuite.ExampleTest. In test cases, we have used different assertion functions to verify conditions and relations. Depending on the use of the assertion function, you can choose to use the EXPECT_*or ASSERT_*series to meet your testing needs.

When running a test, if an assertion fails, Google Test will output an error message indicating exactly where and under what conditions the assertion failed.

Depending on your testing needs and personal preference, you can choose to use the appropriate assertion function to write test assertions, and choose to use the EXPECT_*or ASSERT_*series according to the test situation.

simple test case

A simple test case would be to use the macroTest

You can refer to: https://github.com/google/googletest/blob/main/googletest/samples/sample1_unittest.cc

TEST(FactorialTest, Positive) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

Here FactorialTest is the test fixture (TestSuite), and Positive is the test case (TestCase).

In the final output, the same TestSuite will be displayed together.

Multiple test cases share data

What if our multiple test cases have some initialization operations? Macros are needed here TEST_F.

You can refer to: https://github.com/google/googletest/blob/main/googletest/samples/sample3_unittest.cc

TEST_FIs a macro in the Google Test framework used to define test cases based on test fixtures (Test Fixture). Test fixtures provide settings and state that are shared between multiple test cases.

Using TEST_Fmacros, you can define multiple test cases in a test fixture class and share member variables and functions in that class. Each test case will execute the setup (SetUp) function of the fixture before running, and execute the cleanup (TearDown) function of the fixture after running.

Here is a TEST_Fsample code for defining a test case using the macro:

#include <gtest/gtest.h>

// 测试夹具类
class MyTestFixture : public ::testing::Test {
protected:
  void SetUp() override {
    // 在每个测试用例之前执行的设置
  }

  void TearDown() override {
    // 在每个测试用例之后执行的清理
  }

  // 夹具类中的成员变量和函数
  int value;
};

// 使用 TEST_F 宏定义测试用例
TEST_F(MyTestFixture, TestCase1) {
  // 使用夹具类中的成员变量和函数进行测试断言
  value = 42;
  EXPECT_EQ(value, 42);
}

TEST_F(MyTestFixture, TestCase2) {
  // ...
}

// ...

int main(int argc, char* argv[]) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

In the above example, we created a MyTestFixturetest fixture class called , which inherits from ::testing::Test. In the fixture class, we can define member variables and functions, and override SetUpand TearDownfunctions to set and clean up the shared state of the test.

Then, we use TEST_Fmacros to define test cases. In each test case, we can use the member variables and functions in the fixture class and write test assertions.

Finally, we initialize the Google Test framework and run all test cases.

By using TEST_Fmacros, we can more conveniently organize and manage test cases based on test fixtures to ensure that they share the same settings and states, and provide more flexible test scenarios.

In fact, it is not difficult to see that the simple test case TEST is actually a special case of TEST_F, and TEST is more like implicitly and automatically filling the TestSuite defined by TEST_F to the background.

Custom main function

Similarly, there is no main function in the test case we defined, but it is also implicitly and automatically filled by the framework. Its prototype is:

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

We can also write such a function in the unit test file.

When may you need to write the main function yourself?

If all your TestSuites have a common data initialization logic, it can only be written in this main function.

How to write good unit tests?

The official website https://google.github.io/googletest/primer.html briefly talks about how to write a good test case. In fact, well-written unit tests have nothing to do with language. The following guidelines apply to unit tests in all languages.

Well-written unit tests are key to ensuring code correctness and reliability. Here are some guidelines on how to write good unit tests:

  1. Clarify test goals: Before writing unit tests, clarify what the goals of the tests are. Know the behavior, inputs, and expected outputs of the code unit under test, as well as possible boundary conditions and exceptions.

  2. Single Responsibility Principle: Make sure that each unit test only focuses on one specific function or behavior. Break down test cases into independent and repeatable units to make it easier to locate and fix problems.

  3. Cover various cases: When writing test cases, make sure to cover different combinations of inputs, boundary conditions, and exceptions. Tests should verify that the code behaves correctly in various situations.

  4. Independence and repeatability: Each test case should be independent and repeatable. There should be no dependencies between test cases to avoid mutual influence between tests.

  5. Names are clear and unambiguous: Give test cases and assertions clear and unambiguous names so that the purpose and expected results of the test can be accurately described. This makes it easier to understand what the test is for, and to locate problems when a test fails.

  6. Use appropriate assertions: Choose appropriate assertion functions to verify the expected behavior of your code. Make sure assertions are clear and provide useful error messages when tests fail, so you can quickly pinpoint problems.

  7. Auxiliary tools and frameworks: Use appropriate unit testing frameworks and auxiliary tools such as Google Test, Catch2, Mockito, etc. to simplify writing and managing tests. These tools can provide rich assertions and helper functions, as well as features such as test running and report generation.

  8. Maintain test maintainability: as code evolves and changes, test cases are updated and maintained in a timely manner. Make sure the tests are kept in sync with the code and still be able to effectively verify the correctness of the code.

  9. Consider edge cases and exception handling: Tests should cover various edge cases and exception handling to verify that the code behaves as expected in those cases. Including boundary values, unusual inputs, null values, etc.

  10. Run the test suite on a regular basis: Make sure to run the entire test suite on a regular basis to catch potential issues early. Integration tests are run into a continuous integration (CI) system to automate test execution and obtain test results in a timely manner.

Well-written unit tests can provide high code coverage and reliability, helping you catch problems, prevent regressions, and provide confidence in code improvements. Following the principles above and testing aggressively are important practices to ensure code quality.

How can test coverage be generated

googletest itself does not provide direct test coverage functionality, but you can combine other tools to generate test coverage reports.

If you are using bazel, then this step is even easier.

Generate code coverage data

This bazel has a bazel coverage code that can generate coverage.

First, modify the BUILD file first, and increase the parameters corresponding to the generated coverage.

# https://docs.bazel.build/versions/master/be/c-cpp.html#cc_library
cc_library(
 name = "sample1",
 srcs = ["sample1.cc"],
 hdrs = ["sample1.h"],
)

cc_test(
  name = "sample1_unittest",
  srcs = ["sample1_unittest.cc"],
  copts = ["-fprofile-arcs", "-ftest-coverage"],
  linkopts = ["-fprofile-arcs"],
  deps = [
   "@com_google_googletest//:gtest_main",
  ":sample1",
 ],
)

Second, run the commandbazel coverage media_cpp_demo/cpp_unit_test:sample1_unittest

The output shows the coverage data path:

INFO: Build completed successfully, 25 total actions
//media_cpp_demo/cpp_unit_test:sample1_unittest                          PASSED in 0.3s
  /root/.cache/bazel/_bazel_root/aa4e8447fb143c448ba118077e918987/execroot/__main__/bazel-out/k8-fastbuild/testlogs/media_cpp_demo/cpp_unit_test/sample1_unittest/coverage.dat

The coverage.dat here is the coverage data we generated.

Generate code coverage html

This step needs to rely on a genhtml tool. Not to mention downloading and installing.

Directly on the command:

genhtml ./bazel-out/k8-fastbuild/testlogs/media_cpp_demo/cpp_unit_test/sample1_unittest/coverage.dat --output-directory ./cov

Generate html from coverage.dat, and put html in the ./cov directory.

![image-20230710172120373](../../../Library/Application Support/typora-user-images/image-20230710172120373.png)

Open it and you will see the following measurement report

73939a470d172e13d439e289b70f637d.png
image-20230710172140211

You can click further to see which rows are covered.

d0b405b2253927ab5559db1129b6d6a4.png
image-20230710172220303

Summarize

There is nothing special about CPP unit tests and unit tests in other languages. The concepts and methods are the same.

Compared with how to write unit tests, how to write unit tests is more difficult. That's another topic.

reference

Master Google C++ unit testing framework GoogleTest in one article

 

おすすめ

転載: blog.csdn.net/qq_42015552/article/details/131733509