1. TestCase的介绍
Gtest提供了若干个case方法进行测试不同的用例。主要常见的有TEST/TEST_F及TEST_P宏的使用。在每个TestCase中可以通过断言提供的方法进行控制检查程序的预期走向是否是期望的结果,从而以此来判定程序的正确性。在同一份TestCase中不能同时出现TEST和TEST_F两者进行混用;其次TEST_F比TEST强的地方是会通过继承::testing::Test生成一个新类,而且这是必须的。在新类中可以通过void SetUp();和void TearDown();进行创建和清除相关的资源数据;

2. TEST宏
TEST宏的作用是创建一个简单测试,它定义了一个测试函数,在这个函数里可以使用任何C++代码并使用提供的断言来进行检查。

TEST语法定义:
TEST(test_case_name, test_name)

  • test_case_name第一个参数是测试用例名,通常是取测试函数名或者测试类名
  • test_name 第二个参数是测试名这个随便取,但最好取有意义的名称
  • 当测试完成后显示的测试结果将以"测试用例名.测试名"的形式给出
// SharedUnique.cpp
#include <iostream>
#include <memory>
#include <gtest/gtest.h>

// A custom make_unique templete class
#include <Memory.h>

using namespace sampleCXX::common;

class Base {
public:
	Base(std::string name):m_name{name} {
		std::cout << "name: " << m_name << std::endl;
	}

	std::string getName() {
		return m_name;
	}
	~Base() {
		
		std::cout << "destory base" << std::endl;
	}
private:
	std::string m_name;
};

void getNameFunc(std::shared_ptr<Base> base) {
	std::cout << __func__ << " : usercount: " << base.use_count() << std::endl;
	std::cout << __func__ << " : name: " << base->getName() << std::endl;
	// EXPECT_EQ(2, base.use_count());
}

TEST(Base, createInstance) {
	std::unique_ptr<Base> instance = make_unique<Base>("SvenBaseUnique");
	// 测试创建的instance实例是否不为nullptr
	EXPECT_NE(instance, nullptr);
	instance.reset();
	// 测试instance实例是否为nullptr
	EXPECT_EQ(instance, nullptr);
}

TEST(Base, getName) {
	std::unique_ptr<Base> instance = make_unique<Base>("BaseUnique");
	EXPECT_NE(instance, nullptr);
	auto name = instance->getName();
	// 测试获取的name值是否和被给的值相等
	EXPECT_STREQ(name.c_str(), "BaseUnique");
	instance.reset();
	EXPECT_EQ(instance, nullptr);
}

TEST(Base, shared_ptr) {
	std::shared_ptr<Base> instance = std::make_shared<Base>("BaseShared");
	EXPECT_NE(instance, nullptr);
	std::cout << "shared_ptr.use_count: " << instance.use_count() << std::endl;
	// 测试instance引用次数是否为1
	EXPECT_EQ(1, instance.use_count());
	getNameFunc(instance);
	EXPECT_EQ(1, instance.use_count());
}

TEST(Base, unique_ptr) {
	std::unique_ptr<Base> instance = make_unique<Base>("BaseUnique");
	EXPECT_NE(instance, nullptr);
	getNameFunc(std::move(instance));
	EXPECT_EQ(instance, nullptr);
}

在这里插入图片描述

 3. TEST_F宏
TEST_F主要是进行多样测试,就是多种不同情况的测试TestCase中都会使用相同一份的测试数据的时候将会才用它。
即用相同的数据测试不同的行为,如果采用TEST宏进行测试那么将会为不同的测试case创建一份数据。TEST_F宏将会共用一份避免重复拷贝共具灵活性。

语法定义为:
TEST_F(test_case_name, test_name);
test_case_name第一个参数是测试用例名,必须取类名。这个和TEST宏不同
test_name 第二个参数是测试名这个随便取,但最好取有意义的名称
使用TEST_F时必须继承::testing::Test类。并且该类提供了两个接口void SetUp(); void TearDown();
void SetUp()函数,为测试准备对象.
void TearDown()函数 为测试后销毁对象资源。
如下程序测试一个Base类的两个方法,它们都共用相同的数据(Base类对象):
程序通过BaseTest类创建一个共用的数据资源,这个在测试时将无需为没有测试用例单独创建Base对象。
 

#include <iostream>
#include <memory>
#include <gtest/gtest.h>

#include <Memory.h>

using namespace sampleCXX::common;

class Base {
public:
	Base(std::string name):m_name{name} {
		std::cout << "Create constructor name: " << m_name << std::endl;
	}

	std::string getName() {
		return m_name;
	}

	void setName(const std::string &name) {
		m_name = std::string(name);
	}

	~Base() {
		
		std::cout << "Destory base" << std::endl;
	}
private:
	std::string m_name;
};


class BaseTest : public ::testing::Test {
protected:
	// 为测试准备数据对象
	void SetUp() override {
		m_base = std::make_shared<Base>("SvenBaseTest");
	}
	// 清除资源
	void TearDown() override {
		m_base.reset();
	}

	std::shared_ptr<Base> m_base;
};

TEST_F(BaseTest, testCreateInstance) {
	std::unique_ptr<Base> instance = make_unique<Base>("SvenBaseUnique");
	EXPECT_NE(instance, nullptr);
	instance.reset();
	EXPECT_EQ(instance, nullptr);
}

TEST_F(BaseTest, testGetName) {
	auto name = m_base->getName();
	EXPECT_STREQ(name.c_str(), "SvenBaseTest");
}

TEST_F(BaseTest, testSetName) {
	m_base->setName("NewSvenBase");
	auto name = m_base->getName();
	EXPECT_STREQ(name.c_str(), "NewSvenBase");
}

在这里插入图片描述

下面介绍gtest中更为高级的功能:test fixture,对应的宏函数是TEST_F(TestFixtureName, TestName)。
fixture,其语义是固定的设施,而test fixture在gtest中的作用就是为每个TEST都执行一些同样的操作。

比如,要测试一个队列Queue的各个接口功能是否正常,因此就需要向队列中添加元素。如果使用一个TEST函数测试Queue的一个接口,那么每次执行TEST时,都需要在TEST宏函数中定义一个Queue对象,并向该对象中添加元素,就很冗余、繁琐。
怎么避免这部分冗余的过程?
TEST_F就是完成这样的事情,它的第一个参数TestFixtureName是个类,需要继承testing::Test,同时根据需要实现以下两个虚函数:

virtual void SetUp():在TEST_F中测试案例之前运行;
virtual void TearDown():在TEST_F之后运行。
可以类比对象的构造函数和析构函数。这样,同一个TestFixtureName下的每个TEST_F都会先执行SetUp,最后执行TearDwom。

此外,testing::Test还提供了两个static函数:
static void SetUpTestSuite():在第一个TEST之前运行
static void TearDownTestSuite():在最后一个TEST之后运行
以sample3-inl中实现的class Queue为例:

class QueueTestSmpl3 : public testing::Test { // 继承了 testing::Test
protected:  

  static void SetUpTestSuite() {
    std::cout<<"run before first case..."<<std::endl;
  } 

  static void TearDownTestSuite() {
    std::cout<<"run after last case..."<<std::endl;
  }

  virtual void SetUp() override {
    std::cout<<"enter into SetUp()" <<std::endl;
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  virtual void TearDown() override {
    std::cout<<"exit from TearDown" <<std::endl;
  }

  static int Double(int n) {
    return 2*n;
  }

  void MapTester(const Queue<int> * q) {
    const Queue<int> * const new_q = q->Map(Double);

    ASSERT_EQ(q->Size(), new_q->Size());

    for (const QueueNode<int>*n1 = q->Head(), *n2 = new_q->Head();
         n1 != nullptr; n1 = n1->next(), n2 = n2->next()) {
      EXPECT_EQ(2 * n1->element(), n2->element());
    }

    delete new_q;
  }

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};
// Tests the default c'tor.
TEST_F(QueueTestSmpl3, DefaultConstructor) {
  // !!! 在 TEST_F 中可以使用 QueueTestSmpl3 的成员变量、成员函数 
  EXPECT_EQ(0u, q0_.Size());
}

// Tests Dequeue().
TEST_F(QueueTestSmpl3, Dequeue) {
  int * n = q0_.Dequeue();
  EXPECT_TRUE(n == nullptr);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0u, q1_.Size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1u, q2_.Size());
  delete n;
}

// Tests the Queue::Map() function.
TEST_F(QueueTestSmpl3, Map) {
  MapTester(&q0_);
  MapTester(&q1_);
  MapTester(&q2_);
}

输出结果

./sample3_unittest
Running main() from /Users/self_study/Cpp/OpenSource/demo/include/googletest/googletest/samples/sample3_unittest.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from QueueTestSmpl3
run before first case...    # 所有的test case 之前运行
[ RUN      ] QueueTestSmpl3.DefaultConstructor
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.DefaultConstructor (0 ms)
[ RUN      ] QueueTestSmpl3.Dequeue
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.Dequeue (0 ms)
[ RUN      ] QueueTestSmpl3.Map
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.Map (0 ms)
run after last case...      # 所有test case结束之后运行
[----------] 3 tests from QueueTestSmpl3 (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.

5. EXPECT_*和ASSERT_*的宏介绍
5.1.gtest之断言
要测试一个类或函数,我们需要对其行为做出断言。当一个断言失败时,Google Test会在屏幕上输出该代码所在的源文件及其所在的位置行号,以及错误信息。也可以在编写断言时,提供一个自定义的错误信息,这个信息在失败时会被附加在Google Test的错误信息之后。
断言常常成对出现,它们都测试同一个类或者函数,但对当前功能有着不同的效果。ASSERT_*版本的断言失败时会产生致命失败,并结束当前函数。EXPECT_*版本的断言产生非致命失败,而不会中止当前函数。通常更推荐使用EXPECT_*断言,因为它们运行一个测试中可以有不止一个的错误被报告出来。但如果在编写断言如果失败,就没有必要继续往下执行的测试时,你应该使用ASSERT_*断言。 因为失败的ASSERT_*断言会立刻从当前的函数返回,可能会跳过其后的一些的清洁代码,这样也许会导致空间泄漏。

gtest中断言的宏可以分为两类:一类是ASSERT宏,另一类就是EXPECT宏了。
1、ASSERT_*系列:如果当前点检测失败则退出当前函数
2、EXPECT_*系列:如果当前点检测失败则继续往下执行

5.2. gtest断言分类

7. 调用小记
TEST()并TEST_F()使用googletest隐式注册他们的测试。因此,与许多其他C ++测试框架不同,您不必重新列出所有已定义的测试以便运行它们。

在定义测试之后,您可以使用RUN_ALL_TESTS()它来运行它们,0如果所有测试都成功,1则返回它们。请注意,在链接单元中 RUN_ALL_TESTS()运行所有测试 - 它们可以来自不同的测试用例,甚至是不同的源文件。

调用时,RUN_ALL_TESTS()宏:

保存所有googletest标志的状态
为第一次测试创建测试夹具对象。

通过初始化它SetUp()。

在夹具对象上运行测试。

通过清理夹具TearDown()。

删除夹具。

恢复所有googletest标志的状态

重复上述步骤进行下一次测试,直到所有测试都运行完毕。

如果发生致命故障,将跳过后续步骤。

重要提示:您不能忽略返回值RUN_ALL_TESTS(),否则您将收到编译器错误。此设计的基本原理是自动化测试服务根据其退出代码确定测试是否已通过,而不是根据其stdout / stderr输出; 因此你的main()函数必须返回值RUN_ALL_TESTS()。
此外,你应该RUN_ALL_TESTS()只打一次电话。多次调用它会与某些高级googletest功能(例如线程安全死亡测试)冲突,因此不受支持。

其实Gtest相比Gmock的使用是简单的多了,主要合理的使用以上断言就能为自己的程序写出一个自动化的测试流程;
 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐