C++测试框架gtest;如何生成覆盖率:lcov使用

        不会用gtest写测试用例?

        不知道如何看自己的代码覆盖率?

        一文全搞定。

1.Gtest安装

        github下载gtest地址:

        git clone https://github.com/google/googletest.git

2.Gtest编译

        Cmake方式编译:

mkdir ~/gtest/gtest-build-x86
cd gtest-build-x86
cmake ../googletest-main -DCMAKE_INSTALL_PREFIX=./_install
make && make install

3.Example

        一个简单的测试用例:

#include <iostream>
#include <gtest/gtest.h>
 
int sum(int a, int b) {
    return a+b;
}
 
TEST(sum, testSum) {
    EXPECT_EQ(5, sum(2, 3));	// 求合2+3=5
    EXPECT_NE(3, sum(3, 4));	// 求合3+4 != 3
}

        记得在cmake链接一下

add_executable(main main.cpp)
target_link_libraries(main gtest gtest_main)

        多个测试用例记得添加:

add_test(NAME main COMMAND main --gtest_output=xml)

        这样就能使用ctest一次性启动所有用例

        一些小技巧也在这里添加,比如设置用例的时间限制:

add_test(
    NAME main
    COMMAND main --gtest_output=xml
    TIMEOUT 10  # 设置时间限制为 10 秒
)

        add_test常用参数

  1. NAME <testName>:指定测试的名称。
  2. COMMAND <command>:指定要运行的测试命令。
  3. CONFIGURATIONS <config>:指定要测试的配置。
  4. WORKING_DIRECTORY <dir>:指定测试的工作目录。
  5. ARGUMENTS <args>:指定传递给测试的参数。
  6. FAIL_REGULAR_EXPRESSION <regex>:指定一个正则表达式,如果测试的输出匹配该正则表达式,则认为测试失败。
  7. PASS_REGULAR_EXPRESSION <regex>:指定一个正则表达式,如果测试的输出匹配该正则表达式,则认为测试通过。
  8. EXCLUDE_FROM_ALL:指定该测试不会被包含在默认构建目标中。
  9. LABELS <label1> ...:指定测试的标签,用于组织和过滤测试。
  10. TIMEOUT <seconds>:指定测试的超时时间。
  11. RESOURCE_LOCK <resource>:指定测试所需的资源锁。

        关于main函数问题:

        在链接库的时候如果链接了gtest_main则无需写main函数,框架会自动帮你调用:

{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

        否则则需主动写出main函数,如果有必须在main函数里init的函数,检查一下链接gtest_main后是否会发生冲突。

4.gtest检查函数

        前缀有两种:

        ASSERT_ : 断言, 不通过检查则中断测试, 当在测试外使用时要求函数返回void.
        EXPECT_ : 期望, 不通过检查并不中断测试.

        test中对数组检查的测试后缀:

后缀参数条件
TRUEcc == true
FALSEcc == false
EQ(a, b)a == b
NE(a, b)a != b
LT(a, b)a < b
LE(a, b)a <= b
GT(a, b)a > b
GE(a, b)a >= b
FLOAT_EQ(a, b)float型 a ≈ b
DOUBLE_EQ(a, b)double型 a ≈ b
NEAR(a, b, e)abs(a - b) <= e
HRESULT_SUCCEEDED(h)SUCCEEDED(h) == true
HRESULT_FAILED(h)FAILED(h) == true

        对字符串的检查后缀:

后缀参数条件
STREQ(a, b)C字符串相等
STRNE(a, b)C字符串不相等
STRCASEEQ(a, b)C字符串忽略大小写相等
STRCASENE(a, b)C字符串忽略大小写不相等

        可以通过<<操作符将一些重要信息输出:

for (int i = 0; i < x.size(); ++i)
{
    EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i;
}

        输出结果中会在测试不通过时,定位打印在数值错误的后面,方便观看:

gtestdemo.cpp(25): error: Value of: y[i]
  Actual: 4
Expected: x[i]
Which is: 3
Vectors x and y differ at index 2

5.事件机制

5.1 全局事件

        要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法。

        1. SetUp()方法在所有案例执行前执行

        2. TearDown()方法在所有案例执行后执行

        3. 类中变量所有用例都可访问

class FooEnvironment : public testing::Environment
{
public:
    virtual void SetUp()
    {
        std::cout << "Foo FooEnvironment SetUP" << std::endl;
    }
    virtual void TearDown()
    {
        std::cout << "Foo FooEnvironment TearDown" << std::endl;
    }
};

        需要在main函数中通过testing::AddGlobalTestEnvironment方法将事件注册进来,也就是说,可以写很多个这样的类,然后将他们的事件都注册执行。

int _tmain(int argc, _TCHAR* argv[])
{
    testing::AddGlobalTestEnvironment(new FooEnvironment);
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
5.2 TestSuite事件

        写一个类,继承testing::Test,然后实现静态方法:

        1. SetUpTestCase() 方法在第一个TestCase之前执行

        2. TearDownTestCase() 方法在最后一个TestCase之后执行

        3. 类中变量所有用例都可访问

class FooTest : public testing::Test {
 protected:
  static void SetUpTestCase() {
    shared_resource_ = new ;
  }
  static void TearDownTestCase() {
    delete shared_resource_;
    shared_resource_ = NULL;
  }
  // 所有使用TestSuit的Test Case都可以直接访问这些变量和方法
  static T* shared_resource_;
};

         **编写测试案例时,我们需要使用TEST_F这个宏,第一个参数必须是我们上面类的名字,代表一个TestSuite,否则上述方法和变量均无效,用例只会被视为一个普通的case。

5.3 TestCase事件

        TestCase事件是挂在每个案例执行前后的,实现方式和上面的几乎一样,不过需要实现的是SetUp方法和TearDown方法:

        1. SetUp():在每个使用了TestSuite类的Test Case运行开始前,都会调用 SetUp,这里可以初始化

        2. TearDown():在每个使用了TestSuite类的Test Case运行结束后,都会调用 TearDown

        3. 类中变量所有用例都可访问

class FooCalcTest:public testing::Test
{
protected:
    virtual void SetUp()
    {
        m_foo.Init();
    }
    virtual void TearDown()
    {
        m_foo.Finalize();
    }

    FooCalc m_foo;
};

TEST_F(FooCalcTest, HandleNoneZeroInput)
{
    EXPECT_EQ(4, m_foo.Calc(12, 16));
}

TEST_F(FooCalcTest, HandleNoneZeroInput_Error)
{
    EXPECT_EQ(5, m_foo.Calc(12, 16));
}

        **编写测试案例时,同样需要使用TEST_F这个宏,否则无效。

6.参数化

        添加一个类,继承testing::TestWithParam<T>,其中T就是你需要参数化的参数类型:

class IsPrimeParamTest : public::testing::TestWithParam<int>
{

};

         拿到参数的值后,具体做些什么样的测试

TEST_P(IsPrimeParamTest, HandleTrueReturn)
{
    int n =  GetParam();
    EXPECT_TRUE(IsPrime(n));
}

         告诉gtest测试的参数范围是什么;

        使用INSTANTIATE_TEST_CASE_P这宏来告诉gtest你要测试的参数范围:

INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing::Values(3, 5, 11, 23, 17));

7.运行gtest用例

        在build下执行ctest即可

# execute test
echo "execute test"
cd $CUR_DIR/build
ctest
echo "test success"

        只是不知道为什么这样启动信息就不会打印在控制台上了......

        要看调试信息的话,在build/Testing/Temporary下会生成三个文件,LastTest.log里就是控制台所有的打印信息,LastTestsFailed.log记录着最后一次运行中失败的测试用例。

        

8.lcov工具

        常用的cmake用法:

add_compile_options(-fprofile-arcs -ftest-coverage)
set(ALTERNATE_LIBS "gcov")
target_link_libraries(
        main PUBLIC
        ${ALTERNATE_LIBS}
)

        统计覆盖率信息:

# get coverage info
echo "generate coverage"
rm -rf $CUR_DIR/cov
mkdir -p $CUR_DIR/cov
cd $CUR_DIR/cov

lcov \
  -d ../build \
  --exclude '/usr/include/*' \
  --rc branch_coverage=1 \
  --rc lcov_excl_br_line='LOG*' \
  --rc lcov_excl_line='LOG*' \
  -c \
  --ignore-errors mismatch \
  --ignore-errors unused \
  -o test_coverage.info
  • -d ../build: 指定要分析的目录为 ../build,即构建目录。
  • --exclude '/usr/include/*': 排除路径中包含 /usr/include/ 的文件。
  • --rc branch_coverage=1: 设置分支覆盖率为开启状态。
  • --rc lcov_excl_br_line='LOG*': 排除以 LOG* 开头的分支覆盖率行。
  • --rc lcov_excl_line='LOG*': 排除以 LOG* 开头的覆盖率行。
  • -c: 执行代码覆盖率分析。
  • --ignore-errors mismatch: 忽略不匹配的错误。
  • --ignore-errors unused: 忽略未使用的错误。
  • -o test_coverage.info: 将生成的覆盖率信息保存到 test_coverage.info 文件中。

       --ignore-errors mismatch和--ignore-errors unused看情况添加。

        生成的test_coverage.info还需要处理一下成可视化,使用 genhtml 工具将之前生成的测试覆盖率报告 test_coverage.info 转换为 HTML 格式的可视化报告

genhtml test_coverage.info -o output/ --rc branch_coverage=1

        --rc branch_coverage=1: 设置分支覆盖率为开启状态,确保生成的报告中包含分支覆盖率信息。

  • 33
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
GTest是Google Test的简称,是一个功能强大的C++单元测试框架。它提供了丰富的断言和测试工具,可以方便地编写、运行和管理测试用例。 首先,我们需要下载并安装GTest框架GTest可以从官方网站下载并编译安装,也可以使用包管理工具进行安装。安装完成后,我们就可以在自己的项目使用GTest进行单元测试了。 在编写测试用例时,我们需要在一个类中定义多个测试函数。每个测试函数都应该以"TEST"宏开始,并且应该在测试函数中使用多个断言来验证被测试代码的行为。例如,我们可以使用"EXPECT_EQ"断言来验证两个值是否相等。当测试函数执行完毕时,我们可以使用"ASSERT_"宏来检查测试是否通过。 GTest还提供了一些高级功能,例如测试夹具(Test Fixture)和参数化测试(Parameterized Test)等。测试夹具可以帮助我们在测试函数之前和之后执行一些共享的设置和清理操作。参数化测试可以使得我们在一组测试数据上运行相同的测试代码,以验证被测试代码在不同输入条件下的行为。 在运行测试时,我们可以使用GTest提供的命令行工具来执行测试用例。它会输出每个测试函数的执行结果以及总体的测试统计信息。我们也可以在IDE中集成GTest,并通过点击运行按钮来执行测试。 总之,GTest是一个非常强大和方便的单元测试框架,可以帮助我们编写高质量的测试用例并验证被测试代码的正确性。通过充分利用GTest提供的功能,我们可以玩转Google单元测试框架,提升软件开发的质量和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值