测试已测试 | ✗ |
语言语言 | SwiftSwift |
许可证 | MIT |
发布最后发布 | 2017年8月 |
SwiftSwift 版本 | 3.0 |
SPM支持 SPM | ✗ |
由 Undead1116 维护。
使用 Nimble 表达 Swift 或 Objective-C 表达式的预期结果。受 Cedar 启发。
// Swift
expect(1 + 1).to(equal(2))
expect(1.2).to(beCloseTo(1.1, within: 0.1))
expect(3) > 2
expect("seahorse").to(contain("sea"))
expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
expect(ocean.isClean).toEventually(beTruthy())
目录 由 DocToc 生成
Apple 的 Xcode 包含 XCTest 框架,它提供了断言宏来测试代码行为是否正常。例如,要断言 1 + 1 = 2
,XCTest 会让您编写
// Swift
XCTAssertEqual(1 + 1, 2, "expected one plus one to equal two")
或者,在 Objective-C 中
// Objective-C
XCTAssertEqual(1 + 1, 2, @"expected one plus one to equal two");
XCTest 断言有几个缺点
Nimble 解决了这些问题。
expect(...).to
表达期望Nimble 允许你使用一种自然、易于理解的语言来表达期望
// Swift
import Nimble
expect(seagull.squawk).to(equal("Squee!"))
// Objective-C
@import Nimble;
expect(seagull.squawk).to(equal(@"Squee!"));
expect
函数可以自动补全包含file:
和line:
,但这些参数是可选的。使用默认值,当期望未满足时,Xcode 会高亮正确的行。
要执行相反的期望——断言某个值不等于——可以使用 toNot
或 notTo
// Swift
import Nimble
expect(seagull.squawk).toNot(equal("Oh, hello there!"))
expect(seagull.squawk).notTo(equal("Oh, hello there!"))
// Objective-C
@import Nimble;
expect(seagull.squawk).toNot(equal(@"Oh, hello there!"));
expect(seagull.squawk).notTo(equal(@"Oh, hello there!"));
你希望在测试的失败消息中添加更多信息吗?使用可选参数 description
添加你自己的文本
// Swift
expect(1 + 1).to(equal(3))
// failed - expected to equal <3>, got <2>
expect(1 + 1).to(equal(3), description: "Make sure libKindergartenMath is loaded")
// failed - Make sure libKindergartenMath is loaded
// expected to equal <3>, got <2>
或者在 Objective-C 中使用 *WithDescription 版本
// Objective-C
@import Nimble;
expect(@(1+1)).to(equal(@3));
// failed - expected to equal <3.0000>, got <2.0000>
expect(@(1+1)).toWithDescription(equal(@3), @"Make sure libKindergartenMath is loaded");
// failed - Make sure libKindergartenMath is loaded
// expected to equal <3.0000>, got <2.0000>
Nimble 确保你不会比较不匹配的两个类型
// Swift
// Does not compile:
expect(1 + 1).to(equal("Squee!"))
Nimble 使用泛型——仅限于 Swift——来确保类型正确。这意味着当在 Objective-C 中使用 Nimble 时,类型检查不可用。
😭
厌倦了如此多的输入?在 Nimble 中,你可以使用重载的运算符,如 ==
用于等价,或 >
用于比较
// Swift
// Passes if squawk does not equal "Hi!":
expect(seagull.squawk) != "Hi!"
// Passes if 10 is greater than 2:
expect(10) > 2
运算符重载仅限于 Swift,所以你无法在 Objective-C 中使用这种语法。
💔
expect
函数不会在给定时间时评估其给定的值,而是在需要匹配时才进行评估。所以 Nimble 可以测试表达式在被评估后是否抛出异常
// Swift
// Note: Swift currently doesn't have exceptions.
// Only Objective-C code can raise exceptions
// that Nimble will catch.
// (see https://github.com/Quick/Nimble/issues/220#issuecomment-172667064)
let exception = NSException(
name: NSInternalInconsistencyException,
reason: "Not enough fish in the sea.",
userInfo: ["something": "is fishy"])
expect { exception.raise() }.to(raiseException())
// Also, you can customize raiseException to be more specific
expect { exception.raise() }.to(raiseException(named: NSInternalInconsistencyException))
expect { exception.raise() }.to(raiseException(
named: NSInternalInconsistencyException,
reason: "Not enough fish in the sea"))
expect { exception.raise() }.to(raiseException(
named: NSInternalInconsistencyException,
reason: "Not enough fish in the sea",
userInfo: ["something": "is fishy"]))
Objective-C 以同样的方式工作,但在对一个没有返回值的表达式做出期望时,你必须使用 expectAction
宏
// Objective-C
NSException *exception = [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"Not enough fish in the sea."
userInfo:nil];
expectAction(^{ [exception raise]; }).to(raiseException());
// Use the property-block syntax to be more specific.
expectAction(^{ [exception raise]; }).to(raiseException().named(NSInternalInconsistencyException));
expectAction(^{ [exception raise]; }).to(raiseException().
named(NSInternalInconsistencyException).
reason("Not enough fish in the sea"));
expectAction(^{ [exception raise]; }).to(raiseException().
named(NSInternalInconsistencyException).
reason("Not enough fish in the sea").
userInfo(@{@"something": @"is fishy"}));
// You can also pass a block for custom matching of the raised exception
expectAction(exception.raise()).to(raiseException().satisfyingBlock(^(NSException *exception) {
expect(exception.name).to(beginWith(NSInternalInconsistencyException));
}));
一些测试框架使测试原生 C 值变得困难。在 Nimble 中,这只是一个简单的事情
// Swift
let actual: CInt = 1
let expectedValue: CInt = 1
expect(actual).to(equal(expectedValue))
实际上,Nimble 使用类型推断,因此你可以写出上面的内容,而不需要显式指定两种类型
// Swift
expect(1 as CInt).to(equal(1))
在 Objective-C 中,Nimble 只支持 Objective-C 对象。要对原生 C 值做出期望,请将它们包裹在对象字面量中
expect(@(1 + 1)).to(equal(@2));
在 Nimble 中,只需使用 toEventually
或 toEventuallyNot
,就可以很容易地在更新的异步值上做出期望。
// Swift 3.0 and later
DispatchQueue.main.async {
ocean.add("dolphins")
ocean.add("whales")
}
expect(ocean).toEventually(contain("dolphins", "whales"))
// Swift 2.3 and earlier
dispatch_async(dispatch_get_main_queue()) {
ocean.add("dolphins")
ocean.add("whales")
}
expect(ocean).toEventually(contain("dolphins", "whales"))
// Objective-C
dispatch_async(dispatch_get_main_queue(), ^{
[ocean add:@"dolphins"];
[ocean add:@"whales"];
});
expect(ocean).toEventually(contain(@"dolphins", @"whales"));
注意:toEventually 在主线程上触发轮询。阻塞主线程会导致 Nimble 停止运行循环。这可能导致在主线程上运行的任何不完整代码的测试污染。阻塞主线程可能是由于阻塞 IO、对 sleep() 的调用、死锁和同步 IPC。
在上面的例子中,ocean
持续被重新评估。如果它包含海豚和鲸鱼,则期望通过。即使 ocean
被连续重新评估整整一秒钟,它仍然不包含这些动物,期望也会失败。
有时值的更新需要超过一秒钟。在这些情况下,请使用 timeout
参数。
// Swift
// Waits three seconds for ocean to contain "starfish":
expect(ocean).toEventually(contain("starfish"), timeout: 3)
// Evaluate someValue every 0.2 seconds repeatedly until it equals 100, or fails if it timeouts after 5.5 seconds.
expect(someValue).toEventually(equal(100), timeout: 5.5, pollInterval: 0.2)
// Objective-C
// Waits three seconds for ocean to contain "starfish":
expect(ocean).withTimeout(3).toEventually(contain(@"starfish"));
你还可以通过使用 waitUntil
函数提供回调。
// Swift
waitUntil { done in
// do some stuff that takes a while...
NSThread.sleepForTimeInterval(0.5)
done()
}
// Objective-C
waitUntil(^(void (^done)(void)){
// do some stuff that takes a while...
[NSThread sleepForTimeInterval:0.5];
done();
});
waitUntil
还可以可选地接受超时参数
// Swift
waitUntil(timeout: 10) { done in
// do some stuff that takes a while...
NSThread.sleepForTimeInterval(1)
done()
}
// Objective-C
waitUntilTimeout(10, ^(void (^done)(void)){
// do some stuff that takes a while...
[NSThread sleepForTimeInterval:1];
done();
});
注意:waitUntil在主线程上触发超时代码。阻止主线程将导致Nimble停止运行循环以继续。这可能会造成主线程上未完成代码的测试污染。阻止主线程可能由阻塞I/O、调用sleep()、死锁和同步IPC引起。
在某些情况下(例如在较慢的机器上运行时),修改默认超时和轮询间隔值可能会有用。可以通过以下方式完成
// Swift
// Increase the global timeout to 5 seconds:
Nimble.AsyncDefaults.Timeout = 5
// Slow the polling interval to 0.1 seconds:
Nimble.AsyncDefaults.PollInterval = 0.1
Nimble完全支持Objective-C。但是,在使用Objective-C中的Nimble时,有两个需要注意的事项
传递给expect
函数的所有参数,以及像equal
这样的匹配器函数,必须是Objective-C对象,或者可以转换为NSObject
等价物。
// Objective-C
@import Nimble;
expect(@(1 + 1)).to(equal(@2));
expect(@"Hello world").to(contain(@"world"));
// Boxed as NSNumber *
expect(2).to(equal(2));
expect(1.2).to(beLessThan(2.0));
expect(true).to(beTruthy());
// Boxed as NSString *
expect("Hello world").to(equal("Hello world"));
// Boxed as NSRange
expect(NSMakeRange(1, 10)).to(equal(NSMakeRange(1, 10)));
要在一个不返回值的表达式上设置期望(例如-[NSException raise]
),请使用expectAction
而不是expect
。
// Objective-C
expectAction(^{ [exception raise]; }).to(raiseException());
以下类型目前被转换为NSObject
类型
NSNumber *
。NSRange
转换为NSValue *
。char *
转换为NSString *
。对于以下匹配器
equal
beGreaterThan
beGreaterThanOrEqual
beLessThan
beLessThanOrEqual
beCloseTo
beTrue
beFalse
beTruthy
beFalsy
haveCount
如果您想了解更多信息,请提交问题。
Nimble为使用expect
函数表达期望提供了一种简写方式。要在Objective-C中禁用此简写,请在其代码中的某个位置定义NIMBLE_DISABLE_SHORT_SYNTAX
宏,然后再导入Nimble。
#define NIMBLE_DISABLE_SHORT_SYNTAX 1
@import Nimble;
NMB_expect(^{ return seagull.squawk; }, __FILE__, __LINE__).to(NMB_equal(@"Squee!"));
如果您正在测试与Nimble函数(如
expect
或equal
)冲突的函数名称,则禁用简写是有用的。如果不是这种情况,禁用简写没有意义。
Nimble包括大量匹配器函数。
Nimble支持检查任何类型的对象是否属于某个类型,无论该对象是否遵循Objective-C。
// Swift
protocol SomeProtocol{}
class SomeClassConformingToProtocol: SomeProtocol{}
struct SomeStructConformingToProtocol: SomeProtocol{}
// The following tests pass
expect(1).to(beAKindOf(Int.self))
expect("turtle").to(beAKindOf(String.self))
let classObject = SomeClassConformingToProtocol()
expect(classObject).to(beAKindOf(SomeProtocol.self))
expect(classObject).to(beAKindOf(SomeClassConformingToProtocol.self))
expect(classObject).toNot(beAKindOf(SomeStructConformingToProtocol.self))
let structObject = SomeStructConformingToProtocol()
expect(structObject).to(beAKindOf(SomeProtocol.self))
expect(structObject).to(beAKindOf(SomeStructConformingToProtocol.self))
expect(structObject).toNot(beAKindOf(SomeClassConformingToProtocol.self))
// Objective-C
// The following tests pass
NSMutableArray *array = [NSMutableArray array];
expect(array).to(beAKindOf([NSArray class]));
expect(@1).toNot(beAKindOf([NSNull class]));
对象可以使用beAnInstanceOf
匹配器来测试其确切的类型。
// Swift
protocol SomeProtocol{}
class SomeClassConformingToProtocol: SomeProtocol{}
struct SomeStructConformingToProtocol: SomeProtocol{}
// Unlike the 'beKindOf' matcher, the 'beAnInstanceOf' matcher only
// passes if the object is the EXACT type requested. The following
// tests pass -- note its behavior when working in an inheritance hierarchy.
expect(1).to(beAnInstanceOf(Int.self))
expect("turtle").to(beAnInstanceOf(String.self))
let classObject = SomeClassConformingToProtocol()
expect(classObject).toNot(beAnInstanceOf(SomeProtocol.self))
expect(classObject).to(beAnInstanceOf(SomeClassConformingToProtocol.self))
expect(classObject).toNot(beAnInstanceOf(SomeStructConformingToProtocol.self))
let structObject = SomeStructConformingToProtocol()
expect(structObject).toNot(beAnInstanceOf(SomeProtocol.self))
expect(structObject).to(beAnInstanceOf(SomeStructConformingToProtocol.self))
expect(structObject).toNot(beAnInstanceOf(SomeClassConformingToProtocol.self))
// Swift
// Passes if 'actual' is equivalent to 'expected':
expect(actual).to(equal(expected))
expect(actual) == expected
// Passes if 'actual' is not equivalent to 'expected':
expect(actual).toNot(equal(expected))
expect(actual) != expected
// Objective-C
// Passes if 'actual' is equivalent to 'expected':
expect(actual).to(equal(expected))
// Passes if 'actual' is not equivalent to 'expected':
expect(actual).toNot(equal(expected))
值必须是Equatable
、Comparable
或NSObject
的子类。equal
在用于比较一个或多个nil
值时总是失败。
// Swift
// Passes if 'actual' has the same pointer address as 'expected':
expect(actual).to(beIdenticalTo(expected))
expect(actual) === expected
// Passes if 'actual' does not have the same pointer address as 'expected':
expect(actual).toNot(beIdenticalTo(expected))
expect(actual) !== expected
重要的是要记住,只有当比较具有引用语义的类型时,beIdenticalTo
才有意义,这些类型具有身份概念。在Swift中,这意味着定义为class
的类型。
此匹配器在比较具有值语义的类型时不会工作,例如定义为struct
或enum
的类型。如果您需要比较两个值类型,请考虑您的类型实例相同的含义。这可能意味着比较单个属性,如果这样有意义,则可以将您的类型符合Equatable
并使用Nimble的等价匹配器。
// Objective-C
// Passes if 'actual' has the same pointer address as 'expected':
expect(actual).to(beIdenticalTo(expected));
// Passes if 'actual' does not have the same pointer address as 'expected':
expect(actual).toNot(beIdenticalTo(expected));
// Swift
expect(actual).to(beLessThan(expected))
expect(actual) < expected
expect(actual).to(beLessThanOrEqualTo(expected))
expect(actual) <= expected
expect(actual).to(beGreaterThan(expected))
expect(actual) > expected
expect(actual).to(beGreaterThanOrEqualTo(expected))
expect(actual) >= expected
// Objective-C
expect(actual).to(beLessThan(expected));
expect(actual).to(beLessThanOrEqualTo(expected));
expect(actual).to(beGreaterThan(expected));
expect(actual).to(beGreaterThanOrEqualTo(expected));
提供的比较匹配器的值必须实现
Comparable
。
由于计算机如何表示浮点数,有时确保两个浮点数相等的主张会失败。要表达两个数应该在一定的误差范围内相互接近,请使用 beCloseTo
// Swift
expect(actual).to(beCloseTo(expected, within: delta))
// Objective-C
expect(actual).to(beCloseTo(expected).within(delta));
例如,要断言 10.01
接近于 10
,可以编写
// Swift
expect(10.01).to(beCloseTo(10, within: 0.1))
// Objective-C
expect(@(10.01)).to(beCloseTo(@10).within(0.1));
Swift 还提供了一种快捷操作符
// Swift
expect(actual) ≈ expected
expect(actual) ≈ (expected, delta)
(在美式键盘上按 Option-x 可获得 ≈)
前一个版本使用默认的 delta 值为 0.0001。这里是另一种做法
// Swift
expect(actual) ≈ expected ± delta
expect(actual) == expected ± delta
(在美式键盘上按 Option-Shift-= 可获得 ±)
如果比较的是浮点数的数组,下面的功能对你会很实用
// Swift
expect([0.0, 2.0]) ≈ [0.0001, 2.0001]
expect([0.0, 2.0]).to(beCloseTo([0.1, 2.1], within: 0.1))
传递给
beCloseTo
匹配器的值必须可以转换为Double
。
// Swift
// Passes if 'instance' is an instance of 'aClass':
expect(instance).to(beAnInstanceOf(aClass))
// Passes if 'instance' is an instance of 'aClass' or any of its subclasses:
expect(instance).to(beAKindOf(aClass))
// Objective-C
// Passes if 'instance' is an instance of 'aClass':
expect(instance).to(beAnInstanceOf(aClass));
// Passes if 'instance' is an instance of 'aClass' or any of its subclasses:
expect(instance).to(beAKindOf(aClass));
实例必须是 Objective-C 对象:是
NSObject
的子类,或者使用@objc
前缀桥接到 Objective-C 的 Swift 对象。
例如,要断言 dolphin
是一种 Mammal
// Swift
expect(dolphin).to(beAKindOf(Mammal))
// Objective-C
expect(dolphin).to(beAKindOf([Mammal class]));
beAnInstanceOf
使用了-[NSObject isMemberOfClass:]
方法来测试成员资格。beAKindOf
使用-[NSObject isKindOfClass:]
。
// Passes if 'actual' is not nil, true, or an object with a boolean value of true:
expect(actual).to(beTruthy())
// Passes if 'actual' is only true (not nil or an object conforming to Boolean true):
expect(actual).to(beTrue())
// Passes if 'actual' is nil, false, or an object with a boolean value of false:
expect(actual).to(beFalsy())
// Passes if 'actual' is only false (not nil or an object conforming to Boolean false):
expect(actual).to(beFalse())
// Passes if 'actual' is nil:
expect(actual).to(beNil())
// Objective-C
// Passes if 'actual' is not nil, true, or an object with a boolean value of true:
expect(actual).to(beTruthy());
// Passes if 'actual' is only true (not nil or an object conforming to Boolean true):
expect(actual).to(beTrue());
// Passes if 'actual' is nil, false, or an object with a boolean value of false:
expect(actual).to(beFalsy());
// Passes if 'actual' is only false (not nil or an object conforming to Boolean false):
expect(actual).to(beFalse());
// Passes if 'actual' is nil:
expect(actual).to(beNil());
如果你正在使用 Swift,可以使用 throwAssertion
匹配器来检查是否抛出了断言(例如 fatalError()
)。这是由 @mattgallagher 的 CwlPreconditionTesting 库实现的。
// Swift
// Passes if 'somethingThatThrows()' throws an assertion,
// such as by calling 'fatalError()' or if a precondition fails:
expect { try somethingThatThrows() }.to(throwAssertion())
expect { () -> Void in fatalError() }.to(throwAssertion())
expect { precondition(false) }.to(throwAssertion())
// Passes if throwing an NSError is not equal to throwing an assertion:
expect { throw NSError(domain: "test", code: 0, userInfo: nil) }.toNot(throwAssertion())
// Passes if the code after the precondition check is not run:
var reachedPoint1 = false
var reachedPoint2 = false
expect {
reachedPoint1 = true
precondition(false, "condition message")
reachedPoint2 = true
}.to(throwAssertion())
expect(reachedPoint1) == true
expect(reachedPoint2) == false
注意
x86_64
二进制文件,这意味着 您不能在 iOS 设备上运行此匹配器,只能运行模拟器。Debug executable
方案设置。如果您正在使用 Swift 2.0 或更高版本,可以使用 throwError
匹配器来检查是否抛出了错误。
注意:以下代码示例引用了 Swift.Error
协议。在 Swift 3.0 之前的版本中,这是 Swift.ErrorProtocol
。
// Swift
// Passes if 'somethingThatThrows()' throws an 'Error':
expect { try somethingThatThrows() }.to(throwError())
// Passes if 'somethingThatThrows()' throws an error within a particular domain:
expect { try somethingThatThrows() }.to(throwError { (error: Error) in
expect(error._domain).to(equal(NSCocoaErrorDomain))
})
// Passes if 'somethingThatThrows()' throws a particular error enum case:
expect { try somethingThatThrows() }.to(throwError(NSCocoaError.PropertyListReadCorruptError))
// Passes if 'somethingThatThrows()' throws an error of a particular type:
expect { try somethingThatThrows() }.to(throwError(errorType: NimbleError.self))
直接与 Error
值一起工作时,使用 matchError
匹配器允许您在不显式转换错误的情况下对错误本身执行某些检查。
matchError
匹配器允许您检查错误
这在使用 Result
或 Promise
类型时很有用。
// Swift
let actual: Error = ...
// Passes if 'actual' represents any error value from the NimbleErrorEnum type:
expect(actual).to(matchError(NimbleErrorEnum.self))
// Passes if 'actual' represents the case 'timeout' from the NimbleErrorEnum type:
expect(actual).to(matchError(NimbleErrorEnum.timeout))
// Passes if 'actual' contains an NSError equal to the one provided:
expect(actual).to(matchError(NSError(domain: "err", code: 123, userInfo: nil)))
注意:此功能仅适用于 Swift。
// Swift
// Passes if 'actual', when evaluated, raises an exception:
expect(actual).to(raiseException())
// Passes if 'actual' raises an exception with the given name:
expect(actual).to(raiseException(named: name))
// Passes if 'actual' raises an exception with the given name and reason:
expect(actual).to(raiseException(named: name, reason: reason))
// Passes if 'actual' raises an exception which passes expectations defined in the given closure:
// (in this case, if the exception's name begins with "a r")
expect { exception.raise() }.to(raiseException { (exception: NSException) in
expect(exception.name).to(beginWith("a r"))
})
// Objective-C
// Passes if 'actual', when evaluated, raises an exception:
expect(actual).to(raiseException())
// Passes if 'actual' raises an exception with the given name
expect(actual).to(raiseException().named(name))
// Passes if 'actual' raises an exception with the given name and reason:
expect(actual).to(raiseException().named(name).reason(reason))
// Passes if 'actual' raises an exception and it passes expectations defined in the given block:
// (in this case, if name begins with "a r")
expect(actual).to(raiseException().satisfyingBlock(^(NSException *exception) {
expect(exception.name).to(beginWith(@"a r"));
}));
注意:Swift 目前没有异常(请参阅 #220)。只有 Objective-C 代码可以引发 Nimble 捕获的异常。
// Swift
// Passes if all of the expected values are members of 'actual':
expect(actual).to(contain(expected...))
// Passes if 'actual' is empty (i.e. it contains no elements):
expect(actual).to(beEmpty())
// Objective-C
// Passes if expected is a member of 'actual':
expect(actual).to(contain(expected));
// Passes if 'actual' is empty (i.e. it contains no elements):
expect(actual).to(beEmpty());
在 Swift 中
contain
可以接受任意数量的参数。如果所有这些参数都是集合的成员,则预期通过。在 Objective-C 中,目前contain
只接受一个参数 (暂时如此)。
例如,要断言海洋生物名称列表包含“dolphin”和“starfish”
// Swift
expect(["whale", "dolphin", "starfish"]).to(contain("dolphin", "starfish"))
// Objective-C
expect(@[@"whale", @"dolphin", @"starfish"]).to(contain(@"dolphin"));
expect(@[@"whale", @"dolphin", @"starfish"]).to(contain(@"starfish"));
contain
和beEmpty
期望集合是NSArray
、NSSet
或由Equatable
元素组成的 Swift 集合的实例。
要测试一组元素是否在有序集合的开头或结尾,请使用 beginWith
和 endWith
// Swift
// Passes if the elements in expected appear at the beginning of 'actual':
expect(actual).to(beginWith(expected...))
// Passes if the the elements in expected come at the end of 'actual':
expect(actual).to(endWith(expected...))
// Objective-C
// Passes if the elements in expected appear at the beginning of 'actual':
expect(actual).to(beginWith(expected));
// Passes if the the elements in expected come at the end of 'actual':
expect(actual).to(endWith(expected));
beginWith
和endWith
期望集合是NSArray
或由Equatable
元素组成的有序 Swift 集合的实例。
与 contain
类似,目前 Objective-C 中的 beginWith
和 endWith
仅支持单个参数 (见此处)。
对于返回没有严格排序的复杂对象集合的代码,可以使用 containElementSatisfying
匹配器
// Swift
struct Turtle {
let color: String
}
let turtles: [Turtle] = functionThatReturnsSomeTurtlesInAnyOrder()
// This set of matchers passes regardless of whether the array is
// [{color: "blue"}, {color: "green"}] or [{color: "green"}, {color: "blue"}]:
expect(turtles).to(containElementSatisfying({ turtle in
return turtle.color == "green"
}))
expect(turtles).to(containElementSatisfying({ turtle in
return turtle.color == "blue"
}, "that is a turtle with color 'blue'"))
// The second matcher will incorporate the provided string in the error message
// should it fail
// Objective-C
@interface Turtle : NSObject
@property (nonatomic, readonly, nonnull) NSString *color;
@end
@implementation Turtle
@end
NSArray<Turtle *> * __nonnull turtles = functionThatReturnsSomeTurtlesInAnyOrder();
// This set of matchers passes regardless of whether the array is
// [{color: "blue"}, {color: "green"}] or [{color: "green"}, {color: "blue"}]:
expect(turtles).to(containElementSatisfying(^BOOL(id __nonnull object) {
return [[turtle color] isEqualToString:@"green"];
}));
expect(turtles).to(containElementSatisfying(^BOOL(id __nonnull object) {
return [[turtle color] isEqualToString:@"blue"];
}));
// Swift
// Passes if 'actual' contains 'substring':
expect(actual).to(contain(substring))
// Passes if 'actual' begins with 'prefix':
expect(actual).to(beginWith(prefix))
// Passes if 'actual' ends with 'suffix':
expect(actual).to(endWith(suffix))
// Passes if 'actual' represents the empty string, "":
expect(actual).to(beEmpty())
// Passes if 'actual' matches the regular expression defined in 'expected':
expect(actual).to(match(expected))
// Objective-C
// Passes if 'actual' contains 'substring':
expect(actual).to(contain(expected));
// Passes if 'actual' begins with 'prefix':
expect(actual).to(beginWith(prefix));
// Passes if 'actual' ends with 'suffix':
expect(actual).to(endWith(suffix));
// Passes if 'actual' represents the empty string, "":
expect(actual).to(beEmpty());
// Passes if 'actual' matches the regular expression defined in 'expected':
expect(actual).to(match(expected))
Nimble 提供了一种检查集合的每个元素是否满足某个预期的方法。
在 Swift 中,集合必须是符合 Sequence
类型约定的类型实例。
// Swift
// Providing a custom function:
expect([1, 2, 3, 4]).to(allPass { $0! < 5 })
// Composing the expectation with another matcher:
expect([1, 2, 3, 4]).to(allPass(beLessThan(5)))
在 Objective-C 中,集合必须是实现了 NSFastEnumeration
协议的类型实例,并且其元素是 NSObject
子类的实例。
此外,与 Swift 不同,没有重写以指定自定义匹配器函数。
// Objective-C
expect(@[@1, @2, @3, @4]).to(allPass(beLessThan(@5)));
// Swift
// Passes if 'actual' contains the 'expected' number of elements:
expect(actual).to(haveCount(expected))
// Passes if 'actual' does _not_ contain the 'expected' number of elements:
expect(actual).notTo(haveCount(expected))
// Objective-C
// Passes if 'actual' contains the 'expected' number of elements:
expect(actual).to(haveCount(expected))
// Passes if 'actual' does _not_ contain the 'expected' number of elements:
expect(actual).notTo(haveCount(expected))
对于 Swift,实际值必须是符合 Collection
类型约定的类型实例。例如,Array
、Dictionary
或 Set
的实例。
对于 Objective-C,实际值必须是以下类之一,或它们的子类
NSArray
,NSDictionary
,NSSet
,或者NSHashTable
.// Swift
let testNotification = Notification(name: "Foo", object: nil)
// passes if the closure in expect { ... } posts a notification to the default
// notification center.
expect {
NotificationCenter.default.postNotification(testNotification)
}.to(postNotifications(equal([testNotification]))
// passes if the closure in expect { ... } posts a notification to a given
// notification center
let notificationCenter = NotificationCenter()
expect {
notificationCenter.postNotification(testNotification)
}.to(postNotifications(equal([testNotification]), fromNotificationCenter: notificationCenter))
此匹配器仅可在 Swift 中使用。
// Swift
// passes if actual is either less than 10 or greater than 20
expect(actual).to(satisfyAnyOf(beLessThan(10), beGreaterThan(20)))
// can include any number of matchers -- the following will pass
// **be careful** -- too many matchers can be the sign of an unfocused test
expect(6).to(satisfyAnyOf(equal(2), equal(3), equal(4), equal(5), equal(6), equal(7)))
// in Swift you also have the option to use the || operator to achieve a similar function
expect(82).to(beLessThan(50) || beGreaterThan(80))
// Objective-C
// passes if actual is either less than 10 or greater than 20
expect(actual).to(satisfyAnyOf(beLessThan(@10), beGreaterThan(@20)))
// can include any number of matchers -- the following will pass
// **be careful** -- too many matchers can be the sign of an unfocused test
expect(@6).to(satisfyAnyOf(equal(@2), equal(@3), equal(@4), equal(@5), equal(@6), equal(@7)))
注意:此匹配器允许您链式连接任意数量的匹配器。这提供了灵活性,但如果您在一个测试中链式连接了许多匹配器,那么考虑是否可以将该单个测试重构为多个更专注于特定内容的测试,以获得更好的覆盖率。
// Swift
// passes if .succeed is returned from the closure
expect({
guard case .enumCaseWithAssociatedValueThatIDontCareAbout = actual else {
return .failed("wrong enum case")
}
return .succeeded
}).to(succeed())
// passes if .failed is returned from the closure
expect({
guard case .enumCaseWithAssociatedValueThatIDontCareAbout = actual else {
return .failed("wrong enum case")
}
return .succeeded
}).notTo(succeed())
当测试失败时,使用 .failed()
提供的 String
将显示。
使用 toEventually()
时,请务必注意不要进行状态更改或运行资源密集型代码,因为此闭包将被多次执行。
在 Nimble 中,匹配器是接受一个预期值并返回一个 Predicate
闭包的 Swift 函数。以 equal
为例
// Swift
public func equal<T: Equatable>(expectedValue: T?) -> Predicate<T> {
// Can be shortened to:
// Predicate { actual in ... }
//
// But shown with types here for clarity.
return Predicate { (actual: Expression<T>) throws -> PredicateResult in
let msg = ExpectationMessage.expectedActualValueTo("equal <\(expectedValue)>")
if let actualValue = try actualExpression.evaluate() {
return PredicateResult(
bool: actualValue == expectedValue!,
message: msg
)
} else {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
}
}
Predicate
闭包的返回值是一个 PredicateResult
,它指示实际值是否与预期匹配,以及失败时显示的错误信息。
实际的
equal
匹配器函数在expected
为空时不会匹配;上面示例为了简洁而进行了编辑。
由于匹配器只是 Swift 函数,因此您可以在任何地方定义它们:在测试文件顶部、所有测试共享的文件中,或在您向其他人分发的 Xcode 项目中。
如果您编写的匹配器几乎人人可用,考虑将其添加到Nimble内置匹配器集合中,通过发送pull请求!或者通过GitHub自我分发。
要了解如何编写自己的匹配器,请查看Matchers目录
,了解Nimble内置匹配器的实现方法。您还可以查看以下提示。
PredicateResult
是谓词返回的结构体,用来指示成功和失败。一个PredicateResult
由两个值组成:PredicateStatus
和ExpectationMessage
。
与布尔值不同,PredicateStatus
封装了三元值集合
// Swift
public enum PredicateStatus {
// The predicate "passes" with the given expression
// eg - expect(1).to(equal(1))
case matches
// The predicate "fails" with the given expression
// eg - expect(1).toNot(equal(1))
case doesNotMatch
// The predicate never "passes" with the given expression, even if negated
// eg - expect(nil as Int?).toNot(equal(1))
case fail
// ...
}
与此同时,ExpectationMessage
提供了错误报告的消息语义。
// Swift
public indirect enum ExpectationMessage {
// Emits standard error message:
// eg - "expected to <string>, got <actual>"
case expectedActualValueTo(/* message: */ String)
// Allows any free-form message
// eg - "<string>"
case fail(/* message: */ String)
// ...
}
在报告错误时,谓词通常依赖于.expectedActualValueTo(..)
或.fail(..)
。其他枚举情况可以使用特殊案例。
最后,如果您的谓词利用了其他谓词,您可以使用.appended(details:)
和.appended(message:)
方法用更多详细信息注释现有错误。
一个常见的消息是关于失败的nil值。为此,可以使用.appendedBeNilHint()
。
actualExpression
是围绕expect
函数提供的值的懒memo化闭包。该表达式可以是闭包或直接传递给expect(...)
的值。为了确定该值是否匹配,自定义匹配器应调用actualExpression.evaluate()
// Swift
public func beNil<T>() -> Predicate<T> {
// Predicate.simpleNilable(..) automatically generates ExpectationMessage for
// us based on the string we provide to it. Also, the 'Nilable' postfix indicates
// that this Predicate supports matching against nil actualExpressions, instead of
// always resulting in a PredicateStatus.fail result -- which is true for
// Predicate.simple(..)
return Predicate.simpleNilable("be nil") { actualExpression in
let actualValue = try actualExpression.evaluate()
return PredicateStatus(bool: actualValue == nil)
}
}
在上面的示例中,actualExpression
不是nil
- 它是一个返回值的闭包。该值通过evaluate()
方法访问,可能为nil
。如果该值为nil
,则beNil
匹配器函数返回true
,表示期望通过。
使用Swift的泛型,匹配器可以通过修改返回类型来约束传递给expect
函数的实际值的类型。
例如,以下匹配器haveDescription
仅接受实现了Printable
协议的实际值。它将其description
与传递给匹配器函数的值进行比较,如果它们相同则通过。
// Swift
public func haveDescription(description: String) -> Predicate<Printable?> {
return Predicate.simple("have description") { actual in
return PredicateStatus(bool: actual.evaluate().description == description)
}
}
当使用Predicate.simple(..)
或Predicate.simpleNilable(..)
时,如果期望失败,Nimble将输出以下失败消息
// where `message` is the first string argument and
// `actual` is the actual value received in `expect(..)`
"expected to \(message), got <\(actual)>"
您可以通过修改创建Predicate
的方式来自定义此消息。
对于稍复杂的错误消息,请使用Predicate.define(..)
接收到创建的失败消息。
// Swift
public func equal<T: Equatable>(_ expectedValue: T?) -> Predicate<T> {
return Predicate.define("equal <\(stringify(expectedValue))>") { actualExpression, msg in
let actualValue = try actualExpression.evaluate()
let matches = actualValue == expectedValue && expectedValue != nil
if expectedValue == nil || actualValue == nil {
if expectedValue == nil && actualValue != nil {
return PredicateResult(
status: .fail,
message: msg.appendedBeNilHint()
)
}
return PredicateResult(status: .fail, message: msg)
}
return PredicateResult(bool: matches, message: msg)
}
}
在上面的示例中,msg
是基于传递给Predicate.define
的字符串定义的。代码看起来如下所示
// Swift
let msg = ExpectationMessage.expectedActualValueTo("equal <\(stringify(expectedValue))>")
要完全自定义谓词的行为,请使用期望返回一个PredicateResult
的重载。
除了PredicateResult
之外,还有其他可以使用ExpectationMessage
枚举值
public indirect enum ExpectationMessage {
// Emits standard error message:
// eg - "expected to <message>, got <actual>"
case expectedActualValueTo(/* message: */ String)
// Allows any free-form message
// eg - "<message>"
case fail(/* message: */ String)
// Emits standard error message with a custom actual value instead of the default.
// eg - "expected to <message>, got <actual>"
case expectedCustomValueTo(/* message: */ String, /* actual: */ String)
// Emits standard error message without mentioning the actual value
// eg - "expected to <message>"
case expectedTo(/* message: */ String, /* actual: */ String)
// ...
}
对于由其他匹配器组成的匹配器,有一些辅助函数可以用于注释消息。
appended(message: String)
用于添加到原始失败消息
// produces "expected to be true, got <actual> (use beFalse() for inverse)"
// appended message do show up inline in Xcode.
.expectedActualValueTo("be true").appended(message: " (use beFalse() for inverse)")
对于跨多行的更完整消息,请使用appended(details: String)
代替
// produces "expected to be true, got <actual>\n\nuse beFalse() for inverse\nor use beNil()"
// details do not show inline in Xcode, but do show up in test logs.
.expectedActualValueTo("be true").appended(details: "use beFalse() for inverse\nor use beNil()")
要从 Objective-C 使用 Swift 编写的自定义匹配器,您需要扩展 NMBObjCMatcher
类,为自定义匹配器添加一个新类的方。以下示例定义了类的方 +[NMBObjCMatcher beNilMatcher]
// Swift
extension NMBObjCMatcher {
public class func beNilMatcher() -> NMBObjCMatcher {
return NMBObjCMatcher { actualBlock, failureMessage, location in
let block = ({ actualBlock() as NSObject? })
let expr = Expression(expression: block, location: location)
return beNil().matches(expr, failureMessage: failureMessage)
}
}
}
这允许您从 Objective-C 使用匹配器
// Objective-C
expect(actual).to([NMBObjCMatcher beNilMatcher]());
为了使语法更容易使用,定义一个调用类方法的 C 函数
// Objective-C
FOUNDATION_EXPORT id<NMBMatcher> beNil() {
return [NMBObjCMatcher beNilMatcher];
}
nil
在支持 Objective-C 时,请确保适当地处理 nil
。和 Cedar 一样,大多数匹配器并不与 nil
匹配。这是为了避免测试编写者在未期望的位置遇到 nil
值。
Nimble 为想要对 nil
对象做出期望的测试编写者提供了 beNil
匹配器功能
// Objective-C
expect(nil).to(equal(nil)); // fails
expect(nil).to(beNil()); // passes
如果您的匹配器不希望与 nil
匹配,您将使用 NonNilMatcherFunc
和 NMBObjCMatcher
上的 canMatchNil
构造函数。同时使用这两种类型将在它们为 nil
时自动生成预期的值失败消息。
public func beginWith<S: Sequence, T: Equatable where S.Iterator.Element == T>(startingElement: T) -> NonNilMatcherFunc<S> {
return NonNilMatcherFunc { actualExpression, failureMessage in
failureMessage.postfixMessage = "begin with <\(startingElement)>"
if let actualValue = actualExpression.evaluate() {
var actualGenerator = actualValue.makeIterator()
return actualGenerator.next() == startingElement
}
return false
}
}
extension NMBObjCMatcher {
public class func beginWithMatcher(expected: AnyObject) -> NMBObjCMatcher {
return NMBObjCMatcher(canMatchNil: false) { actualExpression, failureMessage in
let actual = actualExpression.evaluate()
let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
return beginWith(expected).matches(expr, failureMessage: failureMessage)
}
}
}
之前(<7.0.0
),Nimble 通过以下类型支持匹配器
匹配器
非 nil 匹配器函数
匹配器函数
这些类型中的所有类型都已被 Predicate
替换。虽然迁移可能需要大量工作,但 Nimble 目前提供了几个步骤来帮助迁移您的自定义匹配器
.predicate
Nimble 为旧类型提供了一个扩展,该扩展自动将这些类型转换为较新的 Predicate
。
// Swift
public func beginWith<S: Sequence, T: Equatable where S.Iterator.Element == T>(startingElement: T) -> Predicate<S> {
return NonNilMatcherFunc { actualExpression, failureMessage in
failureMessage.postfixMessage = "begin with <\(startingElement)>"
if let actualValue = actualExpression.evaluate() {
var actualGenerator = actualValue.makeIterator()
return actualGenerator.next() == startingElement
}
return false
}.predicate
}
这是支持 Predicate
的最简单方法,这允许更易于组合,而无需做出太多更改。
Predicate
类型第二个最方便的步骤是利用 Predicate
支持的特殊构造函数,这些构造函数与旧 Nimble 匹配器类型的构造函数非常相似。
// Swift
public func beginWith<S: Sequence, T: Equatable where S.Iterator.Element == T>(startingElement: T) -> Predicate<S> {
return Predicate.fromDeprecatedClosure { actualExpression, failureMessage in
failureMessage.postfixMessage = "begin with <\(startingElement)>"
if let actualValue = actualExpression.evaluate() {
var actualGenerator = actualValue.makeIterator()
return actualGenerator.next() == startingElement
}
return false
}
}
这允许您从代码中完全删除旧类型,尽管预期的行为可能会略有改变。
Predicate
类型最后,您可以使用未用于帮助迁移的构造函数之一将转换为原生的 Predicate
格式。
Nimble 7 引入了 Predicate
,但仍将支持旧类型,并发出弃用警告。几个主要的 Nimble 发布版将保持与旧匹配器 API 的向后兼容性,尽管新功能可能不会回滚。
弃用计划是 3 个主要版本的删除。如下所示
Predicate
API,为旧匹配器 API 发出弃用警告。(Nimble v7.x.x
).predicate
,与旧 API 类似的 Predicate
-构造函数)。(Nimble v8.x.x
)v9.x.x
)Nimble可独立使用,也可与其姐妹项目Quick配合使用。要安装Quick和Nimble,请遵循Quick文档中的安装说明。
Nimble目前可以通过两种方式之一安装:使用CocoaPods或使用git子模块。
要将Nimble作为子模块用于测试您的macOS、iOS或tvOS应用程序,请按照以下4个简单的步骤进行操作:
有关每个步骤的更详细说明,请阅读如何安装Quick。为了仅安装Nimble,请忽略涉及将Quick添加到您的项目的步骤。
Nimble与XCTest集成,以便在使用Xcode测试包时能够良好工作,但它也可以在独立应用程序中使用。使用上述方法之一安装Nimble后,还需要两个额外的步骤才能使其工作。
NimbleAssertionHandler
变量。例如class MyAssertionHandler : AssertionHandler {
func assert(assertion: Bool, message: FailureMessage, location: SourceLocation) {
if (!assertion) {
print("Expectation failed: \(message.stringValue)")
}
}
}
// Somewhere before you use any assertions
NimbleAssertionHandler = MyAssertionHandler()
rm "${SWIFT_STDLIB_TOOL_DESTINATION_DIR}/libswiftXCTest.dylib"
现在您可以在代码中使用Nimble断言,并按您的喜好处理失败。