与断言框架一起工作
虽然 testify/mock
的功能在创建 mocks
时非常有用,但 testify
最著名的还是其断言框架。在本节中,我们将探讨一些常见的断言框架,以及如何使用它们进一步简化和扩展我们的测试。
到目前为止,我们一直通过 if
语句和在 testing.T
参数上调用正确的失败方法来编写验证:
// Assert
if err != nil {
t.Fatal(err)
}
这种方法简单,但也有以下几个缺点:
-
重复性:如果测试较长或较复杂,可能需要进行多次断言。我们就必须重复这个错误断言块多次,使得测试变得冗长。
-
难以进行高级断言:我们希望在验证
mocks
时,能对整个测试过程中的验证有同样的细粒度控制。 -
与其他语言完全不同:这种方法与其他编程语言完全不同,后者通常拥有强大的
mock
和断言框架。例如,Java
的JUnit
就是一个这样的例子。
虽然 Go 标准库没有提供断言功能,但有两个流行的断言框架提供了这种功能:
-
testify 是一个开源的断言框架,提供了一个易于使用且功能强大的断言包。
assert
包提供了这类功能。你可以在 testify GitHub 页面 中阅读更多内容。 -
ginkgo 是一个开源的断言框架,提供了行为驱动开发(BDD)风格的测试编写和断言功能。你可以在 ginkgo GitHub 页面 中阅读更多内容。采用这种风格的测试允许开发人员编写类似自然语言的测试。
我们将在第 5 章《执行集成测试》中讨论 BDD 风格的测试。因此,我们将把讨论此类测试的内容留到那时。我们将继续使用 testify
框架来进行当前的探索。
使用 testify
assert
包提供了许多有用的函数,用于创建细粒度的断言。以下是一些你会经常遇到的常见断言:
-
相等断言:
assert.Equal
函数允许你检查两个对象是否相等。如果被检查的类型是指针类型,将会对引用值进行值的检查。相对的断言函数assert.NotEqual
也存在:assert.Equal(t, expected, actual) assert.NotEqual(t, expected, actual)
-
空值断言:
assert.Equal
函数不适用于nil
值。相反,应该使用assert.Nil
方法。相对的断言函数assert.NotNil
也存在:assert.Nil(t, actual) assert.NotNil(t, actual)
-
包含断言:
assert.Contains
函数验证指定的值是否包含在一个字符串、列表或映射中。相对的断言函数assert.NotContains
也存在:assert.Contains(t, collection, element) assert.NotContains(t, collection, element)
-
子集断言:
assert.Subset
函数验证指定的子集中的所有值是否包含在指定的列表中。相对的断言函数assert.NotSubset
也存在:assert.Subset(t, list, subset) assert.NotSubset(t, list, subset)
testify/require
包也提供相同的断言,但在断言失败的情况下会终止测试。如果出现致命的测试错误时,应该使用这个包。例如,我们可以用以下一行代码替换之前的 if
语句,它会在断言失败时调用 t.Fatal
:
require.Nil(t, err)
扩展
testing 包你应该使用断言框架来补充 |
断言错误
在讨论断言时,最后一个方面是如何验证错误。有时,我们不仅需要验证错误是否发生,还需要验证返回的错误消息是否正确。你应该确保在适当的时候验证错误消息。
assert.EqualError
函数验证返回的错误是否为非 nil
,并且其消息是否与提供的字符串相等。这使得验证错误消息变得非常容易。与我们之前看到的所有函数一样,require
包也提供了这个函数。
让我们看一个验证错误场景的示例:
t.Run("invalid operation", func(t *testing.T) {
// Arrange
expr := "2 % 3"
operator := "%"
operands := []float64{2.0, 3.0}
expectedErrMsg := "bad operator"
engine := mocks.NewOperationProcessor(t)
validator := mocks.NewValidationHelper(t)
parser := input.NewParser(engine, validator)
validator.On("CheckInput", operator, operands).
Return(fmt.Errorf(expectedErrMsg)).Once()
// Act
result, err := parser.ProcessExpression(expr)
// Assert
require.NotNil(t, err)
require.Nil(t, result)
assert.Contains(t, err.Error(), expr)
assert.Contains(t, err.Error(), expectedErrMsg)
validator.AssertExpectations(t)
})
该测试创建了一个变量 expectedErrMsg
,它代表了 mock
将返回的错误消息。然后,这个消息被传递给 assert.Contains
函数,用于验证它是否出现在 ProcessExpression
方法返回的错误中。
自定义错误类型
你也可以创建自己的自定义错误类型,而不仅仅依赖 Go 内建的 |
Mock
和断言框架是我们用来轻松编写测试的工具。然而,即使是最熟练的测试编写者,也会在测试设计不良的代码时遇到困难。通过 TDD 的迭代过程以及良好的软件设计原则,将会得到可测试、可维护的代码。