sample6展示了如何测试 使用了同一个接口对相同属性的多个实现——也就是接口测试。
首先编译出sample6,在test/src/samples/CMakeLists.txt
最后添加一下两行:
add_executable(sample6_unittest sample6_unittest.cc)
target_link_libraries(sample6_unittest PUBLIC gtest gtest_main)
先看一下要测试的接口源码。
prime_tables.h
prime_tables中定义了一个接口PrimeTable
,主要有两个方法其一是确定一个数字是否为质数另一是获得一个数的下一个质数。接口定义如下:
class PrimeTable {
public:
virtual ~PrimeTable() {}
virtual bool IsPrime(int n) const = 0; //判定一个数是否为质数
virtual int GetNextPrime(int p) const = 0; //获得大于p的质数中邻近p的质数,如果下一个质数超出质数表上限则返回-1.
};
第一种实现方式,实时计算质数OnTheFlyPrimeTable
。
第二种实现方式,先判断出一个数是否为质数,而后将结果存储到数组中PreCalculatedPrimeTable
。
sample6_unittest.cc
首先定义了一些工厂函数来创建prime_tables
中定义的类的实例,如果你的所有实现都可以用相同的方式创建则可以跳过这一步。
template <class T>
PrimeTable* CreatePrimeTable();
template <>
PrimeTable* CreatePrimeTable<OnTheFlyPrimeTable>() {
return new OnTheFlyPrimeTable;
}
template <>
PrimeTable* CreatePrimeTable<PreClaculatedPrimeTable>() {
return new PreCalculatedPrimeTable(10000);
}
然后定义一个test fixture模板。
template <class T>
class PrimeTableTest: public testing::Test {
protected:
PrimeTableTest(): table(CreatePrimeTable<T>()) {} //构造方法使用工厂函数创建一个由T实现的质数表
~PrimeTableTest() override { delete table_; }
PrimeTable* const table_;
};
using testing::Types;
Google Test提供了两种方式来复用不同类型的测试。第一种叫做typed tests
。如果在编写测试的时候已经知道了所有的类型,就应该使用他。
要编写一个typed test case,首先使用TYPED_TEST_SUITE(TestCaseName, TypeList)
来声明它并指定类型参数。和TEST_F一样,TestCaseName必须和test fixture名字相同。
typedef Types<OnTheFlyPrimeTable, PreCalculatePrimeTable> Implementations; // 要测试的类型列表。
TYPED_TEST_SUITE(PrimeTableTest, Implementations);
然后使用 TYPED_TEST(TestCaseName, TestName)来定义一个typed test,和TEST_F类似。
TYPED_TEST(PrimeTableTest, 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));
}
在测试内部,可以用TypeParam
来引用类型参数(type parameter),用TestFixture
来引用test fixture类,但是以上例子中不需要引用他们。由于使用模板,C++规则要求在引用test fixture类的成员时需要明确写上this->
。
ps.test内编写的语句没有什么新的内容。
而后是另外两个TYPED_TEST,分别是TYPED_TEST(PrimeTableTest, ReturnsTrueForPrimes)
用来测试方法IsPrime
对一些质数的返回值是否为True,和TYPED_TEST(PrimeTableTest, CanGetNextPrime)
则用来测试方法GetNextPrime
对一些整数的返回值是否符合预期。
真相是Google Test会将TYPED_TEST_SUITE中指定的类型列表中的每一个类型进行重复TYPED_TEST测试。
从输出结果可以看到,Google Test的确对TYPED_TEST_SUITE中指定的TypeParam进行了重复TYPED_TEST测试。
以上是第一种知道所有要测试的具类类型的测试,接着看另一种不知道所有具类类型的类型测试。
有时在测试的时候并不知道所有要测试的所有类型。例如你是一个接口的设计者,并希望其他人实现它,你可能想写一组测试来确保每一个实现都符合要求,但是不知道将来会写出什么实现方式。
如何在不提交类型参数的情况下编写测试,这就是type-parameterized test
可以做的事情,它比typed test复杂一些,但是你可以得到一个在很多情况下可以复用的测试模式,下面是实现方式。
首先定义一个test fixture类模板,这里我们只是重用了之前的PrimeTableTest
test fixture。
template <class T>
class PrimeTableTest2: public PrimeTableTest<T>{};
然后定义test case,参数是test fixture名称,同时也是test case的名字。后缀_P
表示参数化或模式(parameterized
orpattern
)。
TYPED_TEST_SUITE_P(PrimeTableTest2);
接着使用TYPED_TEST_P(TestCaseName, TestName)
定义一个test,和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));
}
ok,内容和TYPED_TEST
测试一样,之后还有test caseTYPED_TEST_P
的另外两个test,TYPED_TEST_P(PrimeTableTest2, ReturnsTrueForPrimes)
以及TYPED_TEST_P(PrimeTableTest2, CanGetNextPrime)
分别对IsPrime
方法和GetNextPrime
方法进行测试。
type-parameterized tests涉及一个额外的步骤,必须列举你定义的一个测试。
REGISTER_TYPED_TEST_SUITE_P(PrimeTableTest2, ReturnsFalseForNonPrimes, ReturnsTrueForPrimes, CanGetNextPrime);
第一个参数是test case名称,其余参数是所有test的名称。
这时,test pattern就完成了,然而到现在为之还没有指定想用哪些类型来进行运行测试。
要把抽象的测试类型变成真正的测试,你需要一个类型列表来实例化它。通常测试模式应该定义在一个.h中,任何人都可以包含和实例化它,甚至可以在同一个程序中多次实例化它。为了区分不同的实例你可以给每一个实例取一个名字,这个名字将成为test case名字的一部分,并且可以在test filters中使用。
我们要测试的类型列表,注意他们不一定要在写TYPED_TEST_P()
时定义。
typedef Types<OnTheFlyPrimeTable, PreCalculatedPrimeTable> PrimeTableImplementations;
INSTANTIATE_TYPED_TEST_SUITE_P(OnTheFlyAndPreCalculated, PrimeTableTest2, PrimeTableImplementations);
INSTANTIATE_TYPED_TEST_SUITE_P
的三个参数依次为,实例名称(instance name)、test case name、type list。
ok贴一下这部分最后的运行结果:
可以看到输出的运行结果和type test相比多了一个OnTheFlyAndPreCalculated
,是Instance name
正如最后那部分描述的那样,instance name
会成为test case名字的一部分。
总结一下
最后总结一下,sample6中主要演示了如何对抽象接口的具类实现进行测试,分以下两种测试方式。
已知要测试的抽象接口的实现类的类型
这种情况下可以直接使用type test case
,以下是测试步骤。
- 第一步:定义一个test fixture模板:
template <class T>
class testFixtureName: public testing::Test{}; // 类内部的话则需要利用T来实例化出抽象接口的具体实现类。
- 第二步:使用
TYPED_TEST_SUITE
指定要测试的类,TYPED_TEST_SUITE
需要连个参数,第一个是上一步定义的test fixture模板类的名字,第二个则是一个Types
类型模板,需要指定所有要测试的类名称(Types
应该在命名空间testing
下)。
typedef Types<className1, className2, className3, ...> Implementations;
TYPED_TEST_SUITE(testFictureName, Implementations);
- 第三步:使用
TYPED_TEST_SUITE
,和定义普通TEST
或者TEST_F
一样定义test case以及其下的所有test。其中test case的名称需要和第一步中定义的test fixture名称一样。
TYPED_TEST_SUITE(testFixtureName, testName) {}
不知道要测试的所有抽象接口的实现类的类型
这种情况下需要使用type-parameterized test
,以下是测试步骤。
- 第一步:定义test fixture的模板,和上一种情况的定义方式相同。
- 第二步:使用
TYPED_TEST_SUITE_P
声明test case,其接受一个参数,参数类型是第一步中定义的test fixture模板类的名字。
TYPED_TEST_SUITE_P(testFixtureName_P);
- 第三步:使用
TYPED_TEST_P
定义出具体的test case 和 test,其定义方式和TEST_F
相同,两个参数test case name和test name,其中test case name需要和第一步中定义的test fixture名字相同。
TYPED_TEST_P(testFixtureName_P, testName) {}
- 第四步:使用
REGISTER_TYPE_TEST_SUITE_P
列举第三步中定义的所有test,其参数含义为:第一个参数是第一步中定义的test fixture模板名字,其后的参数为第三步中定义的所有test name。
REGISTER_TYPE_TEST_SUITE_P(testFixtureName_P, testName1, testName2, testName3, ...)
以上所有的test-parameterized
部分全部定义完毕,最后便是指定要测试的抽象接口的实现类的类型。
- 最后一步:使用
INSTANTIATE_TYPED_TEST_SUITE_P
指定要测试的类,三个参数instance name(为每一个实例取的名字)、test case name(和第一步中的test fixture模板类名字相同)、Type list(Types类型,指明了接口实现类的类名字)。更好的做法是将其定义在头文件内,以便更方便被引用。
typedef Types<className1, className2, ...> PrimeTableImplementations;
INSTANTIATE_TYPED_TEST_TUITE_P(InstanceName, testFixtureName, PrimeTableImplementations);