可匹配协议定义了一种比较两个对象、结构体或值是否相等的方法。
与 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
方法的工作方式相同,但如果在此方法中检查抛出错误,则该错误将包装在一个外部错误中,报告整个结构体匹配失败。
为了方便起见,我们还定义了一种 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)
}
}
请注意,目前如果您传递一个键列表,它们都必须解析为相同类型的成员。 不幸的是,这在某种程度上降低了此方法的实用性。
此协议最初的动机用例是单元测试,在单元测试中,通常需要比较某事物的两个实例,并且能够识别确切的发散点很有用。
虽然我仍然认为这是该协议的主要用途,但我已将其拆分为一个独立的包,因为它可能在其他地方也有用。
Matchable
与 Equatable
不同是单元测试的一个优势,因为它允许两者共存。
在您的代码中,您可以定义 Equatable
仅检查结构体的一部分(例如,唯一标识符)。
这对于生产代码中的效率来说是好的,但对于测试代码来说没有用,因为在测试代码中您确实想知道所有成员是否相等。
在这种情况下,您可以使用 Matchable
定义一个彻底的检查,并将其用于单元测试,而不会干扰 Equatable
的高效实现。
最初,此协议被定义为我的 XCTestExtensions 包的一部分。
该包包含对 XCTAssert
的一些补充,这些补充使用 Matchable
来让您执行匹配检查
XCTAssert(savedModel, matches: reloadedModel)
此断言方法捕获任何错误,并通过调用 XCTFail
以一种友好的方式呈现它们,从而识别确切的失败点。
由于复合结构的匹配失败错误被包装的方式,该方法可以在所有级别的失败中调用 XCTFail
,这导致 Xcode 在所有级别显示错误标记。
这在跟踪深层嵌套结构中的不匹配时很有用。
这是一个早期实现,基于从其他地方提取的代码。
API 可能需要调整,并且这些方法肯定需要文档化。
我还打算探索使用 Swift 的内省来自动为结构体/类生成 assertMatches
的想法。
理论上,这应该可以很好地工作,但它可能会遇到一些问题。
欢迎所有反馈、建议、PR 和错误报告!