断言进阶

2020/2/11 单元测试code tool

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

# 明确的成功与失败

这三个断言 实际上并不测试值或表达式。相反,它们直接产生成功或失败。像实际执行测试的宏一样,您可以将自定义失败消息流式传输到它们中。

SUCCEED();
1

产生成功。这不会使整体测试成功。仅当测试在执行期间没有任何断言失败时,该测试才被视为成功。

注意:SUCCEED() 纯粹是记录性的,当前不会生成任何用户可见的输出。但是,将来我们可能会将 SUCCEED() 消息添加到 googletest 的输出中。

FAIL();
ADD_FAILURE();
ADD_FAILURE_AT("file_path", line_number);
1
2
3

FAIL() 产生致命故障,而 ADD_FAILURE()ADD_FAILURE_AT() 产生非致命故障。当控制流而不是布尔表达式决定测试的成功或失败时,这些选项很有用。例如,您可能想要编写如下内容:

switch(expression) {
  case 1:
     ... some checks ...
  case 2:
     ... some other checks ...
  default:
     FAIL() << "We shouldn't get here.";
}
1
2
3
4
5
6
7
8

注意:您只能在返回 void 的函数中使用 FAIL()。有关更多信息,请参见 Assertion Placement section

# 断言异常

这些用于验证一段代码是否抛出(或不抛出)给定类型的异常:

Fatal assertion Nonfatal assertion Verifies
ASSERT_THROW(statement, exception_type); EXPECT_THROW(statement, exception_type); statement throws an exception of the given type
ASSERT_ANY_THROW(statement); EXPECT_ANY_THROW(statement); statement throws an exception of any type
ASSERT_NO_THROW(statement); EXPECT_NO_THROW(statement); statement doesn't throw any exception

例如:

ASSERT_THROW(Foo(5), bar_exception);

EXPECT_NO_THROW({
  int n = 5;
  Bar(&n);
});
1
2
3
4
5
6

可用性:Linux,Windows,Mac。

# Predicate Assertions,以获取更好的错误消息

即使 googletest 拥有丰富的断言集,它们也永远无法,也不可能(也不是一个好主意)预测用户可能遇到的所有情况。因此,有时由于缺少更好的宏,用户必须使用 EXPECT_TRUE() 来检查复杂的表达式。这样做的问题是无法向您显示表达式各部分的值,从而很难理解出了什么问题。解决方法是,一些用户选择自己构造故障消息,并将其流式传输到 EXPECT_TRUE() 中。但是,这很尴尬,尤其是当表达式具有副作用或评估开销很高时。

googletest 为您提供了三种不同的解决方案来解决此问题:

# 使用现有的布尔函数

如果您已经具有返回 bool(或可以隐式转换为 bool 的类型)的函数或函子,则可以在 predicate assertion 中使用它来自由打印函数参数:

Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED1(pred1, val1) EXPECT_PRED1(pred1, val1) pred1(val1) is true
ASSERT_PRED2(pred2, val1, val2) EXPECT_PRED2(pred2, val1, val2) pred2(val1, val2) is true
... ... ...

在上面,prednn 元 predicate 函数或函子,其中 val1val2,... 和 valn 是其参数。如果 predicate 在应用于给定参数时返回 true,则 assertion 成功,否则失败。assertion 失败时,它将打印每个参数的值。在这两种情况下,参数均仅被评估一次。

这是一个例子。给定

// Returns true if m and n have no common divisors except 1.
bool MutuallyPrime(int m, int n) { ... }

const int a = 3;
const int b = 4;
const int c = 10;
1
2
3
4
5
6

assertion

  EXPECT_PRED2(MutuallyPrime, a, b);
1

将成功,而 assertion

  EXPECT_PRED2(MutuallyPrime, b, c);
1

将失败,并显示以下消息

MutuallyPrime(b, c) is false, where
b is 4
c is 10
1
2
3

注意:如果在使用 ASSERT_PRED*EXPECT_PRED* 时看到编译器错误"no matching function to call"("没有匹配的函数要调用"),请参见 此内容 以解决问题。

# 使用返回 AssertionResult 的函数

尽管 EXPECT_PRED*() 和友类可以快速完成工作,但是语法并不令人满意:您必须为不同的 Arities 使用不同的宏,并且感觉起来更像 Lisp,而不是 C++。::testing::AssertionResult 类解决了此问题。

AssertionResult 对象表示 assertion 的结果(无论是成功还是失败,以及相关的消息)。您可以使用以下函数之一创建 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();

}
1
2
3
4
5
6
7
8
9
10
11

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

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

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

代替:

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

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

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

而不是更模糊的

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

如果您还希望 EXPEX_FALSEASSERT_FALSE 中提供信息性消息(Google 代码库中三分之一的布尔断言是否定的),并且可以在成功情况下使 predicate 变慢,则可以提供成功消息:

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

然后将显示语句 EXPECT_FALSE(IsEven(Fib(6)))

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

# 使用 Predicate-Formatter

如果您发现 (ASSERT|EXPECT)_PRED*(ASSERT|EXPECT)_(TRUE|FALSE) 生成的默认消息不令人满意,或者 predicate 的某些参数不支持向 ostream 进行流传输,则可以使用以下 predicate-formatter 断言,以完全自定义消息的格式:

Fatal assertion Nonfatal assertion Verifies
ASSERT_PRED_FORMAT1(pred_format1, val1); EXPECT_PRED_FORMAT1(pred_format1, val1); pred_format1(val1) is successful
ASSERT_PRED_FORMAT2(pred_format2, val1, val2); EXPECT_PRED_FORMAT2(pred_format2, val1, val2); pred_format2(val1, val2) is successful
... ... ...

此宏与上一组宏之间的区别在于,(ASSERT|EXPECT)_PRED_FORMAT* 代替 predicate,而带有 predicate-formatter 程序(pred_formatn),后者是具有信号的函数或函子:

::testing::AssertionResult PredicateFormattern(const char* expr1,
                                               const char* expr2,
                                               ...
                                               const char* exprn,
                                               T1 val1,
                                               T2 val2,
                                               ...
                                               Tn valn);
1
2
3
4
5
6
7
8

其中 val1val2,... 和 valn 是 predicate 参数的值,而 expr1expr2,... 和 exprn 是它们在源代码中出现的相应表达式。类型 T1T2,... 和 Tn 可以是值类型或引用类型。例如,如果参数的类型为 Foo,则可以将其声明为 Fooconst Foo&(以适当者为准)。

例如,让我们改进与 EXPECT_PRED2() 一起使用的 MutuallyPrime() 中的失败消息:

// Returns the smallest prime common divisor of m and n,
// or 1 when m and n are mutually prime.
int SmallestPrimeCommonDivisor(int m, int n) { ... }

// A predicate-formatter for asserting that two integers are mutually prime.
::testing::AssertionResult AssertMutuallyPrime(const char* m_expr,
                                               const char* n_expr,
                                               int m,
                                               int n) {
  if (MutuallyPrime(m, n)) return ::testing::AssertionSuccess();

  return ::testing::AssertionFailure() << m_expr << " and " << n_expr
      << " (" << m << " and " << n << ") are not mutually prime, "
      << "as they have a common divisor " << SmallestPrimeCommonDivisor(m, n);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

有了这个 predicate-formatter ,我们可以使用

EXPECT_PRED_FORMAT2(AssertMutuallyPrime, b, c);
1

生成消息

b and c (4 and 10) are not mutually prime, as they have a common divisor 2.
1

您可能已经意识到,我们前面介绍的许多内置断言都是 (EXPECT|ASSERT)_PRED_FORMAT* 的特殊情况。实际上,大多数确实是使用 (EXPECT|ASSERT)_PRED_FORMAT* 定义的。

# 浮点比较

比较浮点数很棘手。由于舍入误差,两个浮点将很难完全匹配。因此,ASSERT_EQ 的直接比较通常不起作用。并且由于浮点可以具有较宽的值范围,因此没有单个固定的错误界限有效。最好以固定的相对误差范围进行比较,但由于精度损失而使值接近 0 时除外。

通常,为了使浮点比较有意义,用户需要仔细选择误差范围。如果他们不希望或不在乎,则比较"末尾单位项"(ULP)是一个很好的默认值,并且 googletest 提供了断言来做到这一点。有关 ULP 的详细信息很长。如果您想了解更多信息,请参见 此处 (opens new window)

# 浮点宏

Fatal assertion Nonfatal assertion Verifies
ASSERT_FLOAT_EQ(val1, val2); EXPECT_FLOAT_EQ(val1, val2); the two float values are almost equal
ASSERT_DOUBLE_EQ(val1, val2); EXPECT_DOUBLE_EQ(val1, val2); the two double values are almost equal

"几乎相等"是指这些值彼此在 4 个 ULP 之内。

以下断言允许您选择可接受的错误范围:

Fatal assertion Nonfatal assertion Verifies
ASSERT_NEAR(val1, val2, abs_error); EXPECT_NEAR(val1, val2, abs_error); the difference between val1 and val2 doesn't exceed the given absolute error

# 浮点 Predicate-Format 函数

一些浮点运算很有用,但并不常用。为了避免出现大量新的宏,我们将其作为可用于 predicate assertion 宏(例如 EXPECT_PRED_FORMAT2 等)的 predicate-format 函数提供。

EXPECT_PRED_FORMAT2(::testing::FloatLE, val1, val2);
EXPECT_PRED_FORMAT2(::testing::DoubleLE, val1, val2);
1
2

验证 val1 小于或几乎等于 val2。您可以将上表中的 EXPECT_PRED_FORMAT2 替换为 ASSERT_PRED_FORMAT2

# Asserting 使用 gMock 匹配器

gMock 带有一个匹配器库,用于验证传递给模拟对象的参数。gMock 匹配器基本上是知道如何描述自己的 predicate。可以在以下 assertion 宏中使用它:

Fatal assertion Nonfatal assertion Verifies
ASSERT_THAT(value, matcher); EXPECT_THAT(value, matcher); value matches matcher

例如,StartsWith(prefix) 是一个匹配器,它匹配以前缀开头的字符串,您可以编写:

using ::testing::StartsWith;
...
    // Verifies that Foo() returns a string starting with "Hello".
    EXPECT_THAT(Foo(), StartsWith("Hello"));
1
2
3
4

有关更多详细信息,请阅读 gMock 中的 此信息

gMock 具有丰富的匹配器集。您可以做很多 googletest 无法独自完成的事情。有关 gMock 提供的匹配器列表,请阅读 此内容。编写 自己的匹配器 也很容易。

gMock 与 googletest 捆绑在一起,因此您无需添加任何构建依赖项即可利用此优势。只需添加 "testing/base/public/gmock.h",即可开始使用。

# 更多的字符串断言

(如果尚未阅读,请先阅读 [上一节](./advanced.md#Asserting%20 使用%20gMock%20 匹配器))

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

如果该字符串包含格式正确的 HTML 或 XML 文档,则可以检查其 DOM 树是否与 XPath 表达式匹配:

// Currently still in //template/prototemplate/testing:xpath_matcher
#include "template/prototemplate/testing/xpath_matcher.h"
using prototemplate::testing::MatchesXPath;
EXPECT_THAT(html_string, MatchesXPath("//a[text()='click here']"));
1
2
3
4

# Windows HRESULT assertions

这些断言测试 HRESULT 是成功还是失败。

Fatal assertion Nonfatal assertion Verifies
ASSERT_HRESULT_SUCCEEDED(expression) EXPECT_HRESULT_SUCCEEDED(expression) expression is a success HRESULT
ASSERT_HRESULT_FAILED(expression) EXPECT_HRESULT_FAILED(expression) expression is a failure HRESULT

生成的输出包含 expression 返回的 HRESULT 代码相关的适合人类阅读错误消息。

您可以这样使用它们:

CComPtr<IShellDispatch2> shell;
ASSERT_HRESULT_SUCCEEDED(shell.CoCreateInstance(L"Shell.Application"));
CComVariant empty;
ASSERT_HRESULT_SUCCEEDED(shell->ShellExecute(CComBSTR(url), empty, empty, empty, empty));
1
2
3
4

# Type 断言

您可以调用该函数

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

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

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

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

代码:

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

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

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

来导致编译器错误。

# Assertion Placement

您可以在任何 C++ 函数中使用 assertion。特别是,它不一定是测试 fixture 类的方法。一个约束是,生成致命故障的断言(FAIL*ASSERT_*)只能在返回 void 的函数中使用。这是 Google 不使用异常的结果。通过将其放置在非 void 函数中,您将得到一个令人困惑的编译错误,例如:

  • "error: void value not ignored as it ought to be"("错误:void 值不应被忽略")
  • "cannot initialize return object of type 'bool' with an rvalue of type 'void'"("无法使用值为 "void" 的右值初始化类型为 "bool" 的返回对象)
  • "error: no viable conversion from 'void' to 'string'" ("错误:无法从 'void' 转换为 'string'")。

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

如果不能选择更改函数的类型,则应仅使用会产生非致命故障的 assertion,例如ADD_FAILURE*EXPECT_*

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

警告:从构造函数或析构函数调用的辅助函数(私有 void 返回方法)中的致命 assertion 不会终止当前测试,正如您的直觉所暗示的那样:您只是从构造函数或析构函数中早返回,可能会令您的对象处于部分构造或部分破坏的状态!您几乎肯定要 abort 或改用 SetUp/TearDown。

需注意

生成致命故障的断言(FAIL*ASSERT_*)只能在返回 void 的函数中使用

Last Updated: 2023-10-29T08:26:04.000Z