Google C++单元测试框架(Gtest)系列教程之四——参数化

2024-03-16 01:08

本文主要是介绍Google C++单元测试框架(Gtest)系列教程之四——参数化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文转自http://www.cnblogs.com/bangerlee/archive/2011/10/08/2199701.html

引言

在上一篇文章中,我们学习了如何使用Gtest的测试固件(Test fixture)完成测试代码和测试数据的复用,这一节我们来学习如何使用Gtest值参数化的方法,简化函数测试;使用类型参数化的方法,简化对模板类的测试。

值参数化

假设我们要对以下函数进行测试:

// 判断n是否为质数
bool IsPrime(int n) 

假设我们要编写判定结果为false的测试案例,根据之前学习的断言和TEST()的使用方法,我们编写测试代码如下:

复制代码
// Tests negative input.
TEST(IsPrimeTest, Negative) {EXPECT_FALSE(IsPrime(-1));EXPECT_FALSE(IsPrime(-2));EXPECT_FALSE(IsPrime(-5));EXPECT_FALSE(IsPrime(-100));EXPECT_FALSE(IsPrime(INT_MIN));
}
复制代码

显然我们对“EXPECT_FALSE(IsPrime(X))”这样的语句复制粘贴了5次,但当被测数据有几十个上百个的时候,再使用复制粘帖的方式就弱爆了。下面我们来看Gtest中为解决这个问题,给我们提供的方法。
首先,我们添加一个继承自::testing::TestWithParam<T>的类,其中T就是我们被测数据的类型,针对以上函数IsPrimeTest,添加以下类:

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

在该类中,我们可以编写SetUp()和TearDown()函数,分别完成数据初始化和数据清理,还可以添加类成员、其他类成员函数,相关的用法,可以参看Gtest Project的这个例子,这里我们仅对函数作测试,SetUp()等方法都不需要用到,IsPrimeParamTest为一个空的类。

接着我们需要使用宏TEST_P来编写相应的测试代码:

TEST_P(IsPrimeParamTest, Negative)
{int n =  GetParam();EXPECT_FALSE(IsPrime(n));
}

GetParam()方法用于获取当前参数的具体值,这段测试代码相比上面的是不是精简多了?!

最后,我们使用INSTANTIATE_TEST_CASE_P()告知Gtest我们的被测参数都有哪些:

INSTANTIATE_TEST_CASE_P(NegativeTest, IsPrimeParamTest, testing::Values(-1,-2,-5,-100,INT_MIN));

以上第一个参数为测试实例的前缀,可以随意取;第二个参数为测试类的名称;第三个参数指示被测参数,test::Values表示使用括号内的参数。运行该测试用例,得到结果如下:

复制代码
Running main() from gtest_main.cc
[==========] Running 5 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 5 tests from NegativeTest/IsPrimeParamTest
[ RUN      ] NegativeTest/IsPrimeParamTest.Negative/0
[       OK ] NegativeTest/IsPrimeParamTest.Negative/0 (0 ms)
[ RUN      ] NegativeTest/IsPrimeParamTest.Negative/1
[       OK ] NegativeTest/IsPrimeParamTest.Negative/1 (0 ms)
[ RUN      ] NegativeTest/IsPrimeParamTest.Negative/2
[       OK ] NegativeTest/IsPrimeParamTest.Negative/2 (0 ms)
[ RUN      ] NegativeTest/IsPrimeParamTest.Negative/3
[       OK ] NegativeTest/IsPrimeParamTest.Negative/3 (0 ms)
[ RUN      ] NegativeTest/IsPrimeParamTest.Negative/4
[       OK ] NegativeTest/IsPrimeParamTest.Negative/4 (0 ms)
[----------] 5 tests from NegativeTest/IsPrimeParamTest (1 ms total)[----------] Global test environment tear-down
[==========] 5 tests from 1 test case ran. (1 ms total)
[  PASSED  ] 5 tests.
复制代码

从结果上可以看出每个测试实例的全称为:前缀/测试用例名称.测试实例名称。

类型参数化

像能以参数的形式列出被测值一样,我们也可以以参数的形式列出被测类型,这极大地方便了对模板类的测试。针对编写测试代码前已知被测类型和未知被测类型两种情况,Gtest还为我们提供了两种不同的方法,下面假设我们分别使用两种方法对以下类进行测试:

template <typename E> // E is the element type.
class Queue {public:Queue();void Enqueue(const E& element);E* Dequeue(); // Returns NULL if the queue is empty.
  size_t size() const;...
};


方法一:已知被测类型

对于以上模板类Queue,假设我们在编写测试代码之前已知需要对其作int和char类型的测试,首先我们需要编写生成具体类型的Queue工厂方法:

复制代码
template <class T>
Queue<T>* CreateQueue();
template <>
Queue<int>* CreateQueue<int>()
{return new Queue<int>;
}
template <>
Queue<char>* CreateQueue<char>()
{return new Queue<char>;
}
复制代码

然后我们需要编写测试固件类模板(test fixture class template):

复制代码
template <class T>
class QueueTest:public testing::Test
{protected:QueueTest():queue(CreateQueue<T>()){}virtual ~QueueTest(){delete queue;}Queue<T>* const queue;
};
复制代码

可以看到QueueTest的构造函数中使用了工厂方法对类成员变量queue进行初始化。然后我们需要声明和注册我们要测试的类型:

using testing::Types;
// The list of types we want to test.
typedef Types<int, char> Implementations;TYPED_TEST_CASE(QueueTest, Implementations);

这里又用到了一个新的宏:TYPED_TEST_CASE(TestCaseName, TypeList),第一个参数为测试用例名称,第二个参数为类型列表。最后我们使用TYPED_TEST宏编写检测代码:

// 检测对象生成后,queue的大小是否为0
TYPED_TEST(QueueTest, DefaultConstructor) {EXPECT_EQ(0u, this->queue->Size());
}

编译、运行该测试程序,得到测试结果如下:

复制代码
Running main() from gtest_main.cc
[==========] Running 2 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 1 test from QueueTest/0, where TypeParam = int
[ RUN      ] QueueTest/0.DefaultConstructor
[       OK ] QueueTest/0.DefaultConstructor (0 ms)
[----------] 1 test from QueueTest/0 (1 ms total)[----------] 1 test from QueueTest/1, where TypeParam = char
[ RUN      ] QueueTest/1.DefaultConstructor
[       OK ] QueueTest/1.DefaultConstructor (0 ms)
[----------] 1 test from QueueTest/1 (0 ms total)[----------] Global test environment tear-down
[==========] 2 tests from 2 test cases ran. (1 ms total)
[  PASSED  ] 2 tests.
复制代码


方法二:未知类型

在编写测试案例的时候,我们可能并不知道该Queue类会被哪些类型实例化、被测类型是什么,如此是否就无法编写测试案例?!安心する,Gtest为我们提供了方法,可让我们先写检测案例,具体要测的类型可以后期补上,这种方法如下:

与方法一相同,我们需要使用生成Queue的工厂方法,并定义固件类模板,这里图方便,继承了以上QueueTest模板类。

template <class T>
class QueueTest2:public QueueTest<T>{
};

接下来,使用宏TYPED_TEST_CASE_P声明测试用例,其参数为固件模板类的名称。

TYPED_TEST_CASE_P(QueueTest2);

然后我们就可以使用宏TYPED_TEST_P编写测试案例了:

// 检测对象生成后,queue的大小是否为0
TYPED_TEST_P(QueueTest2, DefaultConstructor) {EXPECT_EQ(0u, this->queue->Size());
}

相比方法一,该方法还多了一步:注册测试实例。这里需要使用到REGISTER_TYPED_TEST_CASE_P宏:

REGISTER_TYPED_TEST_CASE_P(QueueTest2, DefaultConstructor);

通过以上基本,我们大部分的未知类型测试代码都编写完成了,但我们并没有一个真正意义上的测试实例,因为我们还没有指定测试类型。通常我们将以上测试代码写进一个.h头文件中,任何想要使用具体类型实例化的代码都可以#include该头文件。
假设我们要测试int和char类型,我们可以在一个.cc文件中编写以下代码:

typedef Types<int, char> Implementations;INSTANTIATE_TYPED_TEST_CASE_P(QueueInt_Char, QueueTest2, Implementations);

同样,我们需要使用Types列出被测类型,注意到这里列出被测类型是在使用TYPED_TEST_P编写测试实例之后。


小结

这一节我们学习了如何使用Gtest值参数化的方法简化函数测试,如何使用已知类型参数化、未知类型参数化的方法简化对模板类的测试。Gtest project中给了我们一个值和类型均为自定义类的例子,感兴趣的话可以猛击这里。


这篇关于Google C++单元测试框架(Gtest)系列教程之四——参数化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/813842

相关文章

Java内存分配与JVM参数详解(推荐)

《Java内存分配与JVM参数详解(推荐)》本文详解JVM内存结构与参数调整,涵盖堆分代、元空间、GC选择及优化策略,帮助开发者提升性能、避免内存泄漏,本文给大家介绍Java内存分配与JVM参数详解,... 目录引言JVM内存结构JVM参数概述堆内存分配年轻代与老年代调整堆内存大小调整年轻代与老年代比例元空

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

Spring 框架之Springfox使用详解

《Spring框架之Springfox使用详解》Springfox是Spring框架的API文档工具,集成Swagger规范,自动生成文档并支持多语言/版本,模块化设计便于扩展,但存在版本兼容性、性... 目录核心功能工作原理模块化设计使用示例注意事项优缺点优点缺点总结适用场景建议总结Springfox 是

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实