gtest基础使用09:Google Test自带示例六:Interface

一、环境信息

  1. Visual Studio 2019
  2. Windows 10
  3. 特别注意:如果你在VS 2019下使用其集成的Google Test运行sample06,示例中的宏TYPED_TEST_SUITE需要更正为 TYPED_TEST_CASE,否则无法编译通过。经过分析,很可能是 VS 2019集成的gtest版本过低导致
  4. 前导知识: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 执行结果
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值