iOS 单元测试框架 XCTest (二)Assert
iOS测试框架XCTest提供了许多断言,以下是一些常见的断言及其使用例子:
Bool 断言
- XCTAssert:断言一个条件为true
XCTAssert(true, "This should always pass")
- XCTAssertTrue:断言一个条件为true
let a = 5
let b = 10
XCTAssertTrue(a < b, "a should be less than b")
- XCTAssertFalse:断言一个条件为false
let a = 5
let b = 10
XCTAssertFalse(a > b, "a should not be greater than b")
相等和不等断言
值比较
下面两个是值的相等比较,如基础类型Int、String、Array、Struct等。
- XCTAssertEqual:断言两个对象相等
- XCTAssertNotEqual:断言两个对象不相等
let a = "Hello"
let b = "Hello"
XCTAssertEqual(a, b, "These two strings should be equal")
let c = "Hello"
let d = "World"
XCTAssertNotEqual(c, d, "These two strings should not be equal")
还有两个泛型变种,当T类型遵循 FloatingPoint
或者 Numeric
时,可以指定精度 accuracy
,当有两者都有对应的 XCTAssertNotEqual
断言。
func XCTAssertEqual<T>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: T,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) where T : FloatingPoint
func XCTAssertEqual<T>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: T,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line
) where T : Numeric
下面是一个 FloatingPoint
值的比较
let calculator = Calculator()
let result = calculator.calculateSquareRoot(9)
XCTAssertEqual(result, 3.0, accuracy: 0.01, "Square root of 9 should be 3")
对象比较
下面这两个函数通常用于测试对象的引用相等性,即两个对象是否指向同一块内存
- XCTAssertIdentical:断言两个对象是同一个对象,即它们的内存地址相同。
- XCTAssertNotIdentical:断言两个对象不是同一个对象,即它们的内存地址不同。
大小比较断言
用于比较可比较类型(遵循 Comparable 协议)的值的断言函数
- XCTAssertGreaterThan:大于比较
- XCTAssertGreaterThanOrEqual:大于等于比较
- XCTAssertLessThan:小于比较
- XCTAssertLessThanOrEqual:小于等于比较
例如我们验证升序排序算法
func testSortAscendingOrder() {
let numbers = [2, 1, 3]
let sortedNumbers = numbers.sorted()
XCTAssertLessThan(sortedNumbers[0], sortedNumbers[1], "The first number should be less than the second number")
}
空值和非空值断言
- XCTAssertNil:断言一个对象为nil
let a: String? = nil
XCTAssertNil(a, "a should be nil")
- XCTAssertNotNil:断言一个对象不为nil
let a: String? = "Hello"
XCTAssertNotNil(a, "a should not be nil")
- XCTUnwrap:检查可选值是否不为 nil,如果不为 nil,则返回已解包的值。
let optionalValue: String? = "Hello, world!"
let unwrappedValue = try XCTUnwrap(optionalValue)
XCTAssertEqual(unwrappedValue, "Hello, world!")
抛出错误和不抛出错误断言
- XCTAssertThrowsError:断言一个表达式抛出了一个错误
完整定义如下:
func XCTAssertThrowsError<T, E>(
_ expression: @autoclosure () throws -> T,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath,
line: UInt = #line,
_ errorHandler: (E) -> Void = { _ in }
) where E : Error, E : Equatable
该函数有两个参数要注意:
expression
:要测试的表达式,类型为() throws -> T
,表示该表达式可能抛出一个错误。errorHandler
:可选的错误处理函数,用于检查抛出的错误是否符合预期,类型为(E) -> Void
,其中E
是期望的错误类型,并且必须遵循Error
和Equatable
协议。
下面看一个例子:
func divide(_ a: Int, by b: Int) throws -> Int {
guard b != 0 else {
throw NSError(domain: "Division by zero", code: 1, userInfo: nil)
}
return a / b
}
XCTAssertThrowsError(try divide(5, by: 0), "Divide by zero error should be thrown") { error in
XCTAssertEqual(error.localizedDescription, "Division by zero")
}
- XCTAssertNoThrow:测试一个表达式是否不会抛出错误
func testAddingNumbers() {
let calculator = Calculator()
let result = XCTAssertNoThrow(try calculator.add(2, to: 3), "Adding two numbers should not throw an error")
XCTAssertEqual(result, 5, "The result should be 5")
}
无条件的测试失败
- XCTFail:表示该测试总是失败
func testAlwaysFail() {
XCTFail("This test always fails")
}
通常情况下,XCTFail
用于表示某些预期的条件不满足,或者测试的某些部分出现了意外的错误。如果测试代码执行到 XCTFail
,该测试将被标记为“失败”,并在测试报告中显示错误消息。
需要注意的是,XCTFail
可以在任何测试函数中使用,但应该避免在测试代码中使用过多的 XCTFail
,因为它们可能会使测试报告变得混乱和难以理解。通常来说,最好使用其他的断言函数来测试特定的条件和预期结果,只在必要时使用 XCTFail
表示测试失败。
预期中的错误断言
XCTExpectFailure 是 XCTest 框架中的一个函数,用于标记一个测试用例或测试代码中的某个部分为“预期失败”。该函数的定义如下:
func XCTExpectFailure(
_ failureReason: String? = nil,
options: XCTExpectedFailure.Options = .init()
)
func XCTExpectFailure<R>(
_ failureReason: String? = nil,
options: XCTExpectedFailure.Options = .init(),
failingBlock: () throws -> R
) rethrows -> R
其中 failingBlock:需要标记为“预期失败”的测试代码块。
使用 XCTExpectFailure 可以将测试结果标记为“预期失败”。这在测试代码中存在某些已知的问题或者限制时非常有用。例如,如果某个测试用例在特定的条件下经常失败,但是在其他情况下能够正常运行,可以使用 XCTExpectFailure 标记该测试用例的失败部分为“预期失败”,以避免测试结果被错误地标记为“实际失败”。
下面是一个使用 XCTExpectFailure
的示例:
func testDivisionByZero() {
let calculator = Calculator()
XCTExpectFailure("Dividing by zero should throw an error") {
XCTAssertThrowsError(try calculator.divide(10, by: 0), "The error should be divideByZero")
}
}
在这个示例中,XCTExpectFailure 用于标记测试中的某个部分是“预期失败”的。具体来说,它标记了 XCTAssertThrowsError
的某个断言应该是“预期失败”的,因为该断言将抛出一个错误,而该错误是由 calculator.divide(10, by: 0)
导致的,而该表达式是除以零的情况,因此应该抛出一个 CalculatorError.divideByZero
的错误。如果未抛出该错误,该断言将导致测试失败,并在测试报告中显示错误消息 "The error should be divideByZero"。但是,由于该部分代码已经被标记为“预期失败”,因此测试结果将被标记为“预期失败”,而不是“实际失败”。
需要注意的是,XCTExpectFailure
只适用于测试中已经预期测试某些部分会失败的情况。如果测试代码中存在未处理的错误或异常,测试框架将自动将其标记为“实际失败”,而不是“预期失败”。因此,在编写测试代码时,需要仔细考虑预期的测试结果,并使用适当的断言函数来测试预期的条件和结果,以便测试框架能够正确地处理测试结果。
增强的 XCTExpectFailure
Xcode 13.0 增加了两个重载函数,增强了 XCTExpectFailure 的用法。
func XCTExpectFailure(
_ failureReason: String? = nil,
enabled: Bool? = nil,
strict: Bool? = nil,
issueMatcher: ((XCTIssue) -> Bool)? = nil
)
func XCTExpectFailure<R>(
_ failureReason: String? = nil,
enabled: Bool? = nil,
strict: Bool? = nil,
failingBlock: () throws -> R,
issueMatcher: ((XCTIssue) -> Bool)? = nil
) rethrows -> R
下面是一个使用了所有可选参数的示例:
func testDivisionByZero() throws {
let calculator = Calculator()
try XCTContext.runActivity(named: "Test division by zero") { _ in
XCTExpectFailure("Dividing by zero should throw an error",
enabled: false,
strict: true
) {
XCTAssertThrowsError(
try calculator.divide(10, by: 0),
"The error should be divideByZero"
)
} issueMatcher: { issue in
issue.compactDescription.contains("divideByZero")
}
}
下面是这些可选参数的含义:
enabled
被设置为 false
,这意味着该测试用例的“预期失败”标记将被禁用。这将导致该测试用例的所有断言都将被视为正常的测试断言,并将产生“实际失败”测试结果。
strict
被设置为 true
,这意味着测试框架将在处理“预期失败”测试结果时更加严格。具体来说,如果测试代码中存在未标记为“预期失败”的错误或异常,测试框架将抛出一个致命错误,而不是将测试结果标记为“实际失败”。这有助于确保测试代码在预期的条件下失败,并防止测试结果被错误地标记为“实际失败”。
issueMatcher
:可选的闭包,用于自定义“预期失败”测试结果的匹配规则。默认值为 nil
,这意味着测试框架将使用默认的匹配规则。
跳过测试
XCTSkip
即可跳过当前测试用例。跳过测试的使用场景一般是当前的代码没有测试通过,但是我目前没有时间来修复bug,先做其他的测试。
以前的方法是将用例禁用到,可以用快捷键Cmd + Shit + >
或者菜单Product
,进入 编辑Test Plan
界面。
下面两个是增加了条件的跳过测试方法。
- XCTSkipIf:跳过符合条件的测试
func testThatViewAppearsCorrectOnIPad() throws {
try XCTSkipIf(UIDevice.current.userInterfaceIdiom != .pad, "Skipping test. Test only applicable for iPad devices")
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 320, height: 540))
view.adjustSize()
XCTAssertEqual(view.frame.size, CGSize(width: 500.0, height: 500.0))
}
- XCTSkipUnless:除非符合条件,否则都跳过测试
func testThatViewAppearsCorrectOnIPad() throws {
try XCTSkipUnless(UIDevice.current.userInterfaceIdiom == .pad, "Skipping test. Test only applicable for iPad devices")
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 320, height: 540))
view.adjustSize()
XCTAssertEqual(view.frame.size, CGSize(width: 500.0, height: 500.0))
}
总结
以上是一些常见的XCTest断言及其使用例子,XCTest还提供了许多其他断言来帮助测试不同类型的对象和条件。