Nimble 13.4.0

Nimble 13.4.0

测试已测试
语言语言 SwiftSwift
许可 Apache-2.0
发布日期最后发布日期2024年8月
SPM支持SPM

Ash FurrowBrian GesiakJeff HuiBrian CroomBen ChatelainSyo IkedaAdam SharpJesse SquiresRachel Brindle维护。



Nimble 13.4.0

  • 快速贡献者

Nimble

Build Status CocoaPods Carthage Compatible Platforms

使用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())

如何使用Nimble

目录 DocToc生成

一些背景:在使用XCTest中使用断言表达结果

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断言有几个缺点

  1. 宏不够。没有简单的方法来断言一个字符串是否包含特定的子字符串,或者一个数字是否小于或等于另一个数字。
  2. 难以编写异步测试。XCTest强制您编写大量的模板代码。

Nimble解决了这些问题。

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将突出显示正确的行。

要执行相反的期望——断言某个东西不等于另一项——请使用toNotnotTo

// 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原语

一些测试框架使测试原生的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));

Async/Await 支持

Nimble 使得等待异步函数完成变得非常简单。只需将异步函数传入 expect 即可。

// Swift
await expect { await aFunctionReturning1() }.to(equal(1))

首先等待异步函数执行,然后再将其传递给匹配器。这使得匹配器可以像以前一样运行同步代码,而不关心它处理的值是异步获取的还是同步获取的。

异步支持仅限 Swift,并且需要在异步上下文中执行测试。对于 XCTest,这就像为测试函数标记上 async 一样简单。如果您使用 Quick,Quick 6 中所有测试都在异步上下文中执行。在 Quick 7 及以后的版本中,只有位于 AsyncSpec 子类中的测试才会以异步上下文执行。

为了避免在异步上下文中使用同步 expect 时出现编译器错误,带有异步表达式的 expect 不支持 autoclosures。然而,提供了 expecta(expect async)函数作为替代方案,它支持 autoclosures。

// Swift
await expecta(await aFunctionReturning1()).to(equal(1)))

类似地,如果您需要在某种情况下强制编译器生成 SyncExpectation,可以使用 expects(expect sync)函数来生成 SyncExpectation。例如:

// Swift
expects(someNonAsyncFunction()).to(equal(1)))

expects(await someAsyncFunction()).to(equal(1)) // Compiler error: 'async' call in an autoclosure that does not support concurrency

异步匹配器

除了在将异步函数传递给同步谓词之前对其断言之外,您还可以编写直接接受异步值的匹配器。这些被称为 AsyncPredicate。这在直接对演员进行断言时最为明显。除了编写自己的异步匹配器外,Nimble 目前还提供以下谓词的异步版本:

  • allPass
  • containElementSatisfying
  • satisfyAllOf&& 操作符重载同时接受 AsyncPredicate 和同步 Predicate
  • satisfyAnyOf|| 操作符重载同时接受 AsyncPredicate 和同步 Predicate

注意:Async/Await 支持与下面描述的 toEventually/toEventuallyNot 功能不同。

轮询期望

在 Nimble 中,对异步更新的值设置期望非常简单。只需使用 toEventuallytoEventuallyNot

// Swift
DispatchQueue.main.async {
    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 停止运行循环。这可能会导致正在主线程上运行的任何未完成代码的测试污染。阻塞主线程可能是由于阻塞 I/O、sleep() 调用、死锁和同步 IPC 导致的。

在上面的示例中,ocean 不断重新评估。如果它包含海豚和鲸鱼,则期望通过。如果即使经过一整秒钟的持续重新评估,ocean 仍然不包含它们,则期望失败。

在异步测试中使用轮询期望

您还可以在异步上下文中轻松使用 toEventuallytoEventuallyNot。您只需要在行的开头添加一个 await 语句

// Swift
DispatchQueue.main.async {
    ocean.add("dolphins")
    ocean.add("whales")
}
await expect(ocean).toEventually(contain("dolphens", "whiles"))

从 Nimble 12 版本开始,toEventually 等。现在也支持异步期望。以下示例现在受到支持

actor MyActor {
    private var counter = 0

    func access() -> Int {
        counter += 1
        return counter
    }
}

let subject = MyActor()
await expect { await subject.access() }.toEventually(equal(2))

验证谓词是否永远不会或始终匹配

您还可以测试值在超时期间始终或永远不会匹配。使用 toNevertoAlways 进行此测试

// Swift
ocean.add("dolphins")
expect(ocean).toAlways(contain("dolphins"))
expect(ocean).toNever(contain("hares"))
// Objective-C
[ocean add:@"dolphins"]
expect(ocean).toAlways(contain(@"dolphins"))
expect(ocean).toNever(contain(@"hares"))

等待回调被调用

您还可以使用 waitUntil 函数提供回调

// Swift

waitUntil { done in
    ocean.goFish { success in
        expect(success).to(beTrue())
        done()
    }
}
// Objective-C

waitUntil(^(void (^done)(void)){
    [ocean goFishWithHandler:^(BOOL success){
        expect(success).to(beTrue());
        done();
    }];
});

waitUntil 还可以可选地接受一个超时参数

// Swift

waitUntil(timeout: .seconds(10)) { done in
    ocean.goFish { success in
        expect(success).to(beTrue())
        done()
    }
}
// Objective-C

waitUntilTimeout(10, ^(void (^done)(void)){
    [ocean goFishWithHandler:^(BOOL success){
        expect(success).to(beTrue());
        done();
    }];
});

注意:waitUntil 在主线程上触发其超时代码。阻塞主线程会导致 Nimble 继续停止运行循环。这可能会导致正在主线程上运行的任何未完成代码的测试污染。阻塞主线程可能是由于阻塞 I/O、sleep() 调用、死锁和同步 IPC 导致的。

修改超时和轮询间隔

有时候,一个值更新可能需要超过一秒钟。在这些情况下,可以使用timeout参数。

// Swift

// Waits three seconds for ocean to contain "starfish":
expect(ocean).toEventually(contain("starfish"), timeout: .seconds(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: .milliseconds(5500), pollInterval: .milliseconds(200))
// Objective-C

// Waits three seconds for ocean to contain "starfish":
expect(ocean).withTimeout(3).toEventually(contain(@"starfish"));

修改默认超时和轮询间隔

在某些情况下(例如在较慢的机器上运行时),修改默认超时和轮询间隔值可能很有用。可以通过以下方式完成。

// Swift

// Increase the global timeout to 5 seconds:
Nimble.PollingDefaults.timeout = .seconds(5)

// Slow the polling interval to 0.1 seconds:
Nimble.PollingDefaults.pollInterval = .milliseconds(100)

您可以在启动测试时全局设置这些参数有两种方法。

快速

如果您使用Quick,请添加一个设置您期望的PollingDefaultsQuickConfiguration子类。

import Quick
import Nimble

class PollingConfiguration: QuickConfiguration {
    override class func configure(_ configuration: QCKConfiguration) {
        Nimble.PollingDefaults.timeout = .seconds(5)
        Nimble.PollingDefaults.pollInterval = .milliseconds(100)
    }
}

XCTest

如果您使用XCTest,请添加一个符合XCTestObservation的对象并实现testBundleWillStart(_:)

另外,您需要在测试启动时使用XCTestObservationCenter注册此观察者。为此,在您的测试包的Info.plist中设置NSPrincipalClass键,并实现同名类。

例如

<!-- Info.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- ... -->
	<key>NSPrincipalClass</key>
	<string>MyTests.TestSetup</string>
</dict>
</plist>
// TestSetup.swift
import XCTest
import Nimble

@objc
class TestSetup: NSObject {
	override init() {
		XCTestObservationCenter.shared.register(PollingConfigurationTestObserver())
	}
}

class PollingConfigurationTestObserver: NSObject, XCTestObserver {
    func testBundleWillStart(_ testBundle: Bundle) {
        Nimble.PollingDefaults.timeout = .seconds(5)
        Nimble.PollingDefaults.pollInterval = .milliseconds(100)
    }
}

在Linux中,您可以在调用之前实施LinuxMain来设置轮询默认值。

Objective-C 支持

Nimble 对 Objective-C 完全支持。然而,在使用 Nimble 进行 Objective-C 开发时,需要注意以下两点:

  1. 传递给 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)));
  2. 对于不返回值的表达式,如 -[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

如需更多信息,请 提交问题

禁用 Objective-C 简写符号

Nimble 提供了一个使用 expect 函数表达期望的简写。为了在 Objective-C 中禁用这种简写,在导入 Nimble 之前,请在代码的某个位置定义 NIMBLE_DISABLE_SHORT_SYNTAX 宏。

#define NIMBLE_DISABLE_SHORT_SYNTAX 1

@import Nimble;

NMB_expect(^{ return seagull.squawk; }, __FILE__, __LINE__).to(NMB_equal(@"Squee!"));

如果您正在测试与 Nimble 函数冲突的函数名(例如 expectequal)的函数,禁用简写是有用的。如果不是这种情况,则没有禁用简写的必要。

内置匹配函数

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))

值必须是 EquatableComparableNSObject 的子类。当使用 equal 来比较一个或多个 null 值时,总是会失败。

身份

// 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 的类型。

当比较具有值语义的类型(如定义为 structenum 的类型)时,此匹配器将不会生效。如果您需要比较两个值类型,请考虑您的类型实例 Identical 的含义。这可能意味着比较单个属性,或者如果这样做有意义,使类型符合 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.0110 接近,可以写成如下:

// 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 匹配器的值必须符合 FloatingPoint

类型/类

// 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 对象。

例如,要断言 海豚哺乳动物 的一种

// 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 断言

如果您使用 Swift,可以使用 throwAssertion 匹配器来检查是否引发了断言(例如 fatalError())。这得益于 @mattgallagherCwlPreconditionTesting 库。

// 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

注意

  • 该功能仅在 Swift 中可用。
  • tvOS 模拟器支持,但使用不同的机制,需要您关闭 tvOS 方案测试配置中的 Debug executable 方案设置。

Swift错误处理

您可以使用throwError匹配器来检查是否抛出了错误。

// 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匹配器允许您检查错误

  • 是否与您期望的错误类型相同。
  • 表示您期望的特定错误值。

这在使用ResultPromise类型时很有用,例如。

// 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中,目前只接受一个参数暂时如此

例如,要断言海洋生物名称列表包含“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"));

containbeEmpty期望集合是NSArrayNSSet或由Equatable元素组成的Swift集合的实例。

要测试元素集合是否位于有序集合的开始或结束位置,请使用beginWithendWith

// 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));

beginWithendWith期望集合是NSArray的实例,或者是由Equatable元素组成的有序Swift集合。

contain类似,在Objective-C中,beginWithendWith目前只支持单个参数。(更多信息请见此链接)。

对于返回无严格排序的复杂数组对象的代码,可以使用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

注意:在Swift中,containElementSatisfying也有一个变体,它接受异步函数。

// 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"];
}));

要断言给定的Comparable值是否在Range中,请使用beWithin匹配器。

// Swift

// Passes if 5 is within the range 1 through 10, inclusive
expect(5).to(beWithin(1...10))

// Passes if 5 is not within the range 2 through 4.
expect(5).toNot(beWithin(2..<5))

字符串

// 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

在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)))

还有与异步匹配器相关的allPass变体,并接受异步函数。

// Swift

// Providing a custom function:
expect([1, 2, 3, 4]).to(allPass { await asyncFunctionReturningBool($0) })

// Composing the expectation with another matcher:
expect([1, 2, 3, 4]).to(allPass(someAsyncMatcher()))

Objective-C

在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类型的实例。例如,ArrayDictionarySet的实例。

对于Objective-C,实际值必须是以下类之一,或它们的子类

  • NSArray,
  • NSDictionary,
  • NSSet
  • NSHashTable.

通知

// Swift
let testNotification = Notification(name: Notification.Name("Foo"), object: nil)

// Passes if the closure in expect { ... } posts a notification to the default
// notification center.
expect {
    NotificationCenter.default.post(testNotification)
}.to(postNotifications(equal([testNotification])))

// Passes if the closure in expect { ... } posts a notification to a given
// notification center
let notificationCenter = NotificationCenter()
expect {
    notificationCenter.post(testNotification)
}.to(postNotifications(equal([testNotification]), from: notificationCenter))

// Passes if the closure in expect { ... } posts a notification with the provided names to a given
// notification center. Make sure to use this when running tests on Catalina, 
// using DistributedNotificationCenter as there is currently no way 
// of observing notifications without providing specific names.
let distributedNotificationCenter = DistributedNotificationCenter()
expect {
    distributedNotificationCenter.post(testNotification)
}.toEventually(postDistributedNotifications(equal([testNotification]),
                                  from: distributedNotificationCenter,
                                  names: [testNotification.name]))

此匹配器仅适用于Swift。

结果

// Swift
let aResult: Result<String, Error> = .success("Hooray") 

// passes if result is .success
expect(aResult).to(beSuccess()) 

// passes if result value is .success and validates Success value
expect(aResult).to(beSuccess { value in
    expect(value).to(equal("Hooray"))
})


enum AnError: Error {
    case somethingHappened
}
let otherResult: Result<String, AnError> = .failure(.somethingHappened) 

// passes if result is .failure
expect(otherResult).to(beFailure()) 

// passes if result value is .failure and validates error
expect(otherResult).to(beFailure { error in
    expect(error).to(matchError(AnError.somethingHappened))
}) 

此匹配器仅适用于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))

注意:在Swift中,您可以通过使用satisfyAnyOf/||混合使用同步和异步谓词。

// 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 .succeeded is returned from the closure
expect {
    guard case .enumCaseWithAssociatedValueThatIDontCareAbout = actual else {
        return .failed(reason: "wrong enum case")
    }

    return .succeeded
}.to(succeed())

// passes if .failed is returned from the closure
expect {
    guard case .enumCaseWithAssociatedValueThatIDontCareAbout = actual else {
        return .failed(reason: "wrong enum case")
    }

    return .succeeded
}.notTo(succeed())

当测试失败时,使用.failed()提供的String文本将被显示。

当使用toEventually()时,请注意不要进行状态变更或运行大量计算密集型代码,因为这个闭包会被多次运行。

编写你自己的匹配器

在Nimble中,匹配器是Swift函数,接收到一个预期值并返回一个Predicate闭包。以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 { (actualExpression: 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为nil时不匹配;上面的示例已被编辑以节省空间。

由于匹配器只是Swift函数,您可以定义它们在任何地方:在测试文件顶部、所有测试共用的文件中,或您分发给其他人的Xcode项目中。

如果您编写了一个大家都可以使用的匹配器,请考虑通过发送pull request将其添加到Nimble的内置匹配器集中!或者您可以通过GitHub自行分发。

有关如何编写自己的匹配器的示例,请查看Matchers目录,了解Nimble内置匹配器的实现方式。您还可以查看下面的提示。

PredicateResult

PredicateResultPredicate返回的结构体,用于指示成功和失败。一个PredicateResult由两个值组成:PredicateStatusExpectationMessage

与布尔值不同,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:) 方法在现有错误上添加更多详细信息。

常用的附加信息是针对空值的失败。为此,可以使用 .appendedBeNilHint()

懒加载评估

actualExpression 是一个懒、记忆化闭包,它围绕着传递给 expect 函数的值。该表达式可以是闭包,或者直接传递给 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 泛型进行类型检查

使用 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))>")

完全自定义

要完全自定义Predicate的行为,请使用期望返回 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)

// ...
}

对于组合其他匹配器的匹配器,有一系列辅助函数可以用来注释消息。

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()")

异步Predicate

要针对异步表达式编写Predicate,返回一个 AsyncPredicate 实例。传递给 AsyncPredicate的闭包是异步的,而您评估的表达式也是异步的,需要在使用时进行等待。

// Swift

actor CallRecorder<Arguments> {
    private(set) var calls: [Arguments] = []
    
    func record(call: Arguments) {
        calls.append(call)
    }
}

func beCalled<Argument: Equatable>(with arguments: Argument) -> AsyncPredicate<CallRecorder<Argument>> {
    AsyncPredicate { (expression: AsyncExpression<CallRecorder<Argument>>) in
        let message = ExpectationMessage.expectedActualValueTo("be called with \(arguments)")
        guard let calls = try await expression.evaluate()?.calls else {
            return PredicateResult(status: .fail, message: message.appendedBeNilHint())
        }
        
        return PredicateResult(bool: calls.contains(args), message: message.appended(details: "called with \(calls)"))
    }
}

在这个例子中,我们创建了一个actor作为对象来记录对异步函数的调用。然后,我们创建了一个 beCalled(with:) 匹配器来检查actor是否收到了给定参数的调用。

支持 Objective-C

要从 Objective-C 中使用用 Swift 编写的自定义匹配器,您必须扩展 NMBPredicate 类,添加一个用于自定义匹配器的新类方法。下面示例定义了类方法 +[NMBPredicate beNilMatcher]

// Swift

extension NMBPredicate {
    @objc public class func beNilMatcher() -> NMBPredicate {
        return NMBPredicate { actualExpression in
            return try beNil().satisfies(actualExpression).toObjectiveC()
        }
    }
}

这使您可以从 Objective-C 中使用匹配器

// Objective-C

expect(actual).to([NMBPredicate beNilMatcher]());

为使语法更容易使用,定义一个调用类方法的 C 函数

// Objective-C

FOUNDATION_EXPORT NMBPredicate *beNil() {
    return [NMBPredicate beNilMatcher];
}

在 Objective-C 匹配器中正确处理 nil

当支持 Objective-C 时,请确保您适当地处理 nil。就像 Cedar 一样,大多数匹配器都不会匹配 nil。这是为了防止测试编写者对没有预期的 nil 值感到意外。

Nimble 为希望对 nil 对象创建期望的测试编写者提供了 beNil 匹配器函数

// Objective-C

expect(nil).to(equal(nil)); // fails
expect(nil).to(beNil());    // passes

如果您的匹配器不想与 nil 匹配,请使用 Predicate.definePredicate.simple。使用这些工厂方法会在它们为 nil 时自动生成预期的值失败消息。

public func beginWith<S: Sequence>(_ startingElement: S.Element) -> Predicate<S> where S.Element: Equatable {
    return Predicate.simple("begin with <\(startingElement)>") { actualExpression in
        guard let actualValue = try actualExpression.evaluate() else { return .fail }

        var actualGenerator = actualValue.makeIterator()
        return PredicateStatus(bool: actualGenerator.next() == startingElement)
    }
}

extension NMBPredicate {
    @objc public class func beginWithMatcher(_ expected: Any) -> NMBPredicate {
        return NMBPredicate { actualExpression in
            let actual = try actualExpression.evaluate()
            let expr = actualExpression.cast { $0 as? NMBOrderedCollection }
            return try beginWith(expected).satisfies(expr).toObjectiveC()
        }
    }
}

安装 Nimble

Nimble 可以独立使用,也可以与姊妹项目 Quick 一起使用。要安装 Quick 和 Nimble,请遵循 Quick 文档中的安装说明

Nimble 可以通过两种方式之一安装:使用 CocoaPods,或者使用 git 子模块。

将 Nimble 安装为子模块

要将 Nimble 作为子模块用于测试 macOS、iOS 或 tvOS 应用程序,请按照以下 4 个简单步骤操作

  1. 克隆 Nimble 仓库
  2. 将 Nimble.xcodeproj 添加到您项目的 Xcode 工作空间中
  3. 将 Nimble.framework 链接到您的测试目标
  4. 开始编写预期!

有关每个步骤的更详细说明,请阅读 如何安装 Quick。忽略涉及将 Quick 添加到项目的步骤,以便仅安装 Nimble。

通过 CocoaPods 安装 Nimble

要将 Nimble 集成到 CocoaPods 中,并对 macOS、iOS、tvOS 或 watchOS 应用程序进行测试,请在 podfile 中添加 Nimble,并添加 use_frameworks! 行以启用 CocoaPods 的 Swift 支持。

platform :ios, '8.0'

source 'https://github.com/CocoaPods/Specs.git'

# Whatever pods you need for your app go here

target 'YOUR_APP_NAME_HERE_Tests', :exclusive => true do
  use_frameworks!
  pod 'Nimble'
end

最后运行 pod install

通过 Swift Package Manager 安装 Nimble

Xcode

要使用 Xcode 的 Swift 包管理器集成安装 Nimble,请选择项目配置,然后选择项目标签,然后选择“包依赖项”标签。单击列表底部的“+”按钮,然后按照向导将 Quick 添加到您的项目中。指定 https://github.com/Quick/Nimble.git 作为 URL,并确保将 Nimble 添加为单元测试目标的依赖项,而不是您的应用程序目标的依赖项。

Package.Swift

要使用 Swift 包管理器将 Nimble 与您的应用程序一起测试,请将 Nimble 添加到您的 Package.Swift,并将其链接到您的测试目标。

// swift-tools-version:5.5

import PackageDescription

let package = Package(
    name: "MyAwesomeLibrary",
    products: [
        // ...
    ],
    dependencies: [
        // ...
        .package(url:  "https://github.com/Quick/Nimble.git", from: "12.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "MyAwesomeLibrary",
            dependencies: ...),
        .testTarget(
            name: "MyAwesomeLibraryTests",
            dependencies: ["MyAwesomeLibrary", "Nimble"]),
    ]
)

请注意,如果您使用 Swift 包管理器安装 Nimble,则 raiseException 不可用。

在不使用 XCTest 的情况下使用 Nimble

Nimble 与 XCTest 集成,允许它在 Xcode 测试束中使用时运行良好,但它也可以用于独立的应用程序。通过上述方法之一安装 Nimble 后,还需要执行两个额外的步骤来实现这一点。

  1. 创建一个自定义断言处理程序,并将其实例分配给全局 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()
  1. 添加一个后构建操作以解决 Swift XCTest 支持库不必要地复制到您的应用程序中的问题
  • 在 Xcode 中编辑您的方案,导航到“构建”-"后操作”
  • 单击“+”图标并选择“新建运行脚本操作”
  • 打开“提供构建设置来源”下拉菜单并选择您的目标
  • 输入以下脚本内容
rm "${SWIFT_STDLIB_TOOL_DESTINATION_DIR}/libswiftXCTest.dylib"

您现在可以在您的代码中使用 Nimble 断言,并按需处理失败。