VS2010 C++单元测试之gtest与OpenCppCoverage实践
Posted -飞鹤-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了VS2010 C++单元测试之gtest与OpenCppCoverage实践相关的知识,希望对你有一定的参考价值。
1. 前言
想减少开发过程中产生Bug,尤其是迭代开发过程中新引入的Bug。进行单元测试是一个非常的方法,可以用来减少开发过程的Bug,尤其是通过CI自动化,每次有新的修改,都进行回归测试,可以大大增强代码的鲁棒性。
1.1. 单元测试
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。“单元”一般情况定义为函数(包括类中的方法),因为函数是最小的可测试代码。跨模块的测试以及集成测试,各有各的优势,需要相互结合,而不是只用一个就可以解决所有问题。
代码覆盖率,用来衡量代码测试中的测试程序,主要包括语句覆盖、判定覆盖、条件覆盖、条件判定组合覆盖、多条件覆盖和路径覆盖。很多自动检测代码覆盖率的工具,主要还是针对的语句覆盖(即行覆盖)。有时,虽然行覆盖到了,但是一行中有多个判定逻辑的语句,多个判定逻辑不一定覆盖到了。如何提高条件覆盖,判定覆盖,除了自我注意以外,还可以通过CodeReview来协助。
VS2010自带的基于.NET的单元测试框架及代码覆盖率检查都非常好。但是这种测试框架绑定了VS2010,使其只能在Windows下使用。
gtest是Google开发的开源C++单元测试框架,被广泛使用,且其兼具跨平台的特性。另外,gtest+OpenCppCoverage也能很好地完成代码覆盖率的统计和展示。
2. gtest
2.1. 简介
gtest是Google开源的一套跨平台的C++单元测试框架。它提供了丰富的断言及其他判断,另外还有参数化及死亡测试等功能。
2.2. 编译
VS2017及之后的版本,可以直接安装gtest组件,但是VS2010需要自行编译生成相关库。只有V1.8.1版本只支持VS2010,下载地址:https://github.com/google/googletest/releases/tag/release-1.8.1。
解压之后,直接进入msvc\\2010目录,其中有gtest.sln和gtest-md.sln,两个sln代码一样,前者为静态运行时库版本,后者为动态运行时库版本。
编译会生成gtest.lib,gtest_prod_test.exe。前者是gtest的测试框架静态库,后者是测试示例。
2.3. 用法
2.3.1. 初始化
在main函数中添加下面的代码会初始化测试框架,并自动执行所有注册的测试单元。
int main(int argc, char **argv)
printf("Running main() from %s\\n", __FILE__);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
初始化的时候,还可以接受命令行参数。
常用的命令行参数有:
● --gtest_filter,测试案例过滤。
● --gtest_output,测试报告输出。
● --gtest_break_on_failure,异常时中断。
更多命令行参数见附录4.1.。
2.3.2. 断言
单元测试,必然有一个检测的过程,也即测试结果与实际期待值是否一致。如果不一致,触发断言,显示断言代码在哪个文件,哪一行,以方便调试问题。
2.3.2.1. 基本判断
gtest的基本判断断言主要有两类:
- ASSERT_*系列,其测试失败会立即退出当前测试函数。
- EXPECT_*系列,其测试失败会继续顺序执行。
● 例如:
EXPECT_TRUE(false);
● 测试输出:
error: Value of: false
Actual: false
Expected: true
● 附加输出信息:
ASSERT_TRUE(false)<<“For test!”;
测试失败时,不仅会输出上述信息,另外还会附加自定义信息。
断言有判断真假的,有比较两个值的,比较两个字符串,还有比较浮点类型的,详见附录4.2.。
2.3.2.2. 标记成功失败
在一些多逻辑判断分支中,可以直接标记成功与失败。
● SUCCEED(),标记当前的判断是成功的。
● ADD_FAILURE(),标记当前判断失败,输出相关代码位置信息,继续往下执行。
● FAIL(),标记当前判断重大失败,退出当前函数。
2.3.2.3. 判断抛出异常
判断抛出的异常类型是否符合预期。
例如: EXPECT_THROW(Fun(0), int);
会判断Fun(0)函数是否抛出了int类型的异常。
更多见附录4.2.5.。
2.3.2.4. 参数名输出断言
在调用函数的同时,输出其参数值,相当于自定义判断。
ASSERT_PRED2(GreaterThan, a, b);
测试结果:
error: GreaterThanevaluates to false, where
a evaluates to 5
b evaluates to 6
ASSERT_PRED1,ASSERT_PRED3,ASSERT_PRED4,ASSERT_PRED2分别对应不同参数个数的版本。
2.3.2.5. 子过程中使用断言
SCOPED_TRACE来标记接下来的子函数,如果触发断言,会显示子过程前面设置的追踪标记,以方便确认是调用的哪个子函数。
void Sub(int n)
ASSERT_EQ(1, n);
TEST(SubTest, Test1)
SCOPED_TRACE("Step1");
Sub(1);
SCOPED_TRACE("Step2");
Sub(3);
2.3.3. 测试用例的注册
gtest提供了非常多的宏用来注册测试用例。
2.3.3.1. TEST 宏
TEST宏主要针对一般的函数或类的测试。
- 解析TEST宏
# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name)
- 示例
// Tests factorial of 0.
TEST(FactorialTest, Zero)
EXPECT_EQ(1, Factorial(0));
// Tests factorial of negative numbers.
TEST(FactorialTest, Negative)
EXPECT_EQ(1, Factorial(-5));
EXPECT_EQ(1, Factorial(-1));
EXPECT_GT(Factorial(-10), 0);
可以看出FactorialTest是作为测试示例名,Zero是作为测试示例下的一个子具体测试项名。
如果一个测试示例下有多个测试项,可以如上面的代码中,写多个相同测试示例名的TEST。
2.3.3.2. TEST_F宏
#define TEST_F(test_fixture, test_name)\\
GTEST_TEST_(test_fixture, test_name, test_fixture, \\
::testing::internal::GetTypeId<test_fixture>())
通过宏定义可以看出,第一个宏参数必须是一个特定的类型,而不是任意的类型。每个测试实例其实都是test_fixture的实例的接口实现。多个相同类名的测试套件就有多个实例,并且它们是注册在同一个类名之下的。针对类的时候,常有初始化以及清理的操作,测试套件下有三种情况,分别为:每个测试实例单独初始化及清理,同名测试类共享初始化及清理,不同测试套件共享初始化及清理。
2.3.3.2.1. 测试实例单独初始化及清理
gtest要求test_fixture必须是从::testing::Test派生而来的。test_fixture类可以重写SetUp和TealDown两个函数,前者是为初始化测试实例,后者为测试结束前进行相关清理。
- 示例
class TestFixtures : public ::testing::Test
public:
TestFixtures()
printf("\\nTestFixtures\\n");
;
~TestFixtures()
printf("\\n~TestFixtures\\n");
protected:
void SetUp()
printf("\\nSetUp\\n");
data = 0;
;
void TearDown()
printf("\\nTearDown\\n");
protected:
int data;
;
TEST_F(TestFixtures, First)
EXPECT_EQ(data, 0);
data = 1;
EXPECT_EQ(data, 1);
TEST_F(TestFixtures, Second)
EXPECT_EQ(data, 0);
data = 1;
EXPECT_EQ(data, 1);
- 测试结果
下面的测试结果可以看出,每个测试实例都是单独初始化及清理的。
[----------] 2 tests from TestFixtures
[ RUN ] TestFixtures.First
TestFixtures
SetUp
TearDown
~TestFixtures
[ OK ] TestFixtures.First (9877 ms)
[ RUN ] TestFixtures.Second
TestFixtures
SetUp
TearDown
~TestFixtures
[ OK ] TestFixtures.Second (21848 ms)
[----------] 2 tests from TestFixtures (37632 ms total)
2.3.3.2.2. 测试类初始化及清理
gtest要求test_fixture必须是从::testing::Test派生而来的,然后可以重写类静态函数完成类层面的初始化及清理。
- 示例
class TestFixturesS : public ::testing::Test
public:
TestFixturesS()
printf("\\nTestFixturesS\\n");
;
~TestFixturesS()
printf("\\n~TestFixturesS\\n");
protected:
void SetUp()
;
void TearDown()
;
static void SetUpTestCase()
UnitTest& unit_test = *UnitTest::GetInstance();
const TestCase& test_case = *unit_test.current_test_case();
printf("Start Test Case %s \\n", test_case.name());
;
static void TearDownTestCase()
UnitTest& unit_test = *UnitTest::GetInstance();
const TestCase& test_case = *unit_test.current_test_case();
int failed_tests = 0;
int suc_tests = 0;
for (int j = 0; j < test_case.total_test_count(); ++j)
const TestInfo& test_info = *test_case.GetTestInfo(j);
if (test_info.result()->Failed())
failed_tests++;
else
suc_tests++;
printf("End Test Case %s. Suc : %d, Failed: %d\\n", test_case.name(), suc_tests, failed_tests);
;
;
TEST_F(TestFixturesS, SUC)
EXPECT_EQ(1,1);
TEST_F(TestFixturesS, FAI)
EXPECT_EQ(1,2);
- 结果
可以看到同名类的测试只有一个初始化及清理。
[----------] 2 tests from TestFixturesS
Start Test Case TestFixturesS
[ RUN ] TestFixturesS.SUC
TestFixturesS
~TestFixturesS
[ OK ] TestFixturesS.SUC (2 ms)
[ RUN ] TestFixturesS.FAI
TestFixturesS
..\\test\\gtest_unittest.cc(126): error: Expected: 1
To be equal to: 2
~TestFixturesS
[ FAILED ] TestFixturesS.FAI (5 ms)
End Test Case TestFixturesS. Suc : 1, Failed: 1
[----------] 2 tests from TestFixturesS (12 ms total)
2.3.3.2.3. 全局的初始化及清理
有时需要有一个全局统一的一个初始化及退出前的清理操作,此时可以从testing::Environment派生一个类,并重写Setup和TearDown。然后在main函数中调用AddGlobalTestEnvironment添加实例。
- 示例
namespace testing
namespace internal
class EnvironmentTest : public ::testing::Environment
public:
EnvironmentTest()
printf("\\nEnvironmentTest\\n");
;
~EnvironmentTest()
printf("\\n~EnvironmentTest\\n");
public:
void SetUp()
printf("\\n~Start Test\\n");
;
void TearDown()
UnitTest& unit_test = *UnitTest::GetInstance();
for (int i = 0; i < unit_test.total_test_case_count(); ++i)
int failed_tests = 0;
int suc_tests = 0;
const TestCase& test_case = *unit_test.GetTestCase(i);
for (int j = 0; j < test_case.total_test_count(); ++j)
const TestInfo& test_info = *test_case.GetTestInfo(j);
// Counts failed tests that were not meant to fail (those without
// 'Fails' in the name).
if (test_info.result()->Failed())
failed_tests++;
else
suc_tests++;
printf("End Test Case %s. Suc : %d, Failed: %d\\n", test_case.name(), suc_tests, failed_tests);
;
;
GTEST_API_ int main(int argc, char **argv)
printf("Running main() from gtest_main.cc\\n");
::testing::AddGlobalTestEnvironment(new testing::internal::EnvironmentTest);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
2.3.3.3. TEST_P宏
测试代码写好了之后,如果想添加新的验证数据,最好能够不修改原有的测试代码,有一种方法直接添加测试数据。gtest针对这种情况提供了TEST_P宏来将测试参数化,也即将测试参数独立出来。
2.3.3.3.1. 单一参数
- 代码
class IsPrimeParamTest : public testing::TestWithParam<int>
public:
bool IsPrime(int n)
if (n <= 1) return
false;
if (n % 2 == 0)
return n == 2;
for (int i = 3; ; i += 2)
if (i > n / i)
break;
if (n % i == 0)
return false;
return true;
;
TEST_P(IsPrimeParamTest, HandleTrueReturn)
int n = GetParam();
EXPECT_TRUE(IsPrime(n));
INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing::Values(3, 5, 11, 23, 17));
INSTANTIATE_TEST_CASE_P(TrueReturnEx, IsPrimeParamTest, testing::Values(2));
- 测试结果
Running main() from ..\\..\\src\\gtest_main.cc
[==========] Running 6 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 5 tests from TrueReturn/IsPrimeParamTest
[ RUN ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/0
[ OK ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/0 (0 ms)
[ RUN ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/1
[ OK ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/1 (0 ms)
[ RUN ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/2
[ OK ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/2 (1 ms)
[ RUN ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/3
[ OK ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/3 (0 ms)
[ RUN ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/4
[ OK ] TrueReturn/IsPrimeParamTest.HandleTrueReturn/4 (0 ms)
[----------] 5 tests from TrueReturn/IsPrimeParamTest (1 ms total)
[----------] 1 test from TrueReturnEx/IsPrimeParamTest
[ RUN ] TrueReturnEx/IsPrimeParamTest.HandleTrueReturn/0
[ OK ] TrueReturnEx/IsPrimeParamTest.HandleTrueReturn/0 (0 ms)
[----------] 1 test from TrueReturnEx/IsPrimeParamTest (0 ms total)
[----------] Global test environment tear-down
[==========] 6 tests from 2 test cases ran. (3 ms total)
[ PASSED ] 6 tests.
2.3.3.3.2. 多参数
- 代码
class OddEvenTest : public testing::TestWithParam<::testing::tuple<bool, int> >
protected:
bool IsOdd(int n)
return (1 == n%2);
bool IsEven(int n)
return (0 == n%2);
;
TEST_P(OddEvenTest, Case1)
bool bOdd = ::testing::get<0>(GetParam());
int nVal = ::testing::get<1>(GetParam());
if (bOdd)
EXPECT_TRUE(IsOdd(nVal));
else
EXPECT_TRUE(IsEven(nVal));
INSTANTIATE_TEST_CASE_P(TestBisValuesCombine, OddEvenTest, ::testing::Combine(::testing::Bool(), ::testing::Values(0, 1, 2, 3, 4)));
多参数时,会用到可变参数模板,VS2010不支持,VS2013才开始支持。
2.3.3.4. TYPED_TEST宏
针对模板,gtest提供了TYPED_TEST宏来测试其参数的兼容性。
- 代码
#include <list>
template <typename T>
class FooTest: public testing::Test
public:
typedef std::list<T> List;
static T shared_;
T value_;
;
template <typename T> T FooTest<T>::shared_ ;
typedef testing::Types<char, int, unsigned int> MyTypes;
TYPED_TEST_CASE(FooTest, MyTypes);
TYPED_TEST(FooTest, DoesBlah)
// Inside a test, refer to the special name TypeParam to get the type
// parameter. Since we are inside a derived class template, C++ requires
// us to visit the members of FooTest via 'this'.
TypeParam n = this->value_;
// To visit static members of the fixture, add the 'TestFixture::'
// prefix.
// 测试T类型的vlaue_和shared_的运算兼容性
n += TestFixture::shared_;
// To refer to typedefs in the fixture, add the 'typename TestFixture::'
// prefix. The 'typename' is required to satisfy the compiler.
// 测试List的可用性
typename TestFixture::List values;
values.push_back(n);
- 结果
Running main() from ..\\..\\src\\gtest_main.cc
[==========] Running 3 tests from 3 test cases.
[----------] Global test environment set-up.
[----------] 1 以上是关于VS2010 C++单元测试之gtest与OpenCppCoverage实践的主要内容,如果未能解决你的问题,请参考以下文章