GoogleTest 高级主题

简介

现在您已经阅读了 GoogleTest 入门 并学习了如何使用 GoogleTest 编写测试,现在是学习一些新技巧的时候了。本文档将向您展示更多断言,以及如何构建复杂的失败消息、传播致命失败、重用和加速您的测试 fixture,以及在测试中使用各种标志。

更多断言

本节介绍一些不太常用但仍然很重要的断言。

显式成功和失败

请参阅断言参考中的显式成功和失败

异常断言

请参阅断言参考中的异常断言

用于更好错误消息的谓词断言

尽管 GoogleTest 拥有一组丰富的断言,但它们永远不可能完整,因为不可能(也不是一个好主意)预测用户可能遇到的所有情况。因此,有时用户不得不使用 EXPECT_TRUE() 来检查一个复杂的表达式,因为缺少更好的宏。这存在一个问题,即不显示表达式各个部分的值,从而难以理解出错的原因。作为一种解决方法,一些用户选择自己构建失败消息,将其流式传输到 EXPECT_TRUE() 中。然而,这很笨拙,尤其是在表达式具有副作用或评估成本很高时。

GoogleTest 为您提供了三种不同的解决方案来解决这个问题

使用现有的布尔函数

如果您已经有一个返回 bool(或可以隐式转换为 bool 的类型)的函数或仿函数,您可以在谓词断言中使用它来免费打印函数参数。有关详细信息,请参阅断言参考中的 EXPECT_PRED*

使用返回 AssertionResult 的函数

虽然 EXPECT_PRED*() 及其朋友对于快速工作很方便,但语法并不令人满意:您必须为不同的元数使用不同的宏,并且感觉更像 Lisp 而不是 C++。::testing::AssertionResult 类解决了这个问题。

AssertionResult 对象表示断言的结果(无论是成功还是失败,以及相关的消息)。您可以使用以下工厂函数之一创建 AssertionResult

namespace testing {

// Returns an AssertionResult object to indicate that an assertion has
// succeeded.
AssertionResult AssertionSuccess();

// Returns an AssertionResult object to indicate that an assertion has
// failed.
AssertionResult AssertionFailure();

}

然后,您可以使用 << 运算符将消息流式传输到 AssertionResult 对象。

为了在布尔断言(例如 EXPECT_TRUE())中提供更具可读性的消息,编写一个返回 AssertionResult 而不是 bool 的谓词函数。例如,如果您将 IsEven() 定义为

testing::AssertionResult IsEven(int n) {
  if ((n % 2) == 0)
    return testing::AssertionSuccess();
  else
    return testing::AssertionFailure() << n << " is odd";
}

而不是

bool IsEven(int n) {
  return (n % 2) == 0;
}

失败的断言 EXPECT_TRUE(IsEven(Fib(4))) 将打印

Value of: IsEven(Fib(4))
  Actual: false (3 is odd)
Expected: true

而不是更不透明的

Value of: IsEven(Fib(4))
  Actual: false
Expected: true

如果您还希望在 EXPECT_FALSEASSERT_FALSE 中提供信息性消息(Google 代码库中三分之一的布尔断言是负面的),并且可以接受在成功情况下使谓词变慢,则可以提供成功消息

testing::AssertionResult IsEven(int n) {
  if ((n % 2) == 0)
    return testing::AssertionSuccess() << n << " is even";
  else
    return testing::AssertionFailure() << n << " is odd";
}

然后语句 EXPECT_FALSE(IsEven(Fib(6))) 将打印

  Value of: IsEven(Fib(6))
     Actual: true (8 is even)
  Expected: false

使用谓词格式化器

如果您发现 EXPECT_PRED*EXPECT_TRUE 生成的默认消息不令人满意,或者谓词的某些参数不支持流式传输到 ostream,您可以改为使用谓词格式化器断言完全自定义消息的格式。有关详细信息,请参阅断言参考中的 EXPECT_PRED_FORMAT*

浮点比较

请参阅断言参考中的浮点比较

浮点谓词格式函数

一些浮点运算很有用,但并不常用。为了避免出现新的宏的爆炸式增长,我们将它们作为谓词格式函数提供,这些函数可以在谓词断言宏 EXPECT_PRED_FORMAT2 中使用,例如

using ::testing::FloatLE;
using ::testing::DoubleLE;
...
EXPECT_PRED_FORMAT2(FloatLE, val1, val2);
EXPECT_PRED_FORMAT2(DoubleLE, val1, val2);

上面的代码验证 val1 小于或近似等于 val2

使用 gMock 匹配器进行断言

请参阅断言参考中的 EXPECT_THAT

更多字符串断言

(如果您还没有,请先阅读上一节。)

您可以使用 gMock 字符串匹配器EXPECT_THAT 来执行更多字符串比较技巧(子字符串、前缀、后缀、正则表达式等)。例如,

using ::testing::HasSubstr;
using ::testing::MatchesRegex;
...
  ASSERT_THAT(foo_string, HasSubstr("needle"));
  EXPECT_THAT(bar_string, MatchesRegex("\\w*\\d+"));

Windows HRESULT 断言

请参阅断言参考中的Windows HRESULT 断言

类型断言

您可以调用函数

::testing::StaticAssertTypeEq<T1, T2>();

来断言类型 T1T2 相同。如果断言满足,则该函数不执行任何操作。如果类型不同,则函数调用将无法编译,编译器错误消息将显示 T1 and T2 are not the same type,并且很可能(取决于编译器)显示 T1T2 的实际值。这主要在模板代码中很有用。

注意:当在类模板或函数模板的成员函数中使用时,StaticAssertTypeEq<T1, T2>() 仅在该函数被实例化时才有效。例如,给定

template <typename T> class Foo {
 public:
  void Bar() { testing::StaticAssertTypeEq<int, T>(); }
};

代码

void Test1() { Foo<bool> foo; }

不会生成编译器错误,因为 Foo<bool>::Bar() 实际上从未被实例化。相反,您需要

void Test2() { Foo<bool> foo; foo.Bar(); }

导致编译器错误。

断言放置

您可以在任何 C++ 函数中使用断言。特别是,它不必是测试 fixture 类的方法。唯一的约束是,生成致命失败的断言(FAIL*ASSERT_*)只能在返回 void 的函数中使用。这是 Google 不使用异常的结果。通过将其放置在非 void 函数中,您将获得一个令人困惑的编译错误,例如 "error: void value not ignored as it ought to be""cannot initialize return object of type 'bool' with an rvalue of type 'void'""error: no viable conversion from 'void' to 'string'"

如果您需要在返回非 void 的函数中使用致命断言,一种选择是将函数通过一个 out 参数返回该值。例如,您可以将 T2 Foo(T1 x) 重写为 void Foo(T1 x, T2* result)。您需要确保即使函数过早返回,*result 也包含一些有意义的值。由于该函数现在返回 void,因此您可以在其中使用任何断言。

如果更改函数类型不是一种选择,您应该只使用生成非致命失败的断言,例如 ADD_FAILURE*EXPECT_*

注意:根据 C++ 语言规范,构造函数和析构函数不被视为返回 void 的函数,因此您不能在其中使用致命断言;如果您尝试这样做,将会收到编译错误。相反,要么调用 abort 并使整个测试可执行文件崩溃,要么将致命断言放在 SetUp/TearDown 函数中;请参阅 构造函数/析构函数与 SetUp/TearDown

警告:从构造函数或析构函数调用的帮助器函数(私有返回 void 的方法)中的致命断言不会像您可能认为的那样终止当前测试:它只是从构造函数或析构函数提前返回,可能会使您的对象处于部分构造或部分析构的状态!您几乎肯定希望 abort 或使用 SetUp/TearDown 代替。

跳过测试执行

与断言 SUCCEED()FAIL() 相关,您可以使用 GTEST_SKIP() 宏在运行时阻止进一步的测试执行。当您需要在运行时检查被测系统的先决条件并以有意义的方式跳过测试时,这非常有用。

GTEST_SKIP() 可以在单个测试用例中或从 ::testing::Environment::testing::Test 派生的类的 SetUp() 方法中使用。例如

TEST(SkipTest, DoesSkip) {
  GTEST_SKIP() << "Skipping single test";
  FAIL();  // Won't fail; it won't be executed
}

class SkipFixture : public ::testing::Test {
 protected:
  void SetUp() override {
    GTEST_SKIP() << "Skipping all tests for this fixture";
  }
};

// Tests for SkipFixture won't be executed.
TEST_F(SkipFixture, SkipsOneTest) {
  FAIL();  // Won't fail; it won't be executed
}

与断言宏一样,您可以将自定义消息流式传输到 GTEST_SKIP() 中。

教导 GoogleTest 如何打印您的值

当测试断言(例如 EXPECT_EQ)失败时,GoogleTest 会打印参数值以帮助您调试。它通过一个用户可扩展的值打印器来实现这一点。

该打印器知道如何打印内置 C++ 类型、原生数组、STL 容器以及任何支持 << 运算符的类型。对于其他类型,它会打印值中的原始字节,并希望用户能够理解它。

如前所述,该打印器是可扩展的。这意味着您可以教它更好地打印您的特定类型,而不是转储字节。为此,请为您的类型定义一个 AbslStringify() 重载作为 friend 函数模板。

namespace foo {

class Point {  // We want GoogleTest to be able to print instances of this.
  ...
  // Provide a friend overload.
  template <typename Sink>
  friend void AbslStringify(Sink& sink, const Point& point) {
    absl::Format(&sink, "(%d, %d)", point.x, point.y);
  }

  int x;
  int y;
};

// If you can't declare the function in the class it's important that the
// AbslStringify overload is defined in the SAME namespace that defines Point.
// C++'s look-up rules rely on that.
enum class EnumWithStringify { kMany = 0, kChoices = 1 };

template <typename Sink>
void AbslStringify(Sink& sink, EnumWithStringify e) {
  absl::Format(&sink, "%s", e == EnumWithStringify::kMany ? "Many" : "Choices");
}

}  // namespace foo

注意:AbslStringify() 利用一个通用的“sink”缓冲区来构造其字符串。有关 AbslStringify() 的 sink 上支持的操作的更多信息,请参见 go/abslstringify。

AbslStringify() 还可以使用 absl::StrFormat 的全能型 %v 类型说明符在其自己的格式字符串中执行类型推导。例如,上面的 Point 可以格式化为 "(%v, %v)",并将 int 值推导为 %d

有时,AbslStringify() 可能不是一个选项:您的团队可能希望仅出于测试目的而打印带有额外调试信息的类型。如果是这样,您可以改为定义一个 PrintTo() 函数,如下所示:

#include <ostream>

namespace foo {

class Point {
  ...
  friend void PrintTo(const Point& point, std::ostream* os) {
    *os << "(" << point.x << "," << point.y << ")";
  }

  int x;
  int y;
};

// If you can't declare the function in the class it's important that PrintTo()
// is defined in the SAME namespace that defines Point.  C++'s look-up rules
// rely on that.
void PrintTo(const Point& point, std::ostream* os) {
    *os << "(" << point.x << "," << point.y << ")";
}

}  // namespace foo

如果您同时定义了 AbslStringify()PrintTo(),则后者将被 GoogleTest 使用。这允许您自定义值在 GoogleTest 输出中的显示方式,而不会影响依赖于 AbslStringify() 行为的代码。

如果您已经存在一个 << 运算符,并且想要定义一个 AbslStringify(),则后者将用于 GoogleTest 打印。

如果您想自己使用 GoogleTest 的值打印器打印一个值 x,只需调用 ::testing::PrintToString(x),它会返回一个 std::string

vector<pair<Point, int> > point_ints = GetPointIntVector();

EXPECT_TRUE(IsCorrectPointIntVector(point_ints))
    << "point_ints = " << testing::PrintToString(point_ints);

有关 AbslStringify() 及其与其他库集成的更多详细信息,请参见 go/abslstringify。

正则表达式语法

当使用 Bazel 和 Abseil 构建时,GoogleTest 使用 RE2 语法。否则,对于 POSIX 系统(Linux、Cygwin、Mac),GoogleTest 使用 POSIX 扩展正则表达式语法。要了解 POSIX 语法,您可以阅读这篇 Wikipedia 条目

在 Windows 上,GoogleTest 使用其自己的简单正则表达式实现。它缺少许多功能。例如,我们不支持联合 ("x|y")、分组 ("(xy)")、括号 ("[xy]") 和重复计数 ("x{5,7}") 等。以下是我们支持的内容(A 表示一个文字字符、句点 (.) 或单个 \\ 转义序列;xy 表示正则表达式。)

表达式 含义
c 匹配任何文字字符 c
\\d 匹配任何十进制数字
\\D 匹配任何不是十进制数字的字符
\\f 匹配 \f
\\n 匹配 \n
\\r 匹配 \r
\\s 匹配任何 ASCII 空格,包括 \n
\\S 匹配任何不是空格的字符
\\t 匹配 \t
\\v 匹配 \v
\\w 匹配任何字母、_ 或十进制数字
\\W 匹配任何 \\w 不匹配的字符
\\c 匹配任何文字字符 c,它必须是标点符号
. 匹配任何单个字符,除了 \n
A? 匹配 A 出现 0 次或 1 次
A* 匹配 A 出现 0 次或多次
A+ 匹配 A 出现 1 次或多次
^ 匹配字符串的开头(不是每行的开头)
$ 匹配字符串的结尾(不是每行的结尾)
xy 匹配 x,后跟 y

为了帮助您确定您的系统上可用的功能,GoogleTest 定义了宏来控制它正在使用的正则表达式。这些宏是:GTEST_USES_SIMPLE_RE=1GTEST_USES_POSIX_RE=1。如果您希望您的死亡测试在所有情况下都有效,您可以 #if 这些宏,或者仅使用更有限的语法。

死亡测试

在许多应用程序中,存在断言,如果未满足条件,则可能导致应用程序失败。这些一致性检查(确保程序处于已知的良好状态)旨在在某些程序状态损坏后尽快失败。如果断言检查错误的条件,则程序可能会在错误的状态下继续,这可能导致内存损坏、安全漏洞或更糟。因此,测试此类断言语句是否按预期工作至关重要。

由于这些前提条件检查导致进程死亡,因此我们将此类测试称为死亡测试。更一般地说,任何检查程序是否以预期方式终止(除了抛出异常)的测试也是死亡测试。

请注意,如果一段代码抛出异常,我们不会将其视为死亡测试目的的“死亡”,因为代码的调用者可以捕获该异常并避免崩溃。如果您想验证代码抛出的异常,请参阅异常断言

如果您想测试测试代码中的 EXPECT_*()/ASSERT_*() 失败,请参阅“捕获”失败

如何编写死亡测试

GoogleTest 提供了断言宏来支持死亡测试。有关详细信息,请参阅断言参考中的死亡断言

要编写死亡测试,只需在您的测试函数中使用其中一个宏。例如:

TEST(MyDeathTest, Foo) {
  // This death test uses a compound statement.
  ASSERT_DEATH({
    int n = 5;
    Foo(&n);
  }, "Error on line .* of Foo()");
}

TEST(MyDeathTest, NormalExit) {
  EXPECT_EXIT(NormalExit(), testing::ExitedWithCode(0), "Success");
}

TEST(MyDeathTest, KillProcess) {
  EXPECT_EXIT(KillProcess(), testing::KilledBySignal(SIGKILL),
              "Sending myself unblockable signal");
}

验证

警告:如果您的死亡测试包含模拟 (mocks) 并且期望特定的退出代码,那么您必须允许通过 Mock::AllowLeak 泄漏模拟对象。这是因为如果模拟泄漏检测器检测到泄漏,它将以自己的错误代码退出。

如果需要,测试函数体也可以包含其他断言和语句。

请注意,死亡测试只关心三件事:

  1. statement 是否中止或退出进程?
  2. (在 ASSERT_EXITEXPECT_EXIT 的情况下)退出状态是否满足 predicate?或者(在 ASSERT_DEATHEXPECT_DEATH 的情况下)退出状态是否为非零?并且
  3. stderr 输出是否匹配 matcher

特别是,如果 statement 生成 ASSERT_*EXPECT_* 失败,它不会导致死亡测试失败,因为 GoogleTest 断言不会中止进程。

死亡测试命名

重要提示:我们强烈建议您遵循约定,当您的测试套件(不是测试)包含死亡测试时,将其命名为 *DeathTest,如上例所示。下面的死亡测试和线程部分解释了原因。

如果测试夹具类由普通测试和死亡测试共享,您可以使用 usingtypedef 来引入夹具类的别名,并避免重复其代码:

class FooTest : public testing::Test { ... };

using FooDeathTest = FooTest;

TEST_F(FooTest, DoesThis) {
  // normal test
}

TEST_F(FooDeathTest, DoesThat) {
  // death test
}

它是如何工作的

请参阅断言参考中的死亡断言

死亡测试和线程

两种死亡测试样式的原因与线程安全有关。由于在存在线程的情况下分叉的众所周知的问题,死亡测试应在单线程上下文中运行。然而,有时安排这种环境是不可行的。例如,静态初始化的模块可能会在到达 main 之前启动线程。一旦创建了线程,可能很难或不可能清理它们。

GoogleTest 具有三个旨在提高对线程问题的认识的功能。

  1. 当遇到死亡测试时,如果多个线程正在运行,则会发出警告。
  2. 名称以“DeathTest”结尾的测试套件会在所有其他测试之前运行。
  3. 它使用 clone() 而不是 fork() 在 Linux 上生成子进程(clone() 在 Cygwin 和 Mac 上不可用),因为当父进程有多个线程时,fork() 更有可能导致子进程挂起。

在死亡测试语句中创建线程是完全可以的;它们在单独的进程中执行,不会影响父进程。

死亡测试样式

引入“线程安全”死亡测试样式是为了帮助减轻在可能的多线程环境中进行测试的风险。它牺牲了增加的测试执行时间(可能非常显著)来换取改进的线程安全。

自动化测试框架不设置样式标志。您可以通过以编程方式设置标志来选择特定的死亡测试样式:

GTEST_FLAG_SET(death_test_style, "threadsafe");

您可以在 main() 中执行此操作,为二进制文件中的所有死亡测试设置样式,或者在单独的测试中执行此操作。回想一下,标志在运行每个测试之前保存并在之后恢复,因此您无需自己这样做。例如:

int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  GTEST_FLAG_SET(death_test_style, "fast");
  return RUN_ALL_TESTS();
}

TEST(MyDeathTest, TestOne) {
  GTEST_FLAG_SET(death_test_style, "threadsafe");
  // This test is run in the "threadsafe" style:
  ASSERT_DEATH(ThisShouldDie(), "");
}

TEST(MyDeathTest, TestTwo) {
  // This test is run in the "fast" style:
  ASSERT_DEATH(ThisShouldDie(), "");
}

注意事项

ASSERT_EXIT()statement 参数可以是任何有效的 C++ 语句。如果它通过 return 语句或抛出异常离开当前函数,则死亡测试被认为失败。某些 GoogleTest 宏可能会从当前函数返回(例如 ASSERT_TRUE()),因此请务必避免在 statement 中使用它们。

由于 statement 在子进程中运行,因此它引起的任何内存中的副作用(例如,修改变量、释放内存等)在父进程中将不可观察。 特别是,如果在死亡测试中释放内存,您的程序将无法通过堆检查,因为父进程永远不会看到回收的内存。 要解决这个问题,您可以

  1. 尽量不要在死亡测试中释放内存;
  2. 在父进程中再次释放内存;或
  3. 不要在您的程序中使用堆检查器。

由于一个实现细节,您不能在同一行放置多个死亡测试断言;否则,编译将会失败并产生一个不明显的错误消息。

尽管“线程安全”类型的死亡测试提高了线程安全性,但在使用 pthread_atfork(3) 注册的处理程序存在的情况下,仍然可能出现诸如死锁之类的线程问题。

在子例程中使用断言

注意:如果您想在一系列测试断言中放置一个子例程来检查一个复杂的条件,请考虑使用自定义 GMock 匹配器。 这样可以让您在失败时提供更具可读性的错误消息,并避免下面描述的所有问题。

向断言添加追踪信息

如果一个测试子例程从多个地方调用,当其中的一个断言失败时,很难判断失败来自哪个子例程调用。 您可以使用额外的日志记录或自定义失败消息来缓解这个问题,但这通常会使您的测试变得混乱。 一个更好的解决方案是使用 SCOPED_TRACE 宏或 ScopedTrace 实用程序

SCOPED_TRACE(message);
ScopedTrace trace("file_path", line_number, message);

其中 message 可以是任何可以流式传输到 std::ostream 的内容。 SCOPED_TRACE 宏将导致当前文件名、行号和给定的消息添加到每个失败消息中。ScopedTrace 接受参数中的显式文件名和行号,这对于编写测试帮助程序很有用。 当控制离开当前词法范围时,效果将被撤消。

例如,

10: void Sub1(int n) {
11:   EXPECT_EQ(Bar(n), 1);
12:   EXPECT_EQ(Bar(n + 1), 2);
13: }
14:
15: TEST(FooTest, Bar) {
16:   {
17:     SCOPED_TRACE("A");  // This trace point will be included in
18:                         // every failure in this scope.
19:     Sub1(1);
20:   }
21:   // Now it won't.
22:   Sub1(9);
23: }

可能会导致如下消息

path/to/foo_test.cc:11: Failure
Value of: Bar(n)
Expected: 1
  Actual: 2
Google Test trace:
path/to/foo_test.cc:17: A

path/to/foo_test.cc:12: Failure
Value of: Bar(n + 1)
Expected: 2
  Actual: 3

如果没有追踪信息,将很难知道两个失败分别来自 Sub1() 的哪个调用。(您可以向 Sub1() 中的每个断言添加额外消息来指示 n 的值,但这很乏味。)

使用 SCOPED_TRACE 的一些技巧

  1. 使用合适的消息,通常足以在子例程的开头使用 SCOPED_TRACE,而不是在每个调用点使用。
  2. 在循环中调用子例程时,使循环迭代器成为 SCOPED_TRACE 中消息的一部分,这样您就可以知道失败来自哪个迭代。
  3. 有时,追踪点的行号足以识别子例程的特定调用。 在这种情况下,您不必为 SCOPED_TRACE 选择唯一的消息。 您可以简单地使用 ""
  4. 当外部作用域中存在内部作用域时,您可以在内部作用域中使用 SCOPED_TRACE。 在这种情况下,所有活动的追踪点都将包含在失败消息中,按照它们被遇到的相反顺序。
  5. 追踪转储在 Emacs 中是可点击的 - 点击行号上的 return 键,您将被带到源文件中的那一行!

传播致命失败

使用 ASSERT_*FAIL* 时的一个常见陷阱是不理解它们在失败时只会中止当前函数,而不是整个测试。 例如,以下测试将导致段错误

void Subroutine() {
  // Generates a fatal failure and aborts the current function.
  ASSERT_EQ(1, 2);

  // The following won't be executed.
  ...
}

TEST(FooTest, Bar) {
  Subroutine();  // The intended behavior is for the fatal failure
                 // in Subroutine() to abort the entire test.

  // The actual behavior: the function goes on after Subroutine() returns.
  int* p = nullptr;
  *p = 3;  // Segfault!
}

为了缓解这个问题,GoogleTest 提供了三种不同的解决方案。 您可以使用异常、(ASSERT|EXPECT)_NO_FATAL_FAILURE 断言或 HasFatalFailure() 函数。 它们将在以下两个小节中进行描述。

使用异常断言子例程

以下代码可以将 ASSERT 失败转换为异常

class ThrowListener : public testing::EmptyTestEventListener {
  void OnTestPartResult(const testing::TestPartResult& result) override {
    if (result.type() == testing::TestPartResult::kFatalFailure) {
      throw testing::AssertionException(result);
    }
  }
};
int main(int argc, char** argv) {
  ...
  testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener);
  return RUN_ALL_TESTS();
}

如果您有其他监听器,则应在此监听器之后添加,否则他们将看不到失败的 OnTestPartResult

断言子例程

如上所示,如果您的测试调用了一个包含 ASSERT_* 失败的子例程,则测试将在子例程返回后继续。 这可能不是您想要的。

通常人们希望致命失败像异常一样传播。为此,GoogleTest 提供了以下宏

致命断言 非致命断言 验证
ASSERT_NO_FATAL_FAILURE(statement); EXPECT_NO_FATAL_FAILURE(statement); statement 在当前线程中不产生任何新的致命失败。

仅检查执行断言的线程中的失败来确定此类型断言的结果。如果 statement 创建了新线程,则将忽略这些线程中的失败。

示例

ASSERT_NO_FATAL_FAILURE(Foo());

int i;
EXPECT_NO_FATAL_FAILURE({
  i = Bar();
});

当前在 Windows 上不支持来自多个线程的断言。

检查当前测试中的失败

::testing::Test 类中的 HasFatalFailure() 如果当前测试中的断言遇到致命失败,则返回 true。 这允许函数捕获子例程中的致命失败并提前返回。

class Test {
 public:
  ...
  static bool HasFatalFailure();
};

典型的用法,基本上模拟了抛出异常的行为,是

TEST(FooTest, Bar) {
  Subroutine();
  // Aborts if Subroutine() had a fatal failure.
  if (HasFatalFailure()) return;

  // The following won't be executed.
  ...
}

如果 HasFatalFailure()TEST()TEST_F() 或测试夹具之外使用,您必须添加 ::testing::Test:: 前缀,如

if (testing::Test::HasFatalFailure()) return;

类似地,如果当前测试至少有一个非致命失败,则 HasNonfatalFailure() 返回 true,如果当前测试至少有一个任何类型的失败,则 HasFailure() 返回 true

记录其他信息

在您的测试代码中,您可以调用 RecordProperty("key", value) 来记录其他信息,其中 value 可以是字符串或 int。 如果您指定了一个XML 输出,则为键记录的最后一个值将输出到该 XML 输出中。 例如,测试

TEST_F(WidgetUsageTest, MinAndMaxWidgets) {
  RecordProperty("MaximumWidgets", ComputeMaxUsage());
  RecordProperty("MinimumWidgets", ComputeMinUsage());
}

将输出如下 XML

  ...
    <testcase name="MinAndMaxWidgets" file="test.cpp" line="1" status="run" time="0.006" classname="WidgetUsageTest" MaximumWidgets="12" MinimumWidgets="9" />
  ...

注意

  • RecordProperty()Test 类的静态成员。 因此,如果在 TEST 主体和测试夹具类之外使用,则需要在前面加上 ::testing::Test::
  • key 必须是有效的 XML 属性名称,并且不能与 GoogleTest 已经使用的属性名称冲突(namestatustimeclassnametype_paramvalue_param)。
  • 允许在测试的生命周期之外调用 RecordProperty()。如果在测试之外但在测试套件的 SetUpTestSuite()TearDownTestSuite() 方法之间调用它,则它将归因于测试套件的 XML 元素。 如果在所有测试套件之外调用它(例如,在测试环境中),它将归因于顶级 XML 元素。

在同一测试套件中的测试之间共享资源

GoogleTest 为每个测试创建一个新的测试夹具对象,以使测试独立且易于调试。 但是,有时测试使用的资源设置成本很高,使得每个测试一个副本的模型成本过高。

如果测试不更改资源,则它们共享单个资源副本没有任何坏处。 因此,除了每个测试的设置/拆卸之外,GoogleTest 还支持每个测试套件的设置/拆卸。 要使用它

  1. 在您的测试夹具类中(例如 FooTest),将一些成员变量声明为 static 以保存共享资源。
  2. 在您的测试夹具类之外(通常就在它下面),定义这些成员变量,可以选择为它们指定初始值。
  3. 在同一个测试夹具类中,定义一个公共成员函数 static void SetUpTestSuite()(记住不要拼写为带有小写 uSetupTestSuite!)来设置共享资源,以及一个 static void TearDownTestSuite() 函数来拆卸它们。

就是这样! GoogleTest 会在运行 FooTest 测试套件中的第一个测试之前(即,在创建第一个 FooTest 对象之前)自动调用 SetUpTestSuite(),并在运行其中的最后一个测试之后(即,在删除最后一个 FooTest 对象之后)调用 TearDownTestSuite()。 在此期间,测试可以使用共享资源。

请记住,测试顺序是未定义的,因此您的代码不能依赖于一个测试在另一个测试之前或之后。 此外,测试要么不能修改任何共享资源的状态,要么,如果它们确实修改了状态,则必须在将控制权传递给下一个测试之前将状态恢复到其原始值。

请注意,对于具有派生类的测试夹具类,SetUpTestSuite() 可能会被多次调用,因此您不应期望函数体中的代码只运行一次。此外,派生类仍然可以访问定义为静态成员的共享资源,因此在管理共享资源时需要仔细考虑,以避免在 TearDownTestSuite() 中未正确清理共享资源时发生内存泄漏。

这是一个关于每个测试套件设置和拆卸的示例

class FooTest : public testing::Test {
 protected:
  // Per-test-suite set-up.
  // Called before the first test in this test suite.
  // Can be omitted if not needed.
  static void SetUpTestSuite() {
    shared_resource_ = new ...;

    // If `shared_resource_` is **not deleted** in `TearDownTestSuite()`,
    // reallocation should be prevented because `SetUpTestSuite()` may be called
    // in subclasses of FooTest and lead to memory leak.
    //
    // if (shared_resource_ == nullptr) {
    //   shared_resource_ = new ...;
    // }
  }

  // Per-test-suite tear-down.
  // Called after the last test in this test suite.
  // Can be omitted if not needed.
  static void TearDownTestSuite() {
    delete shared_resource_;
    shared_resource_ = nullptr;
  }

  // You can define per-test set-up logic as usual.
  void SetUp() override { ... }

  // You can define per-test tear-down logic as usual.
  void TearDown() override { ... }

  // Some expensive resource shared by all tests.
  static T* shared_resource_;
};

T* FooTest::shared_resource_ = nullptr;

TEST_F(FooTest, Test1) {
  ... you can refer to shared_resource_ here ...
}

TEST_F(FooTest, Test2) {
  ... you can refer to shared_resource_ here ...
}

注意:虽然上面的代码将 SetUpTestSuite() 声明为 protected,但有时可能需要将其声明为 public,例如在使用 TEST_P 时。

全局设置和拆卸

就像您可以在测试级别和测试套件级别进行设置和拆卸一样,您也可以在测试程序级别进行设置和拆卸。 方法如下。

首先,您需要继承 ::testing::Environment 类来定义一个测试环境,该环境知道如何进行设置和拆卸

class Environment : public ::testing::Environment {
 public:
  ~Environment() override {}

  // Override this to define how to set up the environment.
  void SetUp() override {}

  // Override this to define how to tear down the environment.
  void TearDown() override {}
};

然后,您可以通过调用 ::testing::AddGlobalTestEnvironment() 函数向 GoogleTest 注册您的环境类的一个实例

Environment* AddGlobalTestEnvironment(Environment* env);

现在,当调用 RUN_ALL_TESTS() 时,它首先调用 SetUp() 方法。 然后执行测试,前提是没有环境报告致命错误,并且没有调用 GTEST_SKIP()。 最后,调用 TearDown()

请注意,只有在至少有一个要执行的测试时,才会调用 SetUp()TearDown()。 重要的是,即使由于致命错误或 GTEST_SKIP() 而未运行测试,也会执行 TearDown()

每次迭代调用 SetUp()TearDown() 取决于标志 gtest_recreate_environments_when_repeating。 当为每次迭代重新创建对象时,将为每个环境对象调用 SetUp()TearDown()。 但是,如果未为每次迭代重新创建测试环境,则仅在第一次迭代时调用 SetUp(),并且仅在最后一次迭代时调用 TearDown()

可以注册多个环境对象。 在此套件中,它们的 SetUp() 将按照注册的顺序调用,并且它们的 TearDown() 将以相反的顺序调用。

请注意,GoogleTest 拥有已注册环境对象的所有权。 因此,**请勿自行删除它们**。

您应该在调用 RUN_ALL_TESTS() 之前调用 AddGlobalTestEnvironment(),可能是在 main() 中。 如果您使用 gtest_main,则需要在 main() 启动之前调用此函数才能生效。 一种方法是像这样定义一个全局变量

testing::Environment* const foo_env =
    testing::AddGlobalTestEnvironment(new FooEnvironment);

但是,我们强烈建议您编写自己的 main() 并在其中调用 AddGlobalTestEnvironment(),因为依赖于全局变量的初始化会使代码更难阅读,并且当您从不同的翻译单元注册多个环境并且环境之间存在依赖关系时,可能会导致问题(请记住,编译器不保证来自不同翻译单元的全局变量的初始化顺序)。

值参数化测试

值参数化测试 允许您使用不同的参数测试代码,而无需编写同一测试的多个副本。 这在许多情况下很有用,例如

如何编写值参数化测试

要编写值参数化测试,首先您应该定义一个夹具类。 它必须同时继承自 testing::Testtesting::WithParamInterface<T>(后者是一个纯接口),其中 T 是您的参数值的类型。 为方便起见,您可以直接从 testing::TestWithParam<T> 派生夹具类,它本身同时继承自 testing::Testtesting::WithParamInterface<T>T 可以是任何可复制的类型。 如果它是一个原始指针,您有责任管理指向值的生命周期。

注意:如果您的测试夹具定义了 SetUpTestSuite()TearDownTestSuite(),则必须将其声明为 public 而不是 protected,以便使用 TEST_P

class FooTest :
    public testing::TestWithParam<absl::string_view> {
  // You can implement all the usual fixture class members here.
  // To access the test parameter, call GetParam() from class
  // TestWithParam<T>.
};

// Or, when you want to add parameters to a pre-existing fixture class:
class BaseTest : public testing::Test {
  ...
};
class BarTest : public BaseTest,
                public testing::WithParamInterface<absl::string_view> {
  ...
};

然后,使用 TEST_P 宏来定义尽可能多的使用此夹具的测试模式。 后缀 _P 表示 “parameterized”(参数化)或 “pattern”(模式),您可以选择您喜欢的理解方式。

TEST_P(FooTest, DoesBlah) {
  // Inside a test, access the test parameter with the GetParam() method
  // of the TestWithParam<T> class:
  EXPECT_TRUE(foo.Blah(GetParam()));
  ...
}

TEST_P(FooTest, HasBlahBlah) {
  ...
}

最后,您可以使用 INSTANTIATE_TEST_SUITE_P 宏来使用您想要的任何参数集来实例化测试套件。 GoogleTest 定义了许多用于生成测试参数的函数——有关详细信息,请参阅 Testing Reference 中 INSTANTIATE_TEST_SUITE_P 的说明。

例如,以下语句将从 FooTest 测试套件中实例化测试,每个测试都使用参数值 "meeny""miny""moe",并使用 Values 参数生成器

INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe,
                         FooTest,
                         testing::Values("meeny", "miny", "moe"));

注意:上面的代码必须放置在全局或命名空间作用域中,而不是在函数作用域中。

INSTANTIATE_TEST_SUITE_P 的第一个参数是测试套件实例化的唯一名称。 下一个参数是测试模式的名称,最后一个参数是参数生成器

参数生成器表达式直到 GoogleTest 初始化时(通过 InitGoogleTest())才会进行求值。 在 main 函数中完成的任何先前的初始化都可以从参数生成器访问,例如,标志解析的结果。

您可以多次实例化测试模式,因此为了区分该模式的不同实例,实例化名称将作为前缀添加到实际测试套件名称。 请记住为不同的实例化选择唯一的前缀。 上面实例化的测试将具有以下名称

您可以在 --gtest_filter 中使用这些名称。

以下语句将再次从 FooTest 中实例化所有测试,每个测试都使用参数值 "cat""dog",并使用 ValuesIn 参数生成器

constexpr absl::string_view kPets[] = {"cat", "dog"};
INSTANTIATE_TEST_SUITE_P(Pets, FooTest, testing::ValuesIn(kPets));

上面实例化的测试将具有以下名称

请注意,INSTANTIATE_TEST_SUITE_P 将实例化给定测试套件中的所有测试,无论它们的定义是在 INSTANTIATE_TEST_SUITE_P 语句之前还是之后

此外,默认情况下,每个没有相应 INSTANTIATE_TEST_SUITE_PTEST_P 都会导致测试套件 GoogleTestVerification 中的测试失败。 如果您的测试套件中这种遗漏不是错误,例如它位于一个可能由于其他原因链接进来的库中,或者测试用例列表是动态的并且可能为空,则可以通过标记测试套件来禁止此检查

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(FooTest);

您可以参阅 sample7_unittest.ccsample8_unittest.cc 以获取更多示例。

创建值参数化的抽象测试

在上面,我们在同一源文件中定义和实例化了 FooTest。 有时,您可能希望在一个库中定义值参数化测试,并让其他人稍后实例化它们。 这种模式称为抽象测试。 作为其应用程序的一个示例,当您设计接口时,您可以编写一套标准的抽象测试(可能使用工厂函数作为测试参数),期望接口的所有实现都通过这些测试。 当有人实现接口时,他们可以实例化您的套件以免费获得所有接口一致性测试。

要定义抽象测试,您应该像这样组织您的代码

  1. 将参数化测试夹具类(例如 FooTest)的定义放在头文件中,例如 foo_param_test.h。 可以将其视为声明您的抽象测试。
  2. TEST_P 定义放在 foo_param_test.cc 中,其中包括 foo_param_test.h。 可以将其视为实现您的抽象测试。

定义完成后,您可以通过包含 foo_param_test.h、调用 INSTANTIATE_TEST_SUITE_P() 并依赖于包含 foo_param_test.cc 的库目标来实例化它们。 您可以多次实例化同一个抽象测试套件,可能是在不同的源文件中。

为值参数化测试参数指定名称

INSTANTIATE_TEST_SUITE_P() 的可选最后一个参数允许用户指定一个函数或仿函数,该函数或仿函数基于测试参数生成自定义测试名称后缀。 该函数应接受一个 testing::TestParamInfo<class ParamType> 类型的参数,并返回 std::string

testing::PrintToStringParamName 是一个内置的测试后缀生成器,它返回 testing::PrintToString(GetParam()) 的值。 它不适用于 std::string 或 C 字符串。

注意:测试名称必须是非空的、唯一的,并且只能包含 ASCII 字母数字字符。 特别是,它们不应包含下划线

class MyTestSuite : public testing::TestWithParam<int> {};

TEST_P(MyTestSuite, MyTest)
{
  std::cout << "Example Test Param: " << GetParam() << std::endl;
}

INSTANTIATE_TEST_SUITE_P(MyGroup, MyTestSuite, testing::Range(0, 10),
                         testing::PrintToStringParamName());

提供自定义仿函数可以更好地控制测试参数名称的生成,特别是对于自动转换无法生成有用参数名称的类型(例如,上面演示的字符串)。 以下示例说明了多个参数、枚举类型和字符串的情况,并演示了如何组合生成器。 它使用 lambda 以保持简洁

enum class MyType { MY_FOO = 0, MY_BAR = 1 };

class MyTestSuite : public testing::TestWithParam<std::tuple<MyType, std::string>> {
};

INSTANTIATE_TEST_SUITE_P(
    MyGroup, MyTestSuite,
    testing::Combine(
        testing::Values(MyType::MY_FOO, MyType::MY_BAR),
        testing::Values("A", "B")),
    [](const testing::TestParamInfo<MyTestSuite::ParamType>& info) {
      std::string name = absl::StrCat(
          std::get<0>(info.param) == MyType::MY_FOO ? "Foo" : "Bar",
          std::get<1>(info.param));
      absl::c_replace_if(name, [](char c) { return !std::isalnum(c); }, '_');
      return name;
    });

类型测试

假设您有同一接口的多个实现,并希望确保所有实现都满足一些通用要求。 或者,您可能定义了多个应该符合同一“概念”的类型,并且您想验证它。 在这两种情况下,您都希望为不同的类型重复相同的测试逻辑。

虽然您可以为要测试的每种类型编写一个 TESTTEST_F (您甚至可以将测试逻辑分解为一个函数模板,您从 TEST 中调用它),但这很乏味且无法扩展:如果您想对 n 种类型进行 m 个测试,最终您将编写 m*nTEST

类型测试允许您对类型列表重复相同的测试逻辑。 您只需要编写一次测试逻辑,尽管您在编写类型测试时必须知道类型列表。 这是操作方法

首先,定义一个 fixture 类模板。它应该由一个类型参数化。请记住从 ::testing::Test 派生它

template <typename T>
class FooTest : public testing::Test {
 public:
  ...
  using List = std::list<T>;
  static T shared_;
  T value_;
};

接下来,将一个类型列表与测试套件关联,该套件将为列表中的每种类型重复执行

using MyTypes = ::testing::Types<char, int, unsigned int>;
TYPED_TEST_SUITE(FooTest, MyTypes);

类型别名(usingtypedef)对于 TYPED_TEST_SUITE 宏正确解析是必要的。 否则,编译器会认为类型列表中的每个逗号都会引入一个新的宏参数。

然后,使用 TYPED_TEST() 而不是 TEST_F() 为此测试套件定义类型测试。 您可以根据需要重复执行此操作多次

TYPED_TEST(FooTest, DoesBlah) {
  // Inside a test, refer to the special name TypeParam to get the type
  // parameter.  Since we are inside a derived class template, C++ requires
  // us to visit the members of FooTest via 'this'.
  TypeParam n = this->value_;

  // To visit static members of the fixture, add the 'TestFixture::'
  // prefix.
  n += TestFixture::shared_;

  // To refer to typedefs in the fixture, add the 'typename TestFixture::'
  // prefix.  The 'typename' is required to satisfy the compiler.
  typename TestFixture::List values;

  values.push_back(n);
  ...
}

TYPED_TEST(FooTest, HasPropertyA) { ... }

您可以查看 sample6_unittest.cc 以获取完整示例。

类型参数化测试

类型参数化测试类似于类型测试,不同之处在于它们不需要您预先知道类型列表。 相反,您可以先定义测试逻辑,然后在以后使用不同的类型列表实例化它。 您甚至可以在同一个程序中多次实例化它。

如果您正在设计一个接口或概念,您可以定义一套类型参数化测试,以验证任何有效的接口/概念实现都应具有的属性。 然后,每个实现的作者可以简单地使用他们的类型实例化测试套件,以验证它是否符合要求,而无需重复编写类似的测试。 这是一个例子

首先,定义一个 fixture 类模板,就像我们使用类型测试一样

template <typename T>
class FooTest : public testing::Test {
  void DoSomethingInteresting();
  ...
};

接下来,声明您将定义一个类型参数化测试套件

TYPED_TEST_SUITE_P(FooTest);

然后,使用 TYPED_TEST_P() 定义类型参数化测试。 您可以根据需要重复执行此操作多次

TYPED_TEST_P(FooTest, DoesBlah) {
  // Inside a test, refer to TypeParam to get the type parameter.
  TypeParam n = 0;

  // You will need to use `this` explicitly to refer to fixture members.
  this->DoSomethingInteresting()
  ...
}

TYPED_TEST_P(FooTest, HasPropertyA) { ... }

现在是棘手的部分:您需要在使用 REGISTER_TYPED_TEST_SUITE_P 宏实例化所有测试模式之前注册它们。 该宏的第一个参数是测试套件名称; 其余的是此测试套件中的测试名称

REGISTER_TYPED_TEST_SUITE_P(FooTest,
                            DoesBlah, HasPropertyA);

最后,您可以自由地使用所需的类型实例化该模式。 如果您将上面的代码放在头文件中,您可以在多个 C++ 源文件中 #include 它并多次实例化它。

using MyTypes = ::testing::Types<char, int, unsigned int>;
INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);

为了区分该模式的不同实例,INSTANTIATE_TYPED_TEST_SUITE_P 宏的第一个参数是一个前缀,它将被添加到实际的测试套件名称中。 请记住为不同的实例选择唯一的前缀。

在类型列表仅包含一种类型的特殊情况下,您可以直接编写该类型而无需 ::testing::Types<...>,例如这样

INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int);

您可以查看 sample6_unittest.cc 以获取完整示例。

测试私有代码

如果您更改了软件的内部实现,只要用户看不到该更改,您的测试就不应中断。 因此,根据黑盒测试原则,大多数情况下,您应该通过公共接口测试您的代码。

如果您仍然发现需要测试内部实现代码,请考虑是否有更好的设计。 测试内部实现的愿望通常表明该类做得太多了。 考虑提取一个实现类,并对其进行测试。 然后在原始类中使用该实现类。

但是,如果您绝对必须测试非公共接口代码,您可以这样做。 有两种情况需要考虑

为了测试它们,我们使用以下特殊技术

“捕获”失败

如果您正在 GoogleTest 之上构建一个测试实用程序,您将需要测试您的实用程序。 您将使用什么框架来测试它? 当然是 GoogleTest。

挑战在于验证您的测试实用程序是否正确报告失败。 在通过抛出异常来报告失败的框架中,您可以捕获异常并对其进行断言。 但是 GoogleTest 不使用异常,那么我们如何测试一段代码是否会生成预期的失败?

"gtest/gtest-spi.h" 包含一些用于执行此操作的构造。 在 #including 此标头后,您可以使用

  EXPECT_FATAL_FAILURE(statement, substring);

断言 statement 在当前线程中生成一个致命的(例如 ASSERT_*)失败,其消息包含给定的 substring,或者使用

  EXPECT_NONFATAL_FAILURE(statement, substring);

如果您期望一个非致命的(例如 EXPECT_*)失败。

仅检查当前线程中的失败才能确定此类期望的结果。 如果 statement 创建新线程,则也会忽略这些线程中的失败。 如果您也想捕获其他线程中的失败,请改用以下宏之一

  EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substring);
  EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substring);

注意:目前 Windows 上不支持来自多个线程的断言。

由于技术原因,存在一些注意事项

  1. 您不能将失败消息流式传输到任何宏。

  2. EXPECT_FATAL_FAILURE{_ON_ALL_THREADS}() 中的 statement 不能引用局部非静态变量或 this 对象的非静态成员。

  3. EXPECT_FATAL_FAILURE{_ON_ALL_THREADS}() 中的 statement 不能返回值。

以编程方式注册测试

TEST 宏处理了绝大多数用例,但在少数情况下,需要运行时注册逻辑。 对于这些情况,该框架提供了 ::testing::RegisterTest,允许调用者动态注册任意测试。

这是一个高级 API,仅当 TEST 宏不足时才使用。 如果可能,应首选宏,因为它们避免了调用此函数的大部分复杂性。

它提供了以下签名

template <typename Factory>
TestInfo* RegisterTest(const char* test_suite_name, const char* test_name,
                       const char* type_param, const char* value_param,
                       const char* file, int line, Factory factory);

factory 参数是一个工厂可调用(可移动构造)对象或函数指针,用于创建 Test 对象的新实例。 它处理对调用者的所有权。 可调用对象的签名是 Fixture*(),其中 Fixture 是测试的测试 fixture 类。 使用相同的 test_suite_name 注册的所有测试必须返回相同的 fixture 类型。 这会在运行时检查。

该框架将从工厂推断 fixture 类,并为其调用 SetUpTestSuiteTearDownTestSuite

必须在调用 RUN_ALL_TESTS() 之前调用,否则行为未定义。

用例示例

class MyFixture : public testing::Test {
 public:
  // All of these optional, just like in regular macro usage.
  static void SetUpTestSuite() { ... }
  static void TearDownTestSuite() { ... }
  void SetUp() override { ... }
  void TearDown() override { ... }
};

class MyTest : public MyFixture {
 public:
  explicit MyTest(int data) : data_(data) {}
  void TestBody() override { ... }

 private:
  int data_;
};

void RegisterMyTests(const std::vector<int>& values) {
  for (int v : values) {
    testing::RegisterTest(
        "MyFixture", ("Test" + std::to_string(v)).c_str(), nullptr,
        std::to_string(v).c_str(),
        __FILE__, __LINE__,
        // Important to use the fixture type as the return type here.
        [=]() -> MyFixture* { return new MyTest(v); });
  }
}
...
int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  std::vector<int> values_to_test = LoadValuesFromConfig();
  RegisterMyTests(values_to_test);
  ...
  return RUN_ALL_TESTS();
}

获取当前测试的名称

有时,函数可能需要知道当前正在运行的测试的名称。例如,您可以使用测试 fixture 的 SetUp() 方法根据正在运行的测试来设置黄金文件名称。TestInfo 类包含此信息。

要获取当前正在运行的测试的 TestInfo 对象,请在 UnitTest 单例对象上调用 current_test_info()

  // Gets information about the currently running test.
  // Do NOT delete the returned object - it's managed by the UnitTest class.
  const testing::TestInfo* const test_info =
      testing::UnitTest::GetInstance()->current_test_info();

  printf("We are in test %s of test suite %s.\n",
         test_info->name(),
         test_info->test_suite_name());

如果没有正在运行的测试,current_test_info() 将返回空指针。特别是,您无法在 SetUpTestSuite()TearDownTestSuite()(您知道测试套件名称的位置)或从中调用的函数中找到测试套件名称。

通过处理测试事件扩展 GoogleTest

GoogleTest 提供了一个**事件侦听器 API**,使您可以接收有关测试程序进度和测试失败的通知。您可以侦听的事件包括测试程序、测试套件或测试方法的开始和结束等。您可以使用此 API 来扩充或替换标准控制台输出,替换 XML 输出,或提供完全不同的输出形式,例如 GUI 或数据库。您还可以将测试事件用作检查点来实现资源泄漏检查器,例如。

定义事件侦听器

要定义事件侦听器,您可以继承 testing::TestEventListenertesting::EmptyTestEventListener。前者是一个(抽象)接口,其中*每个纯虚方法都可以被覆盖来处理一个测试事件*(例如,当一个测试开始时,将调用 OnTestStart() 方法。)。后者提供了接口中所有方法的空实现,这样子类只需要覆盖它关心的那些方法。

当事件触发时,它的上下文会作为一个参数传递给处理函数。使用以下参数类型:

事件处理函数可以检查它接收到的参数,以查找关于事件和测试程序状态的有趣信息。

这是一个例子:

  class MinimalistPrinter : public testing::EmptyTestEventListener {
    // Called before a test starts.
    void OnTestStart(const testing::TestInfo& test_info) override {
      printf("*** Test %s.%s starting.\n",
             test_info.test_suite_name(), test_info.name());
    }

    // Called after a failed assertion or a SUCCESS().
    void OnTestPartResult(const testing::TestPartResult& test_part_result) override {
      printf("%s in %s:%d\n%s\n",
             test_part_result.failed() ? "*** Failure" : "Success",
             test_part_result.file_name(),
             test_part_result.line_number(),
             test_part_result.summary());
    }

    // Called after a test ends.
    void OnTestEnd(const testing::TestInfo& test_info) override {
      printf("*** Test %s.%s ending.\n",
             test_info.test_suite_name(), test_info.name());
    }
  };

使用事件侦听器

要使用您定义的事件侦听器,请在调用 RUN_ALL_TESTS() 之前,将它的一个实例添加到 GoogleTest 事件侦听器列表(由类 TestEventListeners 表示 - 注意名称末尾的“s”)中。

int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  // Gets hold of the event listener list.
  testing::TestEventListeners& listeners =
      testing::UnitTest::GetInstance()->listeners();
  // Adds a listener to the end.  GoogleTest takes the ownership.
  listeners.Append(new MinimalistPrinter);
  return RUN_ALL_TESTS();
}

只有一个问题:默认的测试结果打印机仍然有效,所以它的输出会与您的极简打印机的输出混合。要抑制默认打印机,只需从事件侦听器列表中释放它并删除它。您可以通过添加一行来实现:

  ...
  delete listeners.Release(listeners.default_result_printer());
  listeners.Append(new MinimalistPrinter);
  return RUN_ALL_TESTS();

现在,请坐下来享受您的测试的完全不同的输出。有关更多详细信息,请参见 sample9_unittest.cc

您可以将多个侦听器附加到列表。当触发 On*Start()OnTestPartResult() 事件时,侦听器将按照它们在列表中出现的顺序接收它(因为新的侦听器被添加到列表的末尾,默认文本打印机和默认 XML 生成器将首先接收事件)。On*End() 事件将按*相反*的顺序被侦听器接收。这允许稍后添加的侦听器的输出被较早添加的侦听器的输出所包围。

在侦听器中生成失败

在处理事件时,您可以使用引发失败的宏(EXPECT_*()ASSERT_*()FAIL() 等)。有一些限制:

  1. 您不能在 OnTestPartResult() 中生成任何失败(否则会导致 OnTestPartResult() 被递归调用)。
  2. 处理 OnTestPartResult() 的侦听器不允许生成任何失败。

当您将侦听器添加到侦听器列表时,您应该将处理 OnTestPartResult() 的侦听器放在可以生成失败的侦听器*之前*。这确保了后者生成的失败被前者归因于正确的测试。

有关引发失败的侦听器的示例,请参见 sample10_unittest.cc

运行测试程序:高级选项

GoogleTest 测试程序是普通的 Executable。一旦构建完成,您可以直接运行它们,并通过以下环境变量和/或命令行标志来影响它们的行为。为了使这些标志起作用,您的程序必须在调用 RUN_ALL_TESTS() 之前调用 ::testing::InitGoogleTest()

要查看支持的标志及其用法的列表,请使用 --help 标志运行您的测试程序。

如果一个选项既由环境变量指定,又由标志指定,则后者优先。

选择测试

列出测试名称

有时需要在运行程序之前列出程序中可用的测试,以便在需要时应用过滤器。包含标志 --gtest_list_tests 会覆盖所有其他标志,并按以下格式列出测试:

TestSuite1.
  TestName1
  TestName2
TestSuite2.
  TestName

如果提供了该标志,则不会实际运行列出的任何测试。此标志没有相应的环境变量。

运行测试的子集

默认情况下,GoogleTest 程序运行用户定义的所有测试。有时,您只想运行测试的子集(例如,用于调试或快速验证更改)。如果将 GTEST_FILTER 环境变量或 --gtest_filter 标志设置为过滤器字符串,则 GoogleTest 将仅运行其完整名称(以 TestSuiteName.TestName 的形式)与该过滤器匹配的测试。

过滤器的格式是由‘:’分隔的通配符模式列表(称为*正模式*),可以选择后跟一个‘-’和另一个‘:’分隔的模式列表(称为*负模式*)。当且仅当测试匹配任何正模式但不匹配任何负模式时,该测试才匹配过滤器。

模式可以包含 '*'(匹配任何字符串)或 '?'(匹配任何单个字符)。为了方便起见,过滤器 '*-NegativePatterns' 也可以写成 '-NegativePatterns'

例如:

在第一次失败时停止测试执行

默认情况下,GoogleTest 程序运行用户定义的所有测试。在某些情况下(例如,迭代测试开发和执行),可能需要在第一次失败时停止测试执行(以改善延迟为代价,换取完整性)。如果设置了 GTEST_FAIL_FAST 环境变量或 --gtest_fail_fast 标志,测试运行器将在找到第一个测试失败时停止执行。

临时禁用测试

如果您有一个损坏的测试,无法立即修复,您可以将 DISABLED_ 前缀添加到其名称。这将使其从执行中排除。这比注释掉代码或使用 #if 0 更好,因为禁用的测试仍然会被编译(因此不会腐烂)。

如果您需要禁用测试套件中的所有测试,您可以将 DISABLED_ 添加到每个测试名称的前面,或者将其添加到测试套件名称的前面。

例如,以下测试将不会被 GoogleTest 运行,即使它们仍然会被编译:

// Tests that Foo does Abc.
TEST(FooTest, DISABLED_DoesAbc) { ... }

class DISABLED_BarTest : public testing::Test { ... };

// Tests that Bar does Xyz.
TEST_F(DISABLED_BarTest, DoesXyz) { ... }

注意:此功能只能用于临时缓解痛苦。您仍然需要在以后修复禁用的测试。作为提醒,如果测试程序包含任何禁用的测试,GoogleTest 将打印一个横幅警告您。

提示:您可以使用 grep 轻松计算您禁用的测试的数量。这个数字可以作为改进测试质量的指标。

临时启用禁用的测试

要将禁用的测试包含在测试执行中,只需使用 --gtest_also_run_disabled_tests 标志调用测试程序,或将 GTEST_ALSO_RUN_DISABLED_TESTS 环境变量设置为除 0 以外的值。您可以将此与 --gtest_filter 标志结合使用,以进一步选择要运行的禁用的测试。

强制要求至少有一个测试用例

一个常见的程序员错误是编写一个没有链接到测试用例的测试程序。例如,当您将测试用例定义放在库中,并且该库未标记为“始终链接”时,可能会发生这种情况。

为了捕捉这类错误,请使用 --gtest_fail_if_no_test_linked 标志运行测试程序,或者将 GTEST_FAIL_IF_NO_TEST_LINKED 环境变量设置为除 0 以外的值。现在,如果未链接任何测试用例,程序将会失败。

请注意,链接任何测试用例都使得程序对于此检查而言是有效的。特别是,即使是禁用的测试用例也足够。

重复测试

偶尔你会遇到一个测试,其结果具有偶然性。也许它只有 1% 的概率会失败,这使得在调试器下重现该错误相当困难。这可能是一个主要的挫败感来源。

--gtest_repeat 标志允许你多次重复程序中的所有(或选定的)测试方法。希望一个不稳定的测试最终会失败,并给你一个调试的机会。以下是如何使用它:

$ foo_test --gtest_repeat=1000
Repeat foo_test 1000 times and don't stop at failures.

$ foo_test --gtest_repeat=-1
A negative count means repeating forever.

$ foo_test --gtest_repeat=1000 --gtest_break_on_failure
Repeat foo_test 1000 times, stopping at the first failure.  This
is especially useful when running under a debugger: when the test
fails, it will drop into the debugger and you can then inspect
variables and stacks.

$ foo_test --gtest_repeat=1000 --gtest_filter=FooBar.*
Repeat the tests whose name matches the filter 1000 times.

如果你的测试程序包含全局设置/拆卸代码,它也会在每次迭代中重复,因为不稳定可能存在于其中。要避免重复全局设置/拆卸,请指定 --gtest_recreate_environments_when_repeating=false{.nowrap}。

你还可以通过设置 GTEST_REPEAT 环境变量来指定重复计数。

打乱测试顺序

你可以指定 --gtest_shuffle 标志(或者将 GTEST_SHUFFLE 环境变量设置为 1)来以随机顺序运行程序中的测试。这有助于揭示测试之间的不良依赖关系。

默认情况下,GoogleTest 使用从当前时间计算出的随机种子。因此,你每次都会得到不同的顺序。控制台输出包含随机种子值,以便你稍后可以重现与顺序相关的测试失败。要显式指定随机种子,请使用 --gtest_random_seed=SEED 标志(或者设置 GTEST_RANDOM_SEED 环境变量),其中 SEED 是范围 [0, 99999] 中的整数。种子值 0 是特殊的:它告诉 GoogleTest 执行从当前时间计算种子的默认行为。

如果将此与 --gtest_repeat=N 结合使用,GoogleTest 将选择一个不同的随机种子,并在每次迭代中重新打乱测试顺序。

将测试函数分发到多台机器

如果你有多台机器可以用来运行测试程序,你可能想并行运行测试函数并更快地获得结果。我们将这种技术称为分片,其中每台机器称为一个分片

GoogleTest 与测试分片兼容。要利用此功能,你的测试运行器(不是 GoogleTest 的一部分)需要执行以下操作:

  1. 分配若干台机器(分片)来运行测试。
  2. 在每个分片上,将 GTEST_TOTAL_SHARDS 环境变量设置为分片的总数。对于所有分片,它必须相同。
  3. 在每个分片上,将 GTEST_SHARD_INDEX 环境变量设置为分片的索引。不同的分片必须被分配不同的索引,这些索引必须在范围 [0, GTEST_TOTAL_SHARDS - 1] 内。
  4. 在所有分片上运行相同的测试程序。当 GoogleTest 看到上述两个环境变量时,它将选择要运行的测试函数子集。在所有分片上,程序中的每个测试函数将恰好运行一次。
  5. 等待所有分片完成,然后收集并报告结果。

你的项目可能包含没有使用 GoogleTest 编写的测试,因此它们不理解此协议。为了让你的测试运行器确定哪些测试支持分片,它可以将环境变量 GTEST_SHARD_STATUS_FILE 设置为不存在的文件路径。如果测试程序支持分片,它将创建此文件以确认这一事实;否则它不会创建它。该文件的实际内容目前并不重要,尽管我们将来可能会在其中放入一些有用的信息。

这是一个使之更清晰的例子。假设你有一个测试程序 foo_test,它包含以下 5 个测试函数:

TEST(A, V)
TEST(A, W)
TEST(B, X)
TEST(B, Y)
TEST(B, Z)

假设你有 3 台机器可以使用。要并行运行测试函数,你将在所有机器上将 GTEST_TOTAL_SHARDS 设置为 3,并在机器上分别将 GTEST_SHARD_INDEX 设置为 0、1 和 2。然后你将在每台机器上运行相同的 foo_test

GoogleTest 保留更改工作在分片之间如何分配的权利,但这里有一个可能的场景:

控制测试输出

彩色终端输出

GoogleTest 可以在其终端输出中使用颜色,以使其更容易发现重要信息。

...
[----------] 1 test from FooTest
[ RUN      ] FooTest.DoesAbc
[       OK ] FooTest.DoesAbc
[----------] 2 tests from BarTest
[ RUN      ] BarTest.HasXyzProperty
[       OK ] BarTest.HasXyzProperty
[ RUN      ] BarTest.ReturnsTrueOnSuccess
... some error messages ...
[   FAILED ] BarTest.ReturnsTrueOnSuccess
...
[==========] 30 tests from 14 test suites ran.
[   PASSED ] 28 tests.
[   FAILED ] 2 tests, listed below:
[   FAILED ] BarTest.ReturnsTrueOnSuccess
[   FAILED ] AnotherTest.DoesXyz

 2 FAILED TESTS

你可以将 GTEST_COLOR 环境变量或 --gtest_color 命令行标志设置为 yesnoauto(默认值)来启用颜色、禁用颜色或让 GoogleTest 决定。当值为 auto 时,当且仅当输出转到终端并且(在非 Windows 平台上)TERM 环境变量设置为 xtermxterm-color 时,GoogleTest 将使用颜色。

抑制测试通过信息

默认情况下,GoogleTest 为每个测试打印 1 行输出,指示其是通过还是失败。要仅显示测试失败信息,请使用 --gtest_brief=1 运行测试程序,或者将 GTEST_BRIEF 环境变量设置为 1

抑制运行时间

默认情况下,GoogleTest 会打印运行每个测试所花费的时间。要禁用它,请使用 --gtest_print_time=0 命令行标志运行测试程序,或者将 GTEST_PRINT_TIME 环境变量设置为 0

抑制 UTF-8 文本输出

在断言失败的情况下,GoogleTest 会将 string 类型的期望值和实际值打印为十六进制编码的字符串,以及在包含有效的非 ASCII UTF-8 字符时,以可读的 UTF-8 文本形式打印。 如果你想抑制 UTF-8 文本,例如,你没有 UTF-8 兼容的输出介质,请使用 --gtest_print_utf8=0 运行测试程序,或者将 GTEST_PRINT_UTF8 环境变量设置为 0

生成 XML 报告

除了正常的文本输出之外,GoogleTest 还可以将详细的 XML 报告输出到文件。该报告包含每个测试的持续时间,因此可以帮助你识别慢速测试。

要生成 XML 报告,请将 GTEST_OUTPUT 环境变量或 --gtest_output 标志设置为字符串 "xml:path_to_output_file",这将在给定位置创建该文件。你也可以只使用字符串 "xml",在这种情况下,可以在当前目录的 test_detail.xml 文件中找到输出。

如果你指定一个目录(例如,Linux 上的 "xml:output/directory/" 或 Windows 上的 "xml:output\directory\"),GoogleTest 将在该目录中创建 XML 文件,并以测试可执行文件命名(例如,对于测试程序 foo_test,文件名为 foo_test.xmlfoo_test.exe)。如果该文件已存在(可能是在上次运行中遗留下来的),GoogleTest 将选择一个不同的名称(例如 foo_test_1.xml)以避免覆盖它。

该报告基于 junitreport Ant 任务。 由于该格式最初是为 Java 设计的,因此需要进行一些解释才能将其应用于 GoogleTest 测试,如下所示:

<testsuites name="AllTests" ...>
  <testsuite name="test_case_name" ...>
    <testcase    name="test_name" ...>
      <failure message="..."/>
      <failure message="..."/>
      <failure message="..."/>
    </testcase>
  </testsuite>
</testsuites>

例如,以下程序:

TEST(MathTest, Addition) { ... }
TEST(MathTest, Subtraction) { ... }
TEST(LogicTest, NonContradiction) { ... }

可以生成此报告:

<?xml version="1.0" encoding="UTF-8"?>
<testsuites tests="3" failures="1" errors="0" time="0.035" timestamp="2011-10-31T18:52:42" name="AllTests">
  <testsuite name="MathTest" tests="2" failures="1" errors="0" time="0.015">
    <testcase name="Addition" file="test.cpp" line="1" status="run" time="0.007" classname="">
      <failure message="Value of: add(1, 1)&#x0A;  Actual: 3&#x0A;Expected: 2" type="">...</failure>
      <failure message="Value of: add(1, -1)&#x0A;  Actual: 1&#x0A;Expected: 0" type="">...</failure>
    </testcase>
    <testcase name="Subtraction" file="test.cpp" line="2" status="run" time="0.005" classname="">
    </testcase>
  </testsuite>
  <testsuite name="LogicTest" tests="1" failures="0" errors="0" time="0.005">
    <testcase name="NonContradiction" file="test.cpp" line="3" status="run" time="0.005" classname="">
    </testcase>
  </testsuite>
</testsuites>

需要注意的事项:

生成 JSON 报告

除了 XML 格式之外,GoogleTest 还可以生成 JSON 报告。要生成 JSON 报告,请将 GTEST_OUTPUT 环境变量或 --gtest_output 标志设置为字符串 "json:path_to_output_file",这将在指定位置创建文件。您也可以只使用字符串 "json",在这种情况下,输出结果可以在当前目录的 test_detail.json 文件中找到。

报告格式符合以下 JSON Schema

{
  "$schema": "https://json-schema.fullstack.org.cn/schema#",
  "type": "object",
  "definitions": {
    "TestCase": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "tests": { "type": "integer" },
        "failures": { "type": "integer" },
        "disabled": { "type": "integer" },
        "time": { "type": "string" },
        "testsuite": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/TestInfo"
          }
        }
      }
    },
    "TestInfo": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "file": { "type": "string" },
        "line": { "type": "integer" },
        "status": {
          "type": "string",
          "enum": ["RUN", "NOTRUN"]
        },
        "time": { "type": "string" },
        "classname": { "type": "string" },
        "failures": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/Failure"
          }
        }
      }
    },
    "Failure": {
      "type": "object",
      "properties": {
        "failures": { "type": "string" },
        "type": { "type": "string" }
      }
    }
  },
  "properties": {
    "tests": { "type": "integer" },
    "failures": { "type": "integer" },
    "disabled": { "type": "integer" },
    "errors": { "type": "integer" },
    "timestamp": {
      "type": "string",
      "format": "date-time"
    },
    "time": { "type": "string" },
    "name": { "type": "string" },
    "testsuites": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/TestCase"
      }
    }
  }
}

该报告使用符合以下 Proto3 的格式,使用 JSON 编码

syntax = "proto3";

package googletest;

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";

message UnitTest {
  int32 tests = 1;
  int32 failures = 2;
  int32 disabled = 3;
  int32 errors = 4;
  google.protobuf.Timestamp timestamp = 5;
  google.protobuf.Duration time = 6;
  string name = 7;
  repeated TestCase testsuites = 8;
}

message TestCase {
  string name = 1;
  int32 tests = 2;
  int32 failures = 3;
  int32 disabled = 4;
  int32 errors = 5;
  google.protobuf.Duration time = 6;
  repeated TestInfo testsuite = 7;
}

message TestInfo {
  string name = 1;
  string file = 6;
  int32 line = 7;
  enum Status {
    RUN = 0;
    NOTRUN = 1;
  }
  Status status = 2;
  google.protobuf.Duration time = 3;
  string classname = 4;
  message Failure {
    string failures = 1;
    string type = 2;
  }
  repeated Failure failures = 5;
}

例如,以下程序:

TEST(MathTest, Addition) { ... }
TEST(MathTest, Subtraction) { ... }
TEST(LogicTest, NonContradiction) { ... }

可以生成此报告:

{
  "tests": 3,
  "failures": 1,
  "errors": 0,
  "time": "0.035s",
  "timestamp": "2011-10-31T18:52:42Z",
  "name": "AllTests",
  "testsuites": [
    {
      "name": "MathTest",
      "tests": 2,
      "failures": 1,
      "errors": 0,
      "time": "0.015s",
      "testsuite": [
        {
          "name": "Addition",
          "file": "test.cpp",
          "line": 1,
          "status": "RUN",
          "time": "0.007s",
          "classname": "",
          "failures": [
            {
              "message": "Value of: add(1, 1)\n  Actual: 3\nExpected: 2",
              "type": ""
            },
            {
              "message": "Value of: add(1, -1)\n  Actual: 1\nExpected: 0",
              "type": ""
            }
          ]
        },
        {
          "name": "Subtraction",
          "file": "test.cpp",
          "line": 2,
          "status": "RUN",
          "time": "0.005s",
          "classname": ""
        }
      ]
    },
    {
      "name": "LogicTest",
      "tests": 1,
      "failures": 0,
      "errors": 0,
      "time": "0.005s",
      "testsuite": [
        {
          "name": "NonContradiction",
          "file": "test.cpp",
          "line": 3,
          "status": "RUN",
          "time": "0.005s",
          "classname": ""
        }
      ]
    }
  ]
}

重要提示:JSON 文档的确切格式可能会更改。

控制如何报告失败

检测测试过早退出

Google Test 实现了 *premature-exit-file* 协议,供测试运行器捕获测试程序中任何类型的意外退出。启动时,Google Test 会创建该文件,该文件将在所有工作完成后自动删除。然后,测试运行器可以检查此文件是否存在。如果该文件仍然未被删除,则表示被检查的测试已过早退出。

只有在设置了 TEST_PREMATURE_EXIT_FILE 环境变量时,此功能才会被启用。

将断言失败转换为断点

在调试器下运行测试程序时,如果调试器能够捕获断言失败并自动进入交互模式,那将非常方便。GoogleTest 的 *break-on-failure* 模式支持此行为。

要启用它,请将 GTEST_BREAK_ON_FAILURE 环境变量设置为除 0 之外的值。或者,您可以使用 --gtest_break_on_failure 命令行标志。

禁用捕获测试抛出的异常

GoogleTest 可以在启用或不启用异常的情况下使用。如果测试抛出 C++ 异常或(在 Windows 上)结构化异常 (SEH),默认情况下,GoogleTest 会捕获它,将其报告为测试失败,并继续执行下一个测试方法。这最大限度地提高了测试运行的覆盖率。此外,在 Windows 上,未捕获的异常将导致弹出窗口,因此捕获异常允许您自动运行测试。

但是,在调试测试失败时,您可能希望异常由调试器处理,以便您可以在抛出异常时检查调用堆栈。要实现这一点,请将 GTEST_CATCH_EXCEPTIONS 环境变量设置为 0,或者在运行测试时使用 --gtest_catch_exceptions=0 标志。

Sanitizer 集成

Undefined Behavior SanitizerAddress SanitizerThread Sanitizer 都提供了可以覆盖的弱函数,以便在它们检测到 sanitizer 错误时触发显式失败,例如从 nullptr 创建引用。要覆盖这些函数,请在您编译为主二进制文件一部分的源文件中放置它们的定义。

extern "C" {
void __ubsan_on_report() {
  FAIL() << "Encountered an undefined behavior sanitizer error";
}
void __asan_on_error() {
  FAIL() << "Encountered an address sanitizer error";
}
void __tsan_on_report() {
  FAIL() << "Encountered a thread sanitizer error";
}
}  // extern "C"

在使用其中一个 sanitizer 启用的情况下编译项目后,如果某个特定测试触发了 sanitizer 错误,GoogleTest 将报告该测试失败。