Google Test Sample06:接口测试示例
- 一、环境信息
- 二、Google Test Sample06
- 1. 示例概述
- 2. 对应的单元测试用例
- 3. sample06的完整编码及执行结果
一、环境信息
- Visual Studio 2019
- Windows 10
- 特别注意:如果你在VS 2019下使用其集成的Google Test运行sample06,示例中的宏TYPED_TEST_SUITE需要更正为 TYPED_TEST_CASE,否则无法编译通过。经过分析,很可能是 VS 2019集成的gtest版本过低导致
- 前导知识:C++ 类、继承、模板(泛型编程)
二、Google Test Sample06
1. 示例概述
1.1 头文件PrimeTable.h定义了接口 PrimeTable,该接口可以用于 ==bool IsPrime(int n) ==判定一个数是否为素数 、int GetNextPrime(int p) 返回比当前数大的最小素数
1.2 PrimeTable.h虽然定义了接口,但未具体实现,它的具体实施例有两个 OnTheFlyPrimeTable、PreCalculatedPrimeTable ,采用了不同算法实现了接口功能
1.3 sample06就是用来演示如何测试 拥有多个实施例的接口
1.4 PrimeTable.h具体代码及注释如下,该代码难度比较高,在正式阅读之前需要掌握C++的类、继承、模板相关知识
1.5 PrimeTable.h对应的单元测试文件是 sample06UnitTest.cpp
#pragma once //#pragma once?
// This provides interface PrimeTable that determines whether a number is a prime and determines a next prime number.
// 本头文件提供了一个接口 interface 素数表 PrimeTable,用于判定一个数是否是素数 及 返回下一个素数
// This interface is used in Google Test samples demonstrating use of parameterized tests.
// 该接口用于gtest参数测试示例
#ifndef GTEST_SAMPLES_PRIME_TABLES_H_ //#ifndef #define #endif
#define GTEST_SAMPLES_PRIME_TABLES_H_
#include <iostream>
using namespace std;
// The prime table interface. //class PrimeTable定义了接口,未具体实现
class PrimeTable
{
public:
virtual ~PrimeTable() {} //析构函数
// Returns true if and only if n is a prime number. //bool IsPrime(int n);
virtual bool IsPrime(int n) const = 0; //virtual? const = 0?
// Returns the smallest prime number greater than p; or returns -1 if the next prime is beyond the capacity of the table.
// GetNextPrime( ) 返回比p大的最小素数;若返回值超过素数表的范围,返回-1
virtual int GetNextPrime(int p) const = 0;
};
// Implementation #1 calculates the primes on-the-fly. //接口的具体实施例一:直接计算素数
class OnTheFlyPrimeTable : public PrimeTable
{
//OnTheFlyPrimeTable继承自类PrimeTable,具体实现了类PrimeTable的成员函数 IsPrime( )和 GetNextPrime( ),
public:
bool IsPrime(int n) const override //const override?
{
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++)
if ((n % i) == 0) return false; // n is divisible by an integer other than 1 and itself.
return true;
}
int GetNextPrime(int p) const override
{
if (p < 0) return -1;
for (int n = p + 1;; n++)
{
if (IsPrime(n)) return n;
}
}
};
// Implementation #2 pre-calculates the primes and stores the result in an array.//接口的具体实施例二:引入数组,对素数进行标记
class PreCalculatedPrimeTable : public PrimeTable
{
private:
const int is_prime_size_; //is_prime_size_ 定义了素数表中的最大数
bool* const is_prime_; //定义布尔型数组 is_prime_
public:
// 'max' specifies the maximum number the prime table holds. // max定义了素数表的最大数
explicit PreCalculatedPrimeTable(int max) : is_prime_size_(max + 1), is_prime_(new bool[max + 1])
{ //构造函数:is_prime_size_ = max+1 ; 定义了数组的大小 is_prime_[max + 1] //explicit?
CalculatePrimesUpTo(max); //声明了函数 CalculatePrimesUpTo( ) 并在下方的 private 中实现
}
~PreCalculatedPrimeTable() override { delete[] is_prime_; } //析构函数,释放数组is_prime_占用的内存空间. delete和new对应
bool IsPrime(int n) const override
{ //在PrimeTable中声明了接口,在实施例中进行具体实现
return 0 <= n && n < is_prime_size_ && is_prime_[n];
} // is_prime_[n]为素数返回true; 0 <= n 处理了负数的情况; n < is_prime_size_ 限定了范围
int GetNextPrime(int p) const override //返回比p大的最小素数,若超出范围,返回-1
{
for (int n = p + 1; n < is_prime_size_; n++)
{
if (is_prime_[n]) return n;
}
return -1;
}
private:
void CalculatePrimesUpTo(int max)
{
//布尔型数组 is_prime_[max+1], is_prime_size_ 数组元素个数
fill(is_prime_, is_prime_ + is_prime_size_, true); // ::std::fill()函数,将is_prime_数组中的所有元素均填充为true
is_prime_[0] = is_prime_[1] = false; //0 和 1 非素数
// Checks every candidate for prime number (we know that 2 is the only even prime).
// 对 2 至 max进行素数的预判定(2是偶数中唯一的素数)
for (int i = 2; i * i <= max; i += i % 2 + 1) //i=2 3 5 7 9 11 13 15
{
//该算法也可以得到正确的素数表
if (!is_prime_[i]) continue;
// Marks all multiples of i (except i itself) as non-prime. We are starting here from i-th multiplier,
// because all smaller complex numbers were already marked.
for (int j = i * i; j <= max; j += i) //i的倍数一定是非素数
{
is_prime_[j] = false;
}
}
}
// Disables compiler warning "assignment operator could not be generated."
// 如下语句用于规避编译警告 "assignment operator could not be generated."
void operator=(const PreCalculatedPrimeTable &rhs);
};
#endif // GTEST_SAMPLES_PRIME_TABLES_H_
2. 对应的单元测试用例
2.1 sample06UnitTest.cpp中介绍了接口测试的两种方法:I.接口的实施例在编写用例时是明确的,可以使用名为 typed tests 的方法。II.接口的实施例不明确(接口的作者不负责具体的实现),可以使用名为 type-parameterized tests的方法
2.2 在使用具体的方法前,需要先编写 factory function CreatePrimeTable( ) 用于获取实例对象。原文中提及了 ==You may be able to skip this step if all your implementations can be constructed the same way. ==这句话目前还不理解
template <class T> PrimeTable* CreatePrimeTable(); //模板函数CreatePrimeTable<T>() 返回一个指向PrimeTable类的指针
template <> PrimeTable* CreatePrimeTable<OnTheFlyPrimeTable>()
{
return new OnTheFlyPrimeTable;
}
template <> PrimeTable* CreatePrimeTable<PreCalculatedPrimeTable>()
{
return new PreCalculatedPrimeTable(10000);
}
2.3 然后定义Test Fixture,Test Fixture继承自类 testing::Test。 typed tests 和 type-parameterized tests中都会用到Test Fixture 。 文中特别提及了,通过接口测试实施例才符合真实场景:实施例通过接口调用,这样也不会遗漏多个实施例的情况
// Then we define a test fixture class template.
template <class T> class PrimeTableTest : public testing::Test
{
protected:
PrimeTable* const table_;
// The ctor calls the factory function to create a prime table implemented by T.
// 构造函数调用上面的工厂函数,工厂函数根据参数T创建一个接口的实施例对象
PrimeTableTest() : table_(CreatePrimeTable<T>()) {} //构建函数 table_=CreatePrimeTable<T>()
~PrimeTableTest() override { delete table_; }
// Note that we test an implementation via the base interface instead of the actual implementation class.
// This is important for keeping the tests close to the real world scenario, where the implementation is invoked via the base interface.
// 经由接口测试实施例符合实际场景(实施例是经由接口调用的)
// It avoids got-yas where the implementation class has a method that shadows a method with the same name (but slightly different argument
// types) in the base interface, for example.
// 这样做可以避免遗漏实施例(这里指一个接口有多个实施例)
};
2.4 typed tests的步骤如下
(1) 首先定义 types(即接口的所有实施例),比如:我们知道接口PrimeTable的实施例是OnTheFlyPrimeTable和PreCalculatedPrimeTable
using testing::Types;
typedef Types<OnTheFlyPrimeTable, PreCalculatedPrimeTable> Implementations;
(2) 使用宏 TYPED_TEST_CASE(TestCaseName, TypeList); 和宏TEST_F的使用一样:TestCaseName=test fixture name 。 注意,在vs2019下是TYPED_TEST_CASE,但是按Google最新的手册v202101,应该是TYPED_TEST_SUITE。鬼知道微软肿么搞的!?
template <class T> class PrimeTableTest : public testing::Test{};
//...
TYPED_TEST_CASE(PrimeTableTest, Implementations);
(3) 最后,使用宏TYPED_TEST(TestCaseName, TestName) 编写具体的测试用例,这里,TestCaseName也是定义的Test Fixture的名称
template <class T> class PrimeTableTest : public testing::Test{};
//...
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));
}
(4) 用例执行的时候,Google Test 会根据TYPED_TEST_CASE中定义的Types:OnTheFlyPrimeTable和PreCalculatedPrimeTable,重复执行 TYPED_TEST中定义的用例
2.5 type-parameterized tests的步骤如下
(1) type-parameterized tests适用于仅知道接口,不知道具体的实施例的情况
(2) 第一步还是要定义 test fixture //作者在这里偷懒了,直接继承了上面的PrimeTableTest,实际情况下肯定要重新编写的
template <class T> class PrimeTableTest2 : public PrimeTableTest<T> { };
(3) 第二步使用 宏TYPED_TEST_CASE_P(TestFixture)
TYPED_TEST_CASE_P(PrimeTableTest2);
(4) 第三步使用宏TYPED_TEST_P(TestFixture,TestCaseName)编写测试用例
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));
}
(5) 最后,通过 REGISTER_TYPED_TEST_SUITE_P 枚举上面的测试用例 //VS2019应该是 REGISTER_TYPED_TEST_CASE_P
// Type-parameterized tests involve one extra step: you have to enumerate the tests you defined:
REGISTER_TYPED_TEST_CASE_P( PrimeTableTest2, ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime);
// The first argument is the test case name. The rest of the arguments are the test names.
(6) 注意:通过上述步骤,完成了一个 test pattern 的定义,但实际上并未执行具体的测试。通常情况下,(2)-(5)的工作会包含在一个头文件中,由接口的作者编写,用于验证具体的实施例,实施例的作者可以包含该头文件,然后执行测试:看实施例是否能满足接口的需求
(7) test pattern 的调用方法如下,非VS2019下,需要将CASE变更为SUITE
typedef Types<OnTheFlyPrimeTable, PreCalculatedPrimeTable> PrimeTableImplementations; //定义Type
INSTANTIATE_TYPED_TEST_CASE_P(OnTheFlyAndPreCalculated, PrimeTableTest2, PrimeTableImplementations);
3. sample06的完整编码及执行结果
3.1 项目文档结构
3.2 sample06UnitTest.cpp完整源码及注释
// This sample shows how to test common properties of multiple implementations of the same interface (aka interface tests).
// 用例展示了如何测试 同一接口下不同的实施例
// The interface and its implementations are in this header.
#include "pch.h"
#include "PrimeTable.h" //将头文件PrimeTable.h放在pch.h的下方,用于规避 编译时无法找到类PrimeTable的问题
namespace
{
// First, we define some factory functions for creating instances of the implementations.
// You may be able to skip this step if all your implementations can be constructed the same way.
// 首先,我们为实施例定义工厂函数 CreatePrimeTable()。如果你的实施例可以进行相同的构建,你就可以跳过这一步?
template <class T> PrimeTable* CreatePrimeTable(); //模板函数CreatePrimeTable<T>() 返回一个指向PrimeTable类的指针
template <> PrimeTable* CreatePrimeTable<OnTheFlyPrimeTable>()
{
return new OnTheFlyPrimeTable;
}
template <> PrimeTable* CreatePrimeTable<PreCalculatedPrimeTable>()
{
return new PreCalculatedPrimeTable(10000);
}
// Then we define a test fixture class template.
template <class T> class PrimeTableTest : public testing::Test
{
protected:
PrimeTable* const table_;
// The ctor calls the factory function to create a prime table implemented by T.
// 构造函数调用上面的工厂函数,工厂函数根据参数T创建一个接口的实施例对象
PrimeTableTest() : table_(CreatePrimeTable<T>()) {} //构建函数 table_=CreatePrimeTable<T>()
~PrimeTableTest() override { delete table_; }
// Note that we test an implementation via the base interface instead of the actual implementation class.
// This is important for keeping the tests close to the real world scenario, where the implementation is invoked via the base interface.
// 经由接口测试实施例符合实际场景(实施例是经由接口调用的)
// It avoids got-yas where the implementation class has a method that shadows a method with the same name (but slightly different argument
// types) in the base interface, for example.
// 这样做可以避免遗漏实施例(这里指一个接口有多个实施例)
};
#if GTEST_HAS_TYPED_TEST
// Google Test offers two ways for reusing tests for different types. The first is called "typed tests".
// You should use it if you already know *all* the types you are gonna exercise when you write the tests.
// 如果你已经知道了接口的所有实施例,可以使用 typed tests
// To write a typed test case, first define the list of types we want to test.
// 1.编写typed test case,首先定义 types(即接口的所有实施例)
using testing::Types;
typedef Types<OnTheFlyPrimeTable, PreCalculatedPrimeTable> Implementations; //using?
// then use TYPED_TEST_SUITE(TestCaseName, TypeList); //for VS 2019,should be TYPED_TEST_CASE(TestCaseName, TypeList);
// to declare it and specify the type parameters. As with TEST_F,TestCaseName must match the test fixture name.
// 2.使用宏TYPED_TEST_CASE(TestCaseName, TypeList); 和宏TEST_F的使用一样, TYPED_TEST_SUITE(TestCaseName, TypeList);中的TestCaseName=test fixture name
TYPED_TEST_CASE(PrimeTableTest, Implementations);
// Then use TYPED_TEST(TestCaseName, TestName) to define a typed test,similar to TEST_F.
// 3.使用宏TYPED_TEST 编写具体的测试用例
TYPED_TEST(PrimeTableTest, ReturnsFalseForNonPrimes)
{
// Inside the test body, you can refer to the type parameter by TypeParam, and refer to the fixture class by TestFixture.
// We don't need them in this example. //在test body中,你可以... ? 在当前示例中不需要
//
// Since we are in the template world, C++ requires explicitly writing 'this->' when referring to members of the fixture class.
// This is something you have to learn to live with.
// 由于使用了模板,在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));
}
// That's it! Google Test will repeat each TYPED_TEST for each type in the type list specified in TYPED_TEST_SUITE.
// Sit back and be happy that you don't have to define them multiple times.
// Google Test 会根据TYPED_TEST_CASE中定义的Types,重复执行 TYPED_TEST中的用例
#endif // GTEST_HAS_TYPED_TEST
#if GTEST_HAS_TYPED_TEST_P
using testing::Types;
// Sometimes, however, you don't yet know all the types that you want
// to test when you write the tests. For example, if you are the
// author of an interface and expect other people to implement it, you
// might want to write a set of tests to make sure each implementation
// conforms to some basic requirements, but you don't know what
// implementations will be written in the future.
//
// How can you write the tests without committing to the type parameters? That's what "type-parameterized tests" can do for you.
// It is a bit more involved than typed tests, but in return you get a test pattern that can be reused in many contexts,
// which is a big win. Here's how you do it:
// 仅知道接口,不知道有哪些具体的实施例时,可以使用 TYPED_TEST_P
// First, define a test fixture class template. Here we just reuse the PrimeTableTest fixture defined earlier:
// 1.定义test fixture
template <class T> class PrimeTableTest2 : public PrimeTableTest<T> { };
// Then, declare the test case. The argument is the name of the test fixture, and also the name of the test case (as usual).
// The _P suffix is for "parameterized" or "pattern".
TYPED_TEST_CASE_P(PrimeTableTest2);
// Next, use TYPED_TEST_P(TestCaseName, TestName) to define a test, similar to what you do with TEST_F.
// 2.使用 TYPED_TEST_CASE_P(TestFixture) , 随后编写测试用例 TYPED_TEST_P(TestFixture,TestCaseName)
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));
}
// Type-parameterized tests involve one extra step: you have to enumerate the tests you defined:
// 3.通过 REGISTER_TYPED_TEST_SUITE_P 枚举上面的测试用例 //VS2019应该是 REGISTER_TYPED_TEST_CASE_P
REGISTER_TYPED_TEST_CASE_P( PrimeTableTest2, ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime);
// The first argument is the test case name. The rest of the arguments are the test names.
// At this point the test pattern is done. However, you don't have any real test yet as you haven't said which types you want to run
// the tests with.
// 通过上述的三个步骤,test pattern就定义结束了。但是,此时并没有进行实际测试,因为你并没有指定实施例(也就是Types) //test pattern?
// To turn the abstract test pattern into real tests, you instantiate it with a list of types.
// Usually the test pattern will be defined in a .h file, and anyone can #include and instantiate it.
// 通常情况下,test pattern 定义在头文件中(由接口的作者编写),用于验证具体的实施例
// 实施例的作者可以包含该头文件,然后执行测试:看实施例是否能满足接口的需求(通过对应的测试用例就是满足接口需求)
// You can even instantiate it more than once in the same program.
// To tell different instances apart, you give each of them a name, which will become part of the test case name and can be used in test filters.
// The list of types we want to test. Note that it doesn't have to be defined at the time we write the TYPED_TEST_P()s.
// 如下仅仅是一个演示,实际情况下,test pattern需要作为头文件被调用执行
typedef Types<OnTheFlyPrimeTable, PreCalculatedPrimeTable> PrimeTableImplementations; //定义Type
INSTANTIATE_TYPED_TEST_CASE_P(OnTheFlyAndPreCalculated, PrimeTableTest2, PrimeTableImplementations);
// Instance name ,Test case name/Test fixture name,Type list
#endif // GTEST_HAS_TYPED_TEST_P
} // namespace
3.3 执行结果