gtest入门

摘要:
介绍gtest是谷歌开发的用来做C++单元测试的测试框架基本概念使用gtest,你就需要写断言,用来检查一个表达式是否为true。测试用例使用断言来核实被测试代码的行为。如果一个断言失败了,gtest会打印出失败的源文件和行号,连同失败的信息。基本的断言true/false条件判断FatalassertionNonfatalassertionVerifiesASSERT_TRUEEXPECT_TRUEconditionistrueASSERT_FALSEEXPECT_FALSEconditionisfalse二进制比较这一块是比较两个值的断言FatalassertionNonfatalassertionVerifierASSERT_EQEXPECT_EQv1==v2ASSERT_NEEXPECT_NEv1!在这个函数中,可以放入任何有效的C++表达式,并使用gtest断言检查值测试结果被断言来判断。创建一个fixture步骤:写一个类继承testing::Test。

介绍

gtest是谷歌开发的用来做C++单元测试的测试框架

基本概念

使用gtest,你就需要写断言(assertions),用来检查一个表达式是否为true。断言的结果有三个:正确、非致命错误、致命错误。如果出现致命错误,就会退出当前函数,否则继续执行当前函数的后续部分。

测试用例(tests)使用断言来核实被测试代码的行为。

测试组件(suits)可以包含一个或多个测试用例。通过把测试用例分组到不同的测试组件,可以展现测试代码的结构。如果同一个测试组件中的测试用例需要共享某些对象,你可以把它们放到一个 fixture 类中。

一个测试程序,可以包含多个测试组件。

断言

gtest的断言就像函数调用一样是一些宏。通过断言一个类或者函数的表现来测试它们。如果一个断言失败了,gtest会打印出失败的源文件和行号,连同失败的信息。你可以定义失败信息,这会被附加到gtest的输出信息上。

ASSERT_* 版本的断言失败的时候生成致命错误,并退出当前函数。EXPECT_* 版本的断言生成非致命错误,不会退出当前函数。通常来说,更推荐EXPECT_*宏,因为它们可以在一个用例中报告出更多的失败测试。不过,如果断言失败时,继续执行下去没有意义,你就应该使用ASERT_* 断言。

因为测试用例失败的时候,ASSERT_* 断言会立刻从函数中退出,所以,可能这样会跳过资源清理的代码,这或许会导致内存泄漏。

为了提供一个定制的错误信息,仅仅使用重定向操作符到断言的宏就可以了:

ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length";

for (int i = 0; i < x.size(); ++i) {
  EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " <<i;
}

任何能被重定向到 std::ostream 的对象,都可以被重定向到断言的宏,包括 C风格的字符串或者 std::string 对象。如果一个宽字节的字符串重定向到了断言,打印输出的时候,它会被转为UTF-8格式。

基本的断言

true/false 条件判断

Fatal assertionNonfatal assertionVerifies
ASSERT_TRUE(condition)EXPECT_TRUE(condition)condition is true
ASSERT_FALSE(condition)EXPECT_FALSE(condition)condition is false

二进制比较

这一块是比较两个值的断言

Fatal assertionNonfatal assertionVerifier
ASSERT_EQ(v1, v2)EXPECT_EQ(v1, v2)v1 == v2
ASSERT_NE(v1, v2)EXPECT_NE(v1, v2)v1 != v2
ASSERT_LT(v1, v2)EXPECT_LT(v1, v2)v1 < v2
ASSERT_GT(v1, v2)EXPECT_GT(v1, v2)v1 > v2

两个值必须是运算符可比的,否则会编译错误。如果重载了需要的运算符,这些断言可以用在用户自定义的类型上。

一般来说,相对于 ASSERT_TRUE(actual == expected),ASSERT_EQ(actual, expected)更被推荐使用,因为失败的时候,它可以告诉你两个参数的值。

参数只被评判一次,因此参数可以有副作用。不过,参数的顺序是未定义的,所以测试代码不应该依赖其它的测试。

ASSERT_EQ指的是指针相等,如果判断C语言风格字符串,它判断的是内存位置是否相等,不是是否有相同的值。如果你想比较 const char* 类型的字符串,使用ASSERT_STREQ。判断字符串是否为NULL,使用ASSERT_STREQ(str, NULL)。比较两个 std::string 对象,应该使用ASSERT_EQ。

对于指针比较,使用 *_EQ(ptr, nullptr)和 *_NE(ptr, nullptr)

对于浮点型数值的比较,看advanced文档

字符串比较

比较C风格的字符串

Fatal assertionNonfatal assertionVerifier
ASSERT_STREQ(s1, s2)EXPECT_STREQ(s1, s2)字符串内容相同
ASSERT_STRNE(s1, s2)EXPECT_STRNE(s1, s2)字符串内容不同
ASSERT_STRCASEEQ(s1, s2)EXPECT_STRCASEEQ(s1, s2)忽略大小写,字符串内容相同
ASSERT_STRCASENE(s1, s2)EXPECT_STRCASENE(s1, s2)忽略大小写,字符串内容不同

CASE意味着忽略大小写。NULL 和空字符串("")是不同的

STREQ和STRNE也接受宽字节字符串

简单的测试例子

创建一个测试步骤

  1. 使用 TEST() 宏定义并命名一个测试函数。
  2. 在这个函数中,可以放入任何有效的C++表达式,并使用gtest断言检查值
  3. 测试结果被断言来判断。如果任何断言失败,测试就失败。
TEST(TestSuiteName, TestName) {
  ... test body ...
}

TEST() 宏的第一个参数为测试组件的名字,第二个参数为用例名字。两个名字都必须是有效的C++标识符,不能包含下划线。一个测试的全名包含测试组件和用例名字。不同的测试组件里的用例名字可以相同。

比如,一个简单的整形函数:

int Factorial(int n);  //Returns the factorial of n

该函数的测试组件可以如下:

//Tests factorial of 0.
TEST(FactorialTest, HandlesZeroInput) {
  EXPECT_EQ(Factorial(0), 1);
}

//Tests factorial of positive numbers.
TEST(FactorialTest, HandlesPositiveInput) {
  EXPECT_EQ(Factorial(1), 1);
  EXPECT_EQ(Factorial(2), 2);
  EXPECT_EQ(Factorial(3), 6);
  EXPECT_EQ(Factorial(8), 40320);
}

gtest通过组件来分组测试结果。所以,逻辑相关的测试应该划分到相同的组件,也就是,第一个参数应该相同。上面的例子,我们有两个测试用例,HandlesZeroInput 和 HandlesPositiveInput 都属于组件FactorialTest

测试设备:多个用例使用同样的数据

如果你写了多个用例,测试同样的数据,你应该使用 fixture。这可以让你重用相同的配置。

创建一个 fixture 步骤:

  1. 写一个类继承 testing::Test。使用 protected: 开始内容,因为我们从子类中访问类的成员
  2. 在类中声明任何你打算使用的成员
  3. 如果有必要,写一个默认构造函数或者SetUp() 函数为每个用例准备数据。常见的错误是把 SetUp 拼写成 Setup,这可以通过关键字 override 来避免这个问题
  4. 如果有必要,写一个析构函数或者 TearDown() 函数来释放SetUp中申请的资源。在faq.md页面可以看到应该使用 构造/析构 还是 SetUp/TearDown
  5. 如果需要的话,为你的测试定义一个子程序来实现复用。

当使用 fixture时,使用TEST_F() 而不是 TEST():

TEST_F(TestFixtureName, TestName) {
  ... test body ...
}

TEST_F(),第一个参数为fixture类的名字,_F的意思是: fixture

当然,使用 TEST_F() 之前,你需要定义一个 fixture 类,否则会编译错误

对于每一个使用 TEST_F() 的测试用例,gtest 会在运行时创建一个全新的 fixture 对象,并立刻通过 SetUp 初始化,运行测试,通过 TearDown() 清理资源,然后析构这个对象。相同 suit 的不同测试用例使用不同的 fixture 对象,并且在创建一个新的 fixture 对象前,gtest 会删除老的 fixture 对象。fixture 对象不会被重用。

作为一个例子,我们为 FIFO 的 Queue 写一个用例,如下接口:

template <typename E>  //E is the element type.
classQueue {
 public:
  Queue();
  void Enqueue(const E&element);
  E* Dequeue();  //Returns NULL if the queue is empty.
  size_t size() const;
  ...
};

首先,定义一个 fixture 类:

class QueueTest : public::testing::Test {
 protected:
  void SetUp() override{
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
  }

  //void TearDown() override {}
Queue<int>q0_;
  Queue<int>q1_;
  Queue<int>q2_;
};

TearDown 不是必须的,因为我们不需要在每个用例之后清理Queue。

现在,写一个测试组件:

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(q0_.size(), 0);
}

TEST_F(QueueTest, DequeueWorks) {
  int* n =q0_.Dequeue();
  EXPECT_EQ(n, nullptr);

  n =q1_.Dequeue();
  ASSERT_NE(n, nullptr);
  EXPECT_EQ(*n, 1);
  EXPECT_EQ(q1_.size(), 0);
  delete n;

  n =q2_.Dequeue();
  ASSERT_NE(n, nullptr);
  EXPECT_EQ(*n, 2);
  EXPECT_EQ(q2_.size(), 1);
  delete n;
}

上面的例子中,我们既使用了 ASSERT_*,也使用了 EXPECT_*。使用 EXPECT_* 是因为我们想让测试函数运行下去,即使某些用例失败了。使用 ASSERT_* 是因为某个用例失败后,继续运行下去,就没有意义了。比如如果某个指针为 nullptr,不能再判断该指针指向的值了。

当这个测试运行的时候,发生了下面的事情:

  1. 构造一个 QueueTest 对象(t1)
  2. t1.SetUp() 初始化 t1
  3. 第一个用例 IsEmptyInitially 运行
  4. t1.TearDown 执行清理工作
  5. t1 被析构
  6. 上面的步骤被重复执行,这次创建一个 t1 的对象,用来运行DequeueWorks 用例

调用测试用例

TEST() 和 TEST_F() 只是注册了测试用例。所以你不需要为了运行它们,重新列出你定义的测试。

定义用例之后,你需要使用 RUN_ALL_TESTS() 来运行它们,这个宏会返回 0 如果所有的用例都是成功的,否则就是1。RUN_ALL_TESTS() 会运行所有的用例,即使是不同的组件,甚至不同的源文件。

RUN_ALL_TESTS()做了如下事情:

  1. 保存所有 flags 的状态
  2. 为第一个测试创建 fixture 对象
  3. 通过 SetUp 初始化
  4. 运行 fixture 对象的用例
  5. 使用 TearDown 做清理工作
  6. 析构 fixture 对象
  7. 恢复所有的 flags 状态
  8. 为下一个测试重复上面的步骤,知道所有用例结束

注意:你不能忽略 RUN_ALL_TESTS() 的返回值,否则会编译错误。这么设计的原因是:自动测试根据退出码来决定是否一个测试通过了,而不是在控制台的输出。同时,你只能调用 RUN_ALL_TESTS() 一次

写main函数

写自己的main函数,这里应该返回RUN_ALL_TESTS()

样板如下:

#include "this/package/foo.h"#include "gtest/gtest.h"

namespace{

//The fixture for testing class Foo.
class FooTest : public::testing::Test {
 protected:
  //You can remove any or all of the following functions if its body
  //is empty.
FooTest() {
     //You can do set-up work for each test here.
}

  ~FooTest() override{
     //You can do clean-up work that doesn't throw exceptions here.
}

  //If the constructor and destructor are not enough for setting up
  //and cleaning up each test, you can define the following methods:

  void SetUp() override{
     //Code here will be called immediately after the constructor (right
     //before each test).
}

  void TearDown() override{
     //Code here will be called immediately after each test (right
     //before the destructor).
}

  //Objects declared here can be used by all tests in the test suite for Foo.
};

//Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
  const std::string input_filepath = "this/package/testdata/myinputfile.dat";
  const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
  Foo f;
  EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
}

//Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
  //Exercises the Xyz feature of Foo.
}

}  //namespace

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  returnRUN_ALL_TESTS();
}

testing::InitGoogleTest() 解析命令行参数,并且移除所有已识别的标志。

局限性

gtest 是线程安全的,但是这个线程安全仅仅在支持 pthread 的系统的可以。在其他系统中使用两个线程运行测试是不安全的,比如 windows。

免责声明:文章转载自《gtest入门》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇在openwrt上编译一个最简单的ipk包利用Fiddler拦截接口请求并篡改数据下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

吴恩达老师深度学习课程第四周编程作业--一步步搭建多层神经网络以及应用

本文是吴恩达老师深度学习第四周的编程作业,我是参考的文章https://blog.csdn.net/u013733326/article/details/79767169完成的。 首先还是作业要求,此次的作业要求还是和第二周作业要求一样,搭建一个神经网络来识别图片是否是猫。只不过本次作业要求搭建两个网络,一个是两层的,一个是多层的,多层的网络层数可以自定。...

深入解析synchronized

深入解析synchronized 1 常见的几个并发问题 1.可见性问题 案例演示:一个线程根据boolean类型的标记flag, while循环,另一个线程改变这个flag变量的值,另一个线程并不会停止循环。 /** * @author WGR * @create 2020/12/22 -- 20:18 */ public class Test01...

判定表法设计测试用例

判定表也称我决策表,能表示输入条件的组合,以及与每一输入组合对应的动作组合。与因果图法相似判定表法主要侧重输入条件之间的逻辑关系。 1.判定表主要包含以下五部分: 条件桩:列出所有可能的条件 条件项:列出所有的条件取值组合 动作桩:列出所有可能的操作 条件项:列出在每一种条件取值组合的情况下,执行动作桩中的哪些动作。 规则:一种条件取值组合与其对应的动作...

TypeError: Identifier 'assert' has already been declared

1、错误描述 > const assert=require('assert'); TypeError: Identifier 'assert' has already been declared at repl:1:1 at REPLServer.defaultEval (repl.js:262:27) at bound (...

TestNG使用教程详解(接口测试用例编写与断言)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/sinat_34766121/article/details/89084983GitHub: https://github.com/reese0329/testng 接口测试用例编写Testng使用...

VC:CString用法整理(转载)

1.CString::IsEmpty BOOL IsEmpty( ) const; 返回值:如果CString 对象的长度为0,则返回非零值;否则返回0。 说明:此成员函数用来测试一个CString 对象是否是空的。 示例: 下面的例子说明了如何使用CString::IsEmpty。 // CString::IsEmpty 示例 CStrin...