Swift Hamcrest
](https://github.com/nschum/SwiftHamcrest/actions/workflows/build.yml/badge.svg) ](https://github.com/Carthage/Carthage)
Hamcrest 为您的 Swift 单元测试提供了高级匹配器和更好的错误消息。
Hamcrest 最初是用 Java 编写的,适用于许多 语言。
教程
此教程还可在 Hamcrest 的工作区中以游乐场的形式获取。
通常,您在单元测试中使用这些匹配器,其中不匹配会导致测试失败,但它们也可以在游乐场中使用,其中不匹配会简单地打印出错误消息。
在任一情况下,都需要导入 Hamcrest 模块。
import Hamcrest
操作匹配器
以下是一些非常简单的匹配器。匹配到的表达式看起来像常规布尔表达式,但提供可读的匹配错误消息,而不是通用错误。
let x = 1 + 1
// The comments show the human-readable error messages created by the assertions.
assertThat(x == 2) // ✓
assertThat(x == 3) // GOT: 2, EXPECTED: equal to 3
assertThat(x > 1) // ✓
assertThat(x > 2) // GOT: 2, EXPECTED: greater than 2
assertThat(x >= 2) // ✓
assertThat(x >= 3) // GOT: 2, EXPECTED: greater than or equal to 3
assertThat(x < 3) // ✓
assertThat(x < 2) // GOT: 2, EXPECTED: less than 2
assertThat(x <= 2) // ✓
assertThat(x <= 1) // GOT: 2, EXPECTED: less than or equal to 1
class Test {}
let o = Test()
assertThat(o === o) // ✓
assertThat(o === Test())
// GOT: __lldb_expr_8.Test (0x7f9572b020d0),
// EXPECTED: same instance as 0x7f9570c702a0
文本匹配器
所有这些匹配器也都可以作为函数使用。
assertThat(x, equalTo(2)) // ✓
assertThat(x, equalTo(3)) // GOT: 2, EXPECTED: equal to 3
assertThat(x, greaterThan(1)) // ✓
assertThat(x, greaterThan(2)) // GOT: 2, EXPECTED: greater than 2
assertThat(x, greaterThanOrEqualTo(2)) // ✓
assertThat(x, greaterThanOrEqualTo(3))
// GOT: 2, EXPECTED: greater than or equal to 3
assertThat(x, lessThan(3)) // ✓
assertThat(x, lessThan(2)) // GOT: 2, EXPECTED: less than 2
assertThat(x, lessThanOrEqualTo(2)) // ✓
assertThat(x, lessThanOrEqualTo(1))
// GOT: 2, EXPECTED: less than or equal to 1
assertThat(x, inInterval(1...2)) // ✓
assertThat(x, inInterval(1..<2)) // GOT: 2, EXPECTED: in interval 1..<2
assertThat(o, sameInstance(o)) // ✓
assertThat(o, sameInstance(Test()))
// GOT: __lldb_expr_53.Test, EXPECTED: same instance as __lldb_expr_53.Test
这里有一些更直接的匹配器
assertThat("foobarbaz", containsString("bar")) // ✓
assertThat("foobarbaz", containsString("bla"))
// GOT: "foobarbaz", EXPECTED: contains "bla"
assertThat("foobarbaz", containsStringsInOrder("f", "b", "b")) // ✓
assertThat("foobarbaz", containsStringsInOrder("foo", "baz", "bar"))
// GOT: "foobarbaz", EXPECTED: contains in order ["foo", "baz", "bar"]
assertThat("foobarbaz", hasPrefix("foo")) // ✓
assertThat("foobarbaz", hasPrefix("oo"))
// GOT: "foobarbaz", EXPECTED: has prefix "oo"
assertThat("foobarbaz", hasSuffix("baz")) // ✓
assertThat("foobarbaz", hasSuffix("ba"))
// GOT: "foobarbaz", EXPECTED: has suffix "ba"
assertThat("ac", matchesPattern("\\b(a|b)(c|d)\\b")) // ✓
assertThat("BD", matchesPattern("\\b(a|b)(c|d)\\b", options: .caseInsensitive)) // ✓
assertThat("aC", matchesPattern("\\b(a|b)(c|d)\\b"))
// "GOT: "aC", EXPECTED: matches \b(a|b)(c|d)\b"
assertThat(10.0, closeTo(10.0, 0.01)) // ✓
assertThat(10.0000001, closeTo(10, 0.01)) // ✓
assertThat(10.1, closeTo(10, 0.01))
// GOT: 10.1 (difference of 0.0999999999999996), EXPECTED: within 0.01 of 10.0
import Foundation
assertThat(CGPoint(x: 5, y: 10), hasProperty("x", closeTo(5.0, 0.00001))) // ✓
assertThat(CGPoint(x: 5, y: 10), hasProperty("y", closeTo(0.0, 0.00001)))
// GOT: (5.0,10.0) (property value 10.0 (difference of 10.0)),
// EXPECTED: has property "y" with value within 1e-05 of 0.0
组合匹配器
Hamcrest的真正实力在于将多个匹配器组合成一个断言语句。
assertThat(x, not(equalTo(3))) // ✓
assertThat(x, not(equalTo(2))) // GOT: 2, EXPECTED: not equal to 2
assertThat(x, allOf(greaterThan(1), lessThan(3))) // ✓
assertThat(x, allOf(greaterThan(2), lessThan(3)))
// GOT: 2 (mismatch: greater than 2),
// EXPECTED: all of [greater than 2, greater than 3]
assertThat(x, greaterThan(1) && lessThan(3)) // ✓
assertThat(x, greaterThan(2) && lessThan(3))
// GOT: 2 (mismatch: greater than 2),
// EXPECTED: all of [greater than 2, greater than 3]
assertThat(x, anyOf(greaterThan(2), lessThan(3))) // ✓
assertThat(x, anyOf(greaterThan(2), lessThan(2)))
// GOT: 2, EXPECTED: any of [greater than 2, greater than 2]
assertThat(x, greaterThan(2) || lessThan(3)) // ✓
assertThat(x, greaterThan(2) || lessThan(2))
// GOT: 2, EXPECTED: any of [greater than 2, greater than 2]
集合
组合匹配器对于匹配序列和字典尤其有用。
let array = ["foo", "bar"]
assertThat(array, hasCount(2)) // ✓
assertThat(array, hasCount(greaterThan(2)))
// GOT: [foo, bar] (count 2), EXPECTED: has count greater than 2
assertThat(array, everyItem(equalTo("foo")))
// GOT: [foo, bar] (mismatch: bar),
// EXPECTED: a sequence where every item equal to foo
assertThat(array, contains("foo", "bar")) // ✓
assertThat(array, contains(equalTo("foo"), equalTo("bar"))) // ✓
assertThat(array, contains(equalTo("foo")))
// GOT: [foo, bar] (unmatched item "bar"),
// EXPECTED: a sequence containing equal to foo
assertThat(array, contains(equalTo("foo"), equalTo("baz")))
// "GOT: [foo, bar] (mismatch: GOT: "bar", EXPECTED: equal to baz),
// EXPECTED: a sequence containing [equal to foo, equal to baz]"
assertThat(array, contains(equalTo("foo"), equalTo("bar"), equalTo("baz")))
// GOT: [foo, bar] (missing item equal to baz),
// EXPECTED: a sequence containing [equal to foo, equal to bar, equal to baz]
assertThat(array, containsInAnyOrder("bar", "foo")) // ✓
assertThat(array, containsInAnyOrder(equalTo("bar"), equalTo("foo"))) // ✓
assertThat(array, hasItem(equalTo("foo"))) // ✓
assertThat(array, hasItem(equalTo("baz")))
// GOT: [foo, bar], EXPECTED: a sequence containing equal to baz
assertThat(array, hasItem("foo", atIndex: 0))) // ✓
assertThat(array, hasItem("foo", atIndex: 1))) // GOT: ["foo", "bar"], EXPECTED: a sequence containing "foo" at index 1"
assertThat(array, hasItem(equalTo("foo"), atIndex: 0))) // ✓
assertThat(array, hasItem(equalTo("foo"), atIndex: 1))) // GOT: ["foo", "bar"], EXPECTED: a sequence containing "foo" at index 1"
assertThat(array, hasItems("foo", "bar")) // ✓
assertThat(array, hasItems(equalTo("foo"), equalTo("baz")))
// GOT: [foo, bar] (missing item equal to baz),
// EXPECTED: a sequence containing all of [equal to foo, equal to baz]
let dictionary = ["foo": 5, "bar": 10]
assertThat(dictionary, hasEntry("foo", 5)) // ✓
assertThat(dictionary, hasEntry(equalTo("foo"), equalTo(5))) // ✓
assertThat(dictionary, hasEntry(equalTo("foo"), equalTo(10)))
// GOT: [bar: 10, foo: 5],
// EXPECTED: a dictionary containing [equal to foo -> equal to 10]
assertThat(dictionary, hasKey("foo")) // ✓
assertThat(dictionary, hasKey(equalTo("baz")))
// GOT: [bar: 10, foo: 5],
// EXPECTED: a dictionary containing [equal to baz -> anything]
assertThat(dictionary, hasValue(10)) // ✓
assertThat(dictionary, hasValue(equalTo(15)))
// GOT: [bar: 10, foo: 5],
// EXPECTED: a dictionary containing [anything -> equal to 15]
可选类型
匹配器不需要匹配Swift中偏好非可选类型的特性。presentAnd可以将匹配器明确地应用到可选类型上。
var optional: Int = 1 + 1
assertThat(optional, present()) // ✓
assertThat(optional, nilValue()) // GOT: Optional(2), EXPECTED: nil
assertThat(optional, presentAnd(equalTo(2))) // ✓
assertThat(optional, presentAnd(equalTo(1)))
// GOT: Optional(2), EXPECTED: present and equal to 1
数据类型和转置
以下匹配器可以用于断言数据类型。类型为 Any 的引用在使用类型匹配器之前需要先进行转置。可以使用 instanceOf(and:) 结合类型验证和转置。
class TestChild: Test {}
assertThat(o, instanceOf(Test.self)) // ✓
assertThat(o, instanceOf(TestChild.self))
// GOT: __lldb_expr_60.Test, EXPECTED: instance of expected type
let any: Any = 10
assertThat(any, instanceOf(Int.self, and: equalTo(10))) // ✓
assertThat(any, instanceOf(Double.self, and: equalTo(10.0)))
// GOT: 10 (mismatched type), EXPECTED: instance of and equal to 10.0
assertThat(any, instanceOf(Int.self, and: equalTo(5)))
// GOT: 10, EXPECTED: instance of and equal to 5
自定义匹配器
创建自定义匹配器有两种方式。第一种方式是创建一个仅返回现有匹配器组合的函数。
func isOnAxis<Point>() -> Matcher<Point> {
return anyOf(hasProperty("x", closeTo(0.0, 0.00001)),
hasProperty("y", closeTo(0.0, 0.00001)))
}
assertThat(CGPoint(x: 0, y: 10), isOnAxis()) // ✓
assertThat(CGPoint(x: 5, y: 10), isOnAxis())
// GOT: (5.0,10.0),
// EXPECTED: any of [has property "x" with value within 1e-05
// of 0.0, has property "y" with value within 1e-05 of 0.0]
可以使用描述为特殊匹配器来自定义描述。
func isOnAxis2<Point>() -> Matcher<Point> {
return describedAs("a point on an axis",
anyOf(hasProperty("x", closeTo(0.0, 0.00001)),
hasProperty("y", closeTo(0.0, 0.00001))))
}
assertThat(CGPoint(x: 0, y: 10), isOnAxis2()) // ✓
assertThat(CGPoint(x: 5, y: 10), isOnAxis2())
// GOT: (5.0,10.0), EXPECTED: a point on an axis
第二种方式是从头开始创建匹配器。SwiftHamcrest特别关注使此类自定义匹配器易于编写。在许多Hamcrest实现中,通常创建一个类。在SwiftHamcrest中,只需创建一个带有自定义闭包的Matcher实例,该闭包接受一个值并返回一个布尔值。
func isEven() -> Matcher<Int> {
return Matcher("even") {$0 % 2 == 0}
}
assertThat(x, isEven()) // ✓
assertThat(3, isEven()) // GOT: 3, EXPECTED: even
虽然布尔值很方便(并且在大多数情况下足够),但在某些情况下,你可能希望获取更多关于误匹配的信息。除了布尔值之外,你也可以让闭包返回一个 MatchResult 枚举。如果误匹配不明显,这一点尤其有用。
func isDivisibleByThree() -> Matcher<Int> {
return Matcher("divisible by three") {
(value) -> MatchResult in
if value % 3 == 0 {
return .Match
} else {
return .Mismatch("remainder: \(value % 3)")
}
}
}
assertThat(342783, isDivisibleByThree()) // ✓
assertThat(489359, isDivisibleByThree())
// GOT: 489359 (remainder: 2), EXPECTED: divisible by three
错误
如果正在测试的函数可以抛出错误,Hamcrest将会报告这些错误。
private enum SampleError: Error {
case Error1
case Error2
}
private func throwingFunc() throws -> Int {
throw SampleError.Error1
}
assertThat(try throwingFunc(), equalTo(1)) // ERROR: SampleError.Error1
如果不想测试可能抛出错误的函数的结果,或者如果该函数不返回任何错误,请使用 assertNotThrows
。
private func notThrowingFunc() throws {
}
assertNotThrows(try notThrowingFunc()) // ✓
assertNotThrows(_ = try throwingFunc()) // ERROR: UNEXPECTED ERROR
如果想要验证是否抛出了错误,请使用 assertThrows
。
assertThrows(try notThrowingFunc()) // EXPECTED ERROR
assertThrows(try notThrowingFunc(), SampleError.Error2)
// EXPECTED ERROR: SampleError.Error2
assertThrows(try throwingFunc(), SampleError.Error2)
// GOT ERROR: SampleError.Error1, EXPECTED ERROR: SampleError.Error2
消息
如果匹配器没有给出有意义的消息,您可以为匹配器未匹配时显示的自定义消息添加。
assertThat(true, equalTo(false), message: "Custom error message")
// 失败:自定义错误消息 – 实际得到的:等于 true,期望:等于 false
集成
Swift 包管理器
在您的 Xcode 项目中选择'添加包依赖项'选项,并在'选择包仓库'窗口中复制此仓库的 URL。
CocoaPods
使用类似以下 Podfile 集成 SwiftHamcrest
use_frameworks!
target 'HamcrestDemoTests' do
inherit! :search_paths
pod 'SwiftHamcrest', '~> 2.2.4'
end
Carthage
在您的 Cartfile 中添加以下内容
github "nschum/SwiftHamcrest"