博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
GTest基础学习-06-第6个单元测试-接口测试(类型参数驱动)
阅读量:4301 次
发布时间:2019-05-27

本文共 7432 字,大约阅读时间需要 24 分钟。

       前面的文章学习了使用gtest进行函数级的测试和类级的测试,这篇来学习如何通过gtest来测试接口。这个接口我们在prime_table.h中,接口的头和实现部分都放置同一个文件,这个文件也在sample文件夹下。这篇学习第6个单元测试,介绍如何测试一个接口有多种实现,而且没种实现都要单元测试,做到测试代码不重复。本篇新引入了几个宏,有一些模板函数的思想,加上新的测试宏,看起来有费劲,不易理解和掌握。这个知识点比前面学习都要高级的特性和复杂。

 

1.代码准备

prime_table.h代码

#ifndef GTEST_SAMPLES_PRIME_TABLES_H_#define GTEST_SAMPLES_PRIME_TABLES_H_#include 
// prime table 接口class PrimeTable {public: virtual ~PrimeTable() {} // 只有n是素数返回true virtual bool IsPrime(int n) const = 0; // 返回比P大的最小的素数 // 如果下一个素数超出表的容量,就返回-1 virtual int GetNextPrime(int p) const = 0;};// 实现 #1 实时计算素数class OnTheFlyPrimeTable : public PrimeTable {public: bool IsPrime(int n) const override { if (n <= 1) return false; for (int i = 2; i*i <= n; i++) { if ((n % i) == 0) return false; } return true; } int GetNextPrime(int p) const override { for (int n = p + 1; n > 0; n++) { if (IsPrime(n)) return n; } return -1; }};// 实现 #2 预计算素数并存储结果到一个数组class PreCalculatedPrimeTable : public PrimeTable {public: // max用来指定素数表最大数 explicit PreCalculatedPrimeTable(int max) : is_prime_size_(max + 1), is_prime_(new bool[max + 1]) { CalculatePrimesUpTo(max); } ~PreCalculatedPrimeTable() override { delete[] is_prime_; } bool IsPrime(int n) const override { return 0 <= n && n < is_prime_size_ && is_prime_[n]; } int GetNextPrime(int p) const override { for (int n = p + 1; n < is_prime_size_; n++) { if (is_prime_[n]) return n; } return -1; }private: void CalculatePrimesUpTo(int max) { ::std::fill(is_prime_, is_prime_ + is_prime_size_, true); is_prime_[0] = is_prime_[1] = false; // 检查每一个候选的素数 (我们知道2是素数中唯一的偶数 for (int i = 2; i*i <= max; i += i % 2 + 1) { if (!is_prime_[i]) continue; // 标记i不是素数 // 我们从第i个乘数开始,因为所有较小的复数均已标记。 for (int j = i*i; j <= max; j += i) { is_prime_[j] = false; } } } const int is_prime_size_; bool* const is_prime_; // 关闭编译器警告 "assignment operator could not be generated." void operator=(const PreCalculatedPrimeTable& rhs);};#endif // GTEST_SAMPLES_PRIME_TABLES_H_

一个类下有两个接口,isPrime()判断是否是素数,GetNextPrime(int p)求比p要打且是素数中最新的数。这两个接口,给出两个实现方式。

TestSample06.cpp

//这示例展示了如何测试多个相同接口的实现,也叫接口测试#include "prime_tables.h"#include "gtest/gtest.h"namespace {	//首先,我们定义一些模板函数来创建实例实现。 	//如果可以用相同的方式构造实现,你可跳过此步骤	template 
PrimeTable* CreatePrimeTable(); template <> // 这个时候,T就是实现OnTheFlyPrimeTable PrimeTable* CreatePrimeTable
() { return new OnTheFlyPrimeTable; } template <> // 这个时候,T就是实现PreCalculatedPrimeTable PrimeTable* CreatePrimeTable
() { return new PreCalculatedPrimeTable(10000); } // 定义一个测试夹具类模板 template
class PrimeTableTest : public testing::Test { protected: // 构造函数去调用模板函数去创建素数表 PrimeTableTest() : table_(CreatePrimeTable
()) {} ~PrimeTableTest() override { delete table_; } // 注意到我们测试一个实现是通过基本接口,而不是通过具体的实现类。 // 这很重要使测试保持接近真实的场景,当通过基本接口调用实现 PrimeTable* const table_; };#if GTEST_HAS_TYPED_TEST using testing::Types; // Google Test 对于不同类型(Type)提供了两种方式去复用测试 // 第一种叫 "typed tests". //使用场景:如果你已经全部知道编写测试时要使用的参数类型 // 为了编写类型化的测试用例,需要使用以下宏 // TYPED_TEST_SUITE(TestCaseName, TypeList); 去声明并指定参数类型 // 如果使用 TEST_F宏, 参数TestCaseName必须和测试夹具类名称匹配 // 我们想要测试的types列表 typedef Types
Implementations; TYPED_TEST_SUITE(PrimeTableTest, Implementations); // 然后使用 TYPED_TEST(TestCaseName, TestName) 去定义一个typed 测试,和TEST_F相似 TYPED_TEST(PrimeTableTest, ReturnsFalseForNonPrimes) { // 在test正文内部,你可以通过TypeParam去引用类型参数 // 通过TestFixture引用测试夹具类,这个例子中我们不需要这样做 // 由于我们处在模板类的世界中,因此C++明确要求 //在引用测试类夹具成员是编写"this->" // 这是我们必须学习和遵守的东西 EXPECT_FALSE(this->table_->IsPrime(-5)); EXPECT_FALSE(this->table_->IsPrime(0)); EXPECT_FALSE(this->table_->IsPrime(1)); EXPECT_FALSE(this->table_->IsPrime(4)); EXPECT_FALSE(this->table_->IsPrime(6)); EXPECT_FALSE(this->table_->IsPrime(100)); } TYPED_TEST(PrimeTableTest, ReturnsTrueForPrimes) { EXPECT_TRUE(this->table_->IsPrime(2)); EXPECT_TRUE(this->table_->IsPrime(3)); EXPECT_TRUE(this->table_->IsPrime(5)); EXPECT_TRUE(this->table_->IsPrime(7)); EXPECT_TRUE(this->table_->IsPrime(11)); EXPECT_TRUE(this->table_->IsPrime(131)); } TYPED_TEST(PrimeTableTest, CanGetNextPrime) { EXPECT_EQ(2, this->table_->GetNextPrime(0)); EXPECT_EQ(3, this->table_->GetNextPrime(2)); EXPECT_EQ(5, this->table_->GetNextPrime(3)); EXPECT_EQ(7, this->table_->GetNextPrime(5)); EXPECT_EQ(11, this->table_->GetNextPrime(7)); EXPECT_EQ(131, this->table_->GetNextPrime(128)); } // gtest框架在TYPED_TEST_SUITE中指定的类型列表中将针对每种类型重复每个TYPED_TEST // 很高兴我们不用多次重复定义这些#endif // GTEST_HAS_TYPED_TEST#if GTEST_HAS_TYPED_TEST_P using testing::Types; // 然而在一些时候,当你在写测试的时候你不知道全部的类型 // 例如, 你是一个接口的作者,但是这个接口是由他人去写实现 // 你可能会想写一组测试去确保每个实现都基本满足了需求 // 但是你不知道这个接口的将来别人是怎么去写实现 // // 没有知道的类型参数,你怎么去写测试用例呢 // 这就是"type-parameterized tests"能够帮你做到的事情 // 它比类型测试要复杂得多,但是作为回报,你会得到可以在许多情况下重用的测试模式 // 接下来教你怎么做 // 第一, 定义一个测试夹具类模板. 这里我们只需复用PrimeTableTest定义的夹具 template
class PrimeTableTest2 : public PrimeTableTest
{ }; // 第二, 声明这个测试用例. 参数名称就是测试夹具类名称 // 通常也有测试名称这个参数. 这里 _P这个后缀代表 "parameterized" 或 "pattern" TYPED_TEST_SUITE_P(PrimeTableTest2); // 第三, 使用 TYPED_TEST_P(TestCaseName, TestName) 去定义一个测试 // 和你在TEST_F做的差不多. TYPED_TEST_P(PrimeTableTest2, ReturnsFalseForNonPrimes) { EXPECT_FALSE(this->table_->IsPrime(-5)); EXPECT_FALSE(this->table_->IsPrime(0)); EXPECT_FALSE(this->table_->IsPrime(1)); EXPECT_FALSE(this->table_->IsPrime(4)); EXPECT_FALSE(this->table_->IsPrime(6)); EXPECT_FALSE(this->table_->IsPrime(100)); } TYPED_TEST_P(PrimeTableTest2, ReturnsTrueForPrimes) { EXPECT_TRUE(this->table_->IsPrime(2)); EXPECT_TRUE(this->table_->IsPrime(3)); EXPECT_TRUE(this->table_->IsPrime(5)); EXPECT_TRUE(this->table_->IsPrime(7)); EXPECT_TRUE(this->table_->IsPrime(11)); EXPECT_TRUE(this->table_->IsPrime(131)); } TYPED_TEST_P(PrimeTableTest2, CanGetNextPrime) { EXPECT_EQ(2, this->table_->GetNextPrime(0)); EXPECT_EQ(3, this->table_->GetNextPrime(2)); EXPECT_EQ(5, this->table_->GetNextPrime(3)); EXPECT_EQ(7, this->table_->GetNextPrime(5)); EXPECT_EQ(11, this->table_->GetNextPrime(7)); EXPECT_EQ(131, this->table_->GetNextPrime(128)); } // 类型参数化测试涉及一个额外的步骤:必须列举你定义的测试: REGISTER_TYPED_TEST_SUITE_P( PrimeTableTest2, // 第一个参数是测试用例名称. // 其余的参数是测试名称,以下有3个测试名 ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime); // 到这里测试模型完成. 然而, 你没有任何真正的测试,因为您还没有说出要运行的类型。 // 要把抽象测试模型转换为真实的测试 // 你需要用带有类型列表实例化 // 通常测试模型都是在一个.h的头文件中定义,任何人可以通过#include包含进来和进行实例化 // 你甚至可以在同一程序中序列化多次这个对象。 // 为了区分不同的实例,请给每个实例起一个名称,成为测试用例名称的一部分,可以在测试过滤器中使用。 //我们要测试的类型列表。 请注意,它不是在我们编写 TYPED_TEST_P()s. typedef Types
PrimeTableImplementations; INSTANTIATE_TYPED_TEST_SUITE_P(OnTheFlyAndPreCalculated, // 实例名称 PrimeTableTest2, // 测试用例名称 PrimeTableImplementations); // 类型列表#endif // GTEST_HAS_TYPED_TEST_P} // namespace

建议大家先跟着注释读一篇代码或者多边代码。这个地方真的有点抽象和复杂,如果C++学习不是很好,理解起来会费劲,我确实没有完全理解,只看出了一个大概。

先看运行结果

结合输出看看有没有帮助理解类型参数测试的真谛。

 

3.新出现的宏

这里的类型参数宏测试的引入

// 我们想要测试的types列表typedef Types
Implementations;TYPED_TEST_SUITE(PrimeTableTest, Implementations);TYPED_TEST(PrimeTableTest, ReturnsFalseForNonPrimes)

先定义一个Types,这种方式参数包含接口的两种全部实现,添加添加到宏TYPED_TEST_SUITE,有了类型参数之后,调用一个新的宏TYPED_TEST,和TEST宏类似

TYPED_TEST_SUITE_P(PrimeTableTest2);TYPED_TEST_P(PrimeTableTest2, ReturnsFalseForNonPrimes)REGISTER_TYPED_TEST_SUITE_P(		PrimeTableTest2,  // 第一个参数是测试用例名称.		// 其余的参数是测试名称,以下有3个测试名		ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime);INSTANTIATE_TYPED_TEST_SUITE_P(OnTheFlyAndPreCalculated,    // 实例名称		PrimeTableTest2,             // 测试用例名称		PrimeTableImplementations);  // 类型列表

这里没有整明白,待以后理解,再回来补充。

转载地址:http://yrxws.baihongyu.com/

你可能感兴趣的文章
FFmpeg 是如何实现多态的?
查看>>
FFmpeg 源码分析 - avcodec_send_packet 和 avcodec_receive_frame
查看>>
FFmpeg 新旧版本编码 API 的区别
查看>>
RecyclerView 源码深入解析——绘制流程、缓存机制、动画等
查看>>
Android 面试题整理总结(一)Java 基础
查看>>
Android 面试题整理总结(二)Java 集合
查看>>
学习笔记_vnpy实战培训day02
查看>>
学习笔记_vnpy实战培训day03
查看>>
VNPY- VnTrader基本使用
查看>>
VNPY - CTA策略模块策略开发
查看>>
VNPY - 事件引擎
查看>>
MongoDB基本语法和操作入门
查看>>
学习笔记_vnpy实战培训day04_作业
查看>>
OCO订单(委托)
查看>>
学习笔记_vnpy实战培训day05
查看>>
学习笔记_vnpy实战培训day06
查看>>
Python super钻石继承
查看>>
回测引擎代码分析流程图
查看>>
Excel 如何制作时间轴
查看>>
股票网格交易策略
查看>>