Test results Latest release swift 5.1 shield swift 5.2 shield swift 5.3 shield swift 5.4 shield swift dev shield Platforms: macOS, iOS, tvOS, watchOS, Linux

可匹配 (Matchable)

可匹配协议定义了一种比较两个对象、结构体或值是否相等的方法。

Equatable 协议不同,Matchable 通过在遇到不匹配时抛出错误来工作。

你可以将其视为一种相等性断言。 因此,主要方法被命名为 assertMatches

这使得代码更加紧凑,因为您不需要为每个失败的比较编写显式的返回语句。

它还允许协议智能地处理复合结构。

如果结构体的匹配检查在其某个成员上失败,可匹配代码将包装该成员抛出的错误,并从结构体中抛出另一个错误。

任何捕获代码都可以深入研究这些复合错误,以清晰地报告不匹配的确切位置。

用法

您可以使用以下代码检查两个值是否匹配:

try x.assertMatches(y)

可以轻松地执行一系列检查 – 第一次失败将抛出错误,导致跳过剩余的检查。

try int1.assertMatches(int2)
try double1.assertMatches(double2)
try string1.assertMatches(string2)

类型可以通过遵循 Matchable 协议并定义 assertMatches 方法来实现匹配。 在此方法中,它可以执行必要的检查。

如果发现失败,它可以抛出 MatchFailedError 来报告不匹配。

assertMatches 的实现为大多数原始类型和一些 Foundation 类型提供(我目前只做了我需要的 - 非常欢迎提交 PR)。

复合类型

虽然您可以匹配原始值类型,但该协议在执行复合类型(结构体、对象等)的成员比较时才能发挥其真正作用。

在这种情况下,类型可以遵循 MatchableCompound 协议,并定义 assertContentMatches 方法。

这与基本的 assertMatches 方法的工作方式相同,但如果在此方法中检查抛出错误,则该错误将包装在一个外部错误中,报告整个结构体匹配失败。

Keypaths (键路径)

为了方便起见,我们还定义了一种 assertMatches 形式,它接受键路径或键路径列表,并依次对两个对象的每个路径调用 assertMatches

这有助于将样板代码量降至最低。

这是一个结合了键路径和 MatchableCompound 的示例。 这测试了一个具有 13 个属性的结构体的可匹配性,并且设法以最少的样板代码来实现。

extension Task: MatchableCompound {
    public func assertContentMatches(_ other: Task, in context: MatchableContext) throws {
        try assertMatches([\.state], of: other)
        try assertMatches([\.name, \.icon, \.details], of: other)
        try assertMatches([\.started], of: other)
        try assertMatches([\.hasDescription, \.hasDuration, \.isScheduled], of: other)
        try assertMatches([\.duration], of: other)
        try assertMatches([\.scheduledHour, \.scheduledMinute], of: other)
        try assertMatches([\.streaks], of: other)
        try assertMatches([\.restDays], of: other)
    }
}

请注意,目前如果您传递一个键列表,它们都必须解析为相同类型的成员。 不幸的是,这在某种程度上降低了此方法的实用性。

单元测试

此协议最初的动机用例是单元测试,在单元测试中,通常需要比较某事物的两个实例,并且能够识别确切的发散点很有用。

虽然我仍然认为这是该协议的主要用途,但我已将其拆分为一个独立的包,因为它可能在其他地方也有用。

MatchableEquatable 不同是单元测试的一个优势,因为它允许两者共存。

在您的代码中,您可以定义 Equatable 仅检查结构体的一部分(例如,唯一标识符)。

这对于生产代码中的效率来说是好的,但对于测试代码来说没有用,因为在测试代码中您确实想知道所有成员是否相等。

在这种情况下,您可以使用 Matchable 定义一个彻底的检查,并将其用于单元测试,而不会干扰 Equatable 的高效实现。

最初,此协议被定义为我的 XCTestExtensions 包的一部分。

该包包含对 XCTAssert 的一些补充,这些补充使用 Matchable 来让您执行匹配检查

XCTAssert(savedModel, matches: reloadedModel)

此断言方法捕获任何错误,并通过调用 XCTFail 以一种友好的方式呈现它们,从而识别确切的失败点。

由于复合结构的匹配失败错误被包装的方式,该方法可以在所有级别的失败中调用 XCTFail,这导致 Xcode 在所有级别显示错误标记。

这在跟踪深层嵌套结构中的不匹配时很有用。

未来

这是一个早期实现,基于从其他地方提取的代码。

API 可能需要调整,并且这些方法肯定需要文档化。

我还打算探索使用 Swift 的内省来自动为结构体/类生成 assertMatches 的想法。

理论上,这应该可以很好地工作,但它可能会遇到一些问题。

欢迎所有反馈、建议、PR 和错误报告!