Reduxable 0.1.7

Reduxable 0.1.7

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最后发布2016年9月
SPM支持 SPM

Karl Rivest Harnois 维护。



Reduxable 0.1.7

  • 作者:
  • Karl Rivest Harnois

Nimble

使用 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中使用此语法。💔

懒计算的值

期待函数不会评估给它的值,直到需要匹配。因此,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));

异步期待

在Nimble中,轻松地对异步更新的值进行期待。只需使用toEventuallytoEventuallyNot

// Swift

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停止运行循环。这可能会污染主线程上运行的不完整代码。阻塞主线程可能是由于阻塞I/O、调用sleep()、死锁和同步IPC导致的。

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

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

// Swift

// Waits three seconds for ocean to contain "starfish":
expect(ocean).toEventually(contain("starfish"), timeout: 3)
// 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

Objective-C支持

Nimble完全支持Objective-C。但在使用Nimble时,有几点需要注意:

  1. 传递给expect函数的所有参数,以及如equal之类的匹配器函数都必须是Objective-C对象。

    // Objective-C
    
    @import Nimble;
    
    expect(@(1 + 1)).to(equal(@2));
    expect(@"Hello world").to(contain(@"world"));
  2. 对于不返回值的表达式(例如-[NSException raise]),请使用expectAction而不是expect来设置期望。

    // Objective-C
    
    expectAction(^{ [exception raise]; }).to(raiseException());

禁用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包含大量匹配器函数。

等价

// 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在比较一个或多个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。此匹配器不适用于如structenum之类的具有值语义的类型。如果需要比较两个值类型,可以比较单独的属性,或者如果这样做有意义,则可以使其类型实现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)

(在US键盘上通过按Option-x获得≈)

此处使用默认delta 0.0001。还有另一种方式来完成此操作

// Swift

expect(actual) ≈ expected ± delta
expect(actual) == expected ± delta

(在US键盘上按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对象。

例如,为了断言dolphinMammal的一种

// 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 BooleanType 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 BooleanType 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 BooleanType 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 BooleanType false):
expect(actual).to(beFalse());

// Passes if actual is nil:
expect(actual).to(beNil());

Swift错误处理

如果你正在使用 Swift 2.0 及以上版本,你可以使用 throwError 断言来检查是否抛出了错误。

// Swift

// Passes if somethingThatThrows() throws an ErrorType:
expect{ try somethingThatThrows() }.to(throwError())

// Passes if somethingThatThrows() throws an error with a given domain:
expect{ try somethingThatThrows() }.to(throwError { (error: ErrorType) in
    expect(error._domain).to(equal(NSCocoaErrorDomain))
})

// Passes if somethingThatThrows() throws an error with a given case:
expect{ try somethingThatThrows() }.to(throwError(NSCocoaError.PropertyListReadCorruptError))

// Passes if somethingThatThrows() throws an error with a given type:
expect{ try somethingThatThrows() }.to(throwError(errorType: MyError.self))

如果你直接使用 ErrorType 值,就像使用 ResultPromise 类型时那样,你可以使用 matchError 断言来检查错误是否为预期的错误,无需进行显式转换。

// Swift

let actual: ErrorType =// Passes if actual contains any error value from the MyErrorEnum type:
expect(actual).to(matchError(MyErrorEnum))

// Passes if actual contains the Timeout value from the MyErrorEnum type:
expect(actual).to(matchError(MyErrorEnum.Timeout))

// Passes if actual contains an NSError equal to the given one:
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 and it passes expectations in the block
// (in this case, if 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 in the 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 an empty collection (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 an empty collection (it contains no elements):
expect(actual).to(beEmpty());

在 Swift 中,contain 可以接受任意数量的参数。如果所有这些参数都是集合的成员,则期望通过。就现在而言,在 Objective-C 中,contain 只能接受一个参数(目前)

例如,为了断言某个海洋生物名称的列表包含“海豚”和“海星”

// 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 只支持一个参数 (目前)

字符串

// Swift

// Passes if actual contains substring expected:
expect(actual).to(contain(expected))

// Passes if actual begins with substring:
expect(actual).to(beginWith(expected))

// Passes if actual ends with substring:
expect(actual).to(endWith(expected))

// Passes if actual is an 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 expected:
expect(actual).to(contain(expected));

// Passes if actual begins with substring:
expect(actual).to(beginWith(expected));

// Passes if actual ends with substring:
expect(actual).to(endWith(expected));

// Passes if actual is an empty string, "":
expect(actual).to(beEmpty());

// Passes if actual matches the regular expression defined in expected:
expect(actual).to(match(expected))

检查集合中所有元素是否符合条件

// Swift

// with a custom function:
expect([1,2,3,4]).to(allPass({$0 < 5}))

// with another matcher:
expect([1,2,3,4]).to(allPass(beLessThan(5)))
// Objective-C

expect(@[@1, @2, @3,@4]).to(allPass(beLessThan(@5)));

对于 Swift,实际值必须是 SequenceType,例如数组、集合或自定义序列类型。

对于 Objective-C,实际值必须是 NSFastEnumeration,例如 NSArrayNSSet,或者 NSObjects 的其中一种变体,并且此处只可用另一种断言器。

验证集合计数

// passes if actual collection's count is equal to expected
expect(actual).to(haveCount(expected))

// passes if actual collection's count is not equal to expected
expect(actual).notTo(haveCount(expected))
// passes if actual collection's count is equal to expected
expect(actual).to(haveCount(expected))

// passes if actual collection's count is not equal to expected
expect(actual).notTo(haveCount(expected))

对于 Swift,实际值必须是诸如数组、字典或集合之类的 CollectionType

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

将值匹配到多个断言器中的一员

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

注意:此断言器允许你将任意数量的断言器链起来。这提供了灵活性,但如果你发现自己在单个测试中多次链多个断言器,考虑将单一测试重构为多个更专注的测试,以实现更好的覆盖。
你可以通过重构将单一测试分解为多个,以便更好地进行测试覆盖。

编写自己的断言器

在 Nimble 中,断言器是接受预期值并返回一个 MatcherFunc 闭包的 Swift 函数。以 equal 为例:

// Swift

public func equal<T: Equatable>(expectedValue: T?) -> MatcherFunc<T?> {
  return MatcherFunc { actualExpression, failureMessage in
    failureMessage.postfixMessage = "equal <\(expectedValue)>"
    return actualExpression.evaluate() == expectedValue
  }
}

MatcherFunc 闭包的返回值是一个 Bool 类型,表示实际值是否与期望匹配:如果匹配则为 true,如果不匹配则为 false

实际的equal匹配函数在actualexpected为nil时不会匹配;上面示例已编辑以节省空间。

因为匹配器只是Swift函数,所以您可以在任何地方定义它们:在测试文件开头、所有测试共享的文件中,或者您分发给他人的Xcode项目中。

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

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

惰性求值

actualExpression是围绕提供给expect函数的值的一个懒加载、记忆化闭包。该表达式可以是闭包或直接传递给expect(...)的值。为了确定该值是否匹配,自定义匹配器应该调用actualExpression.evaluate()

// Swift

public func beNil<T>() -> MatcherFunc<T?> {
  return MatcherFunc { actualExpression, failureMessage in
    failureMessage.postfixMessage = "be nil"
    return actualExpression.evaluate() == nil
  }
}

在上面的例子中,actualExpression不是nil - 它是一个返回值的闭包。它通过访问evaluate()方法返回的值,这个值可能为nil。如果该值为nil,则beNil匹配器函数返回true,表明期望通过。

使用expression.isClosure来确定表达式是否将调用闭包以产生其值。

通过Swift泛型进行类型检查

使用Swift的泛型,匹配器可以通过修改返回类型来约束传递给expect函数的实际值的类型。

例如,以下匹配器haveDescription仅接受实现Printable协议的实际值。它检查它们的description与提供给匹配器函数的值,如果它们相同则通过。

// Swift

public func haveDescription(description: String) -> MatcherFunc<Printable?> {
  return MatcherFunc { actual, failureMessage in
    return actual.evaluate().description == description
  }
}

自定义失败消息

默认情况下,当期望失败时,Nimble输出以下失败消息

expected to match, got <\(actual)>

您可以通过从您的MatcherFunc闭包中修改failureMessage结构来自定义此消息。要改变动词“match”,请更新postfixMessage属性

// Swift

// Outputs: expected to be under the sea, got <\(actual)>
failureMessage.postfixMessage = "be under the sea"

您可以通过更新failureMessage.actualValue来更改actual值的显示方式。或者,如果要完全删除它,将其设置为nil

// Swift

// Outputs: expected to be under the sea
failureMessage.actualValue = nil
failureMessage.postfixMessage = "be under the sea"

支持Objective-C

要从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];
}

在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匹配,您将使用NonNilMatcherFunc以及使用在NMBObjCMatcher上的canMatchNil构造函数。当它们为nil时,使用这两种类型将会自动生成预期的值失败信息。

public func beginWith<S: SequenceType, T: Equatable where S.Generator.Element == T>(startingElement: T) -> NonNilMatcherFunc<S> {
    return NonNilMatcherFunc { actualExpression, failureMessage in
        failureMessage.postfixMessage = "begin with <\(startingElement)>"
        if let actualValue = actualExpression.evaluate() {
            var actualGenerator = actualValue.generate()
            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)
        }
    }
}

安装Nimble

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

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

将Nimble作为子模块安装

要将Nimble作为子模块使用来测试您的iOS或OS X应用程序,请遵循以下4个简单步骤:

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

有关这些步骤的更详细说明,请阅读如何安装Quick。为了仅安装Nimble,请忽略涉及将Quick添加到您项目的步骤。

在不使用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断言,并按照您的想法处理失败。