BlackNest - 可重用测试
像规格一样测试
我们开始一个非常简单的函数。
func asTuple(_ int: Int) -> (Int, Int) {
return (int, int * 2)
}
在为该函数编写测试之前,我们添加 spec。Spec 是一个函数,它接收 Input
和 Expected
,它返回 Output
或抛出错误(返回类型是可选的)。
在函数内部,所有的断言都是由 DSL 执行的。对于 Input
和 Expected
,你可以选择任何类型 - 实际类型、元组、可选类型。
/// This is the spec.
func doubleTuple(input: (Int), expect: (Int, Int)) throws -> (Int, Int) {
// Act: do the tuple
let subject = asTuple(input)
// Assert: check all proofs
try "First entry should still be the same"
=> subject.0 == expect.0
try "Second entry should be double the first"
=> subject.1 == expect.1
return subject
}
DSL 使得测试看起来像方程。易于阅读。现在我们执行不同的测试组合。
// Named arguments
expect(004, in:doubleTuple, is:(04, 08))
expect(008, in:doubleTuple, is:(08, 16))
expect(012, in:doubleTuple, is:(12, 24))
expect(100, in:doubleTuple, is:(100, 200))
// Named Arguments and Operators
expect(004, in: doubleTuple => (04, 08))
expect(008, in: doubleTuple => (08, 16))
expect(012, in: doubleTuple => (12, 24))
expect(100, in: doubleTuple => (100, 200))
// Operators only
expect(004 | doubleTuple => (04, 08))
expect(008 | doubleTuple => (08, 16))
expect(012 | doubleTuple => (12, 24))
expect(100 | doubleTuple => (100, 200))
如果其中一个 spec 失败,你会看到原因
测试组合
再添加一个函数。
func asSum(_ tuple: Int, Int) -> Int {
return tuple.0 + tuple.1
}
并为它添加另一个 spec。
func tupleSum(input: (Int, Int), expect: (Int)) throws -> Int {
// Act: do the sum
let subject = asSum(input)
// Assert: check the spec
try "sum calculation"
=> subject == expect
return subject
}
现在您可以 组合两者。
函数的每次调用都可以返回和接收 单独的期望。返回值将传递到下一次测试运行。相同的代码 - 没有重复 - 并且再次 - 容易记住。
///
expect(04, in:doubleTuple, is:(04, 08)).then(tupleSum, is:12)
expect(08, in:doubleTuple, is:(08, 16)).then(tupleSum, is:24)
expect(12, in:doubleTuple, is:(12, 24)).then(tupleSum, is:36)
///
expect(004 | doubleTuple => (04, 08)).then(tupleSum => 12)
expect(008 | doubleTuple => (08, 16)).then(tupleSum => 24)
expect(012 | doubleTuple => (12, 24)).then(tupleSum => 36)
///
expect(
4 | doubleTuple => (04, 08)
|~ tupleSum => (12)
|~ doubleTuple => (12, 24)
|~ tupleSum => (36)
)
///
expect(4,
in: doubleTuple ◦ tupleSum ◦ doubleTuple ◦ tupleSum,
is: (04, 08) • 12 • (12, 24) • 36
)
更多示例
我们有一个“观鸟者”角色,并且根据他的技能显示不同的显示名称。
struct BirdWatcher {
var name: String
var experience: Int?
var birdsSeen: Int?
init(_ name: String) {
self.name = name
}
var display: String {
switch (experience, birdsSeen) {
case let (y?, s?) where y > 10 && s > 100:
return name + " - The Great Master."
case let (y?, s?) where y > 5 && s > 50:
return name + " - The Master."
case let (y?, s?) where y < 1 && s < 1:
return name + " - The Bloody Rookie."
case let (y?, s?) where y < 5 && s < 5:
return name + " - The Rookie."
case let (y?, s?) where y < 1 && s > 10:
return name + " - The Talent."
default: return name
}
}
}
规范应提供关于你要测试内容的清晰画面。
/// typealias for Input
typealias BirdWatcherInput = (name: String, experience: Int?, birdsSeen: Int?)
/// typealias for Expected Data Tuple
typealias Data = (name: String, experience: Int?, birdsSeen: Int?, display: String)
/// the function that returns our breeding function.
func birdWatcher(_ input: BirdWatcherInput, expect: Data) throws {
// Act:
let subject = BirdWatcher(
name: input.name,
experience: input.experience,
birdsSeen: input.birdsSeen
)
// Assert:
try "name is correct"
=> subject.name == expect.name
try "birdsSeen is correct"
=> subject.birdsSeen == expect.birdsSeen
try "experience is correct"
=> subject.experience == expect.experience
try "display is built correctly"
=> subject.display == expect.display
}
expect(("Burt", nil, 100) | birdWatcher => ("Burt", nil, 100, "Burt"))
expect(("Burt", 20, 100) | birdWatcher => ("Burt", 20, 100, "Burt - The Master."))
expect(("Burt", 20, 0) | birdWatcher => ("Burt", 0, 100, "Burt - The Talent."))
expect(("Burt", nil, 0) | birdWatcher => ("Burt", 0, 0, "Burt - The Bloody Rookie."))
为什么选择BlackNest?
以黑窝金丝燕命名。
我们想要在测试中做的事情就是保护好珍贵的specRuns。不应该有任何裂缝。就是这样——照顾你的代码。
要求
Swift 4.2
安装
CocoaPods
pod 'BlackNest', '1.0.2'
致谢 & 许可证
Corridor 由 Symentis GmbH 拥有并维护。
所有模块均基于MIT许可证发布。有关详细信息,请参阅LICENSE。