您好,欢迎来到尔游网。
搜索
您的当前位置:首页关于全新单元测试框架Swift Testing的知识,以及与XCTest的比较

关于全新单元测试框架Swift Testing的知识,以及与XCTest的比较

来源:尔游网

📝 面试求职:  ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)

📝 职场经验干货:


大家好!我叫 Cyan,是 Mercoin iOS 团队的成员之一,我想分享一些关于 Swift 测试的经验。

个人认为 Swift Testing 比XCTest更加容易使用,功能也更加完善。

Swift Testing 是 Apple 在今年的WWDC24上推出的全新单元测试框架。它旨在成为广为使用的 XCTest 框架的后继者。Swift Testing 只能从 Xcode 16 开始使用,因此如果您的团队尚未更新项目,那么现在是时候更新了 🙂

属性和宏(Attributes and Macros)

@test

当我们使用XCTest时,我们会test在函数名的开头添加,以将该函数作为测试用例。

import XCTest
func test_defaultValue() {
    // ...
}

但是对于 Swift 测试,我们不需要添加test而是使用@Test属性。 

import Testing
@Test func defaultValue() {
    // ...
}

与 XCTest 的测试功能相同,我们仍然可以在测试中添加async、throws和@MainActor。

#expect

此宏用于实际执行检查。它与 XCTest 的XCAssert函数相同。不过,使用 XCTest 进行 Swift 测试的一个关键区别是,我们不需要针对检查的不同情况使用特定的函数。

对于 XCTest,我们可以使用以下所有这些函数:​​​​​​​

XCTAssert, XCTAssertTrue, XCTAssertFalse
XCTAssertNil, XCTAssertNotNil
XCTAssertEqual, XCTAssertNotEqual
XCTAssertIdentical, XCTAssertNotIdentical
XCTAssertGreaterThan, XCTAssertGreaterThanOrEqual
XCTAssertLessThan, XCTAssertLessThanOrEqual

然而,在 Swift 测试中,你可以像这样进行操作:​​​​​​​

#expect(amount == 5000)
#expect(user.name == "Hoge")
#expect(!array.isEmpty)
#expect(numbers.contains(1))
#expect(paymentAmount > 0)

我们只需要传递一个表达式给#expect,这样更简单,也更容易记住。

#require

当你想要有一个必需的期望时,可以使用此宏。这意味着,当此测试用例失败时,整个测试将停止并失败。

try #require(date.isValid)  // ← if it fails here...
#expect(date, Date(timeIntervalSince1970: 0))  // ← then this is not executed

此外,当你想要解开可选值时也可以使用它,并在所述可选值为时停止测试nil。

let method = try #require(paymentMethods.first)  // ← if .first is nil...
#expect(paymentMethods.isCreditCard)  // ← then this is not executed

特质(Traits

这些是 Swift Testing 中的新功能,它们提供了更简单的方法来自定义我们的单元测试。引入了许多特征,因此我尝试将它们分为 3 类,以便于记忆:

细节相关特质

显示名称

此 trait 允许我们为测试用例添加名称。当然,我们可以从函数名称中知道测试用例的作用,但如果我们使用此 Display Name trait,则可以更容易理解它,因为我们可以在其上添加空格。​​​​​​​

@Test("Check default value when there’s a plan")
func defaultValueWithPlan() {
    let dependency = Dependency(plan: 1000)
    #expect(selectedAmount == 1000)
}

Trait .bug

如果在修复特定错误后添加了所述测试用例,则此特性允许我们链接问题。​​​​​​​

@Test(.bug("example.com/issues/123", "Check default value when there’s no plan")func defaultValueWithNoPlan() throws {
    …
    let firstAmountOption = try #require(amounts.first)
    #expect(selectedAmount == firstAmountOption)
}

Trait .tags

此特性允许我们向测试用例添加标签,并能够在 Xcode 的左侧面板上看到它,以便更轻松地组织测试用例。首先,我们必须有一个 Tag 扩展来添加我们想要的标签。​​​​​​​

extension Tag {
    @Tag static var formatting: Self
    @Tag static var location: Self
    @Tag static var playback: Self
    @Tag static var reviews: Self
    @Tag static var users: Self
}

然后,你可以像这样使用它:​​​​​​​

struct SwiftTestingDemoTests {    @Test(.tags(.formatting)) func rating() async throws {
        // add #expect here
    }
    …
    @Test(.tags(.location)) func getLocation() async throws {
        // add #expect here
    }
    …
    @Test(.tags(.reviews)) func addReviews() async throws {
        // add #expect here
    }
}

你会看到类似这样的内容:

你可以将测试分组到一个套件中,然后在该套件上添加标签。这会将标签添加到该套件内的所有测试中。​​​​​​​

@Suite(.tags(.defaultValue))  // ← add .tags herestruct SelectedAmountDefaultValue {
    @Test func defaultValueWithPlan() async throws {
        …
    }

    @Test func defaultValueWithNoPlan() async throws {
        …
    }
}

我稍后会分享更多关于 Suites 的信息 🙇

条件相关特征

Trait .enabled

此特性允许我们指定是否要运行测试用例的条件。​​​​​​​

@Test(.enabled(if: FeatureFlag.isAccordionEnabled))func defaultValueAccordionState() {
    // ...
}

Trait.disabled

此特性允许我们无条件禁用测试。当您的项目中存在不稳定的测试并导致延迟时,这可能会很有用。​​​​​​​

@Test(.disabled("Due to flakiness"))func flakyTestExample() {
    // ...
}

Trait @available

此特性允许我们添加一个条件,根据操作系统版本来决定是否运行测试。​​​​​​​

@Test@available(macOS 15, *)
func caseForFunctionThatUsesNewAPIs() {
    // ...
}
提示:Apple 建议使用@available而不是在运行时检查#available。​​​​​​​
// ✖︎ Avoid checking availability at runtime using #available@Test func caseForFunctionThatUsesNewAPIs() {
    guard #available(macOS 15, *) else { return }

    // ...
}

// ⚪︎ Prefer @available attribute on test function
@Test
@available(macOS 15, *)
func caseForFunctionThatUsesNewAPIs() {
    // ...
}

行为相关特征

Trait .timeLimit

此特性允许我们为测试用例添加时间。如果你不希望某个函数的运行时间超过某个时间阈值,则此特性非常有用。​​​​​​​

@Test(.timeLimit(.minutes(5)))func someMethod() {
    // ...
}

Trait .serialized

此特性允许我们按顺序运行套件中的测试,而不是同时运行所有测试。​​​​​​​

@Suite(.serialized)struct SelectedAmountDefaultValue {
  @Test func defaultValueWithPlan() {
      ...
  }
  @Test func defaultValueWithNoPlan() {
      ...
  }
}

现在我们已经讨论了特征,让我们继续讨论一些可以在 Swift 测试中使用的技巧和窍门。

配对特质(Pairing Traits

你还可以在一个测试用例中使用多个特征。​​​​​​​

@Test(  .disabled("Due to a crash"),
  .bug("example.org/bugs/123", "Crashes at <symbol>")
)
func testExample() {
    // ...
}

套件(Suites)

你可能已经注意到,本文多次提到了套件。基本上,套件是一组测试功能。

 使用@Suite注释。

  • 可以存储实例属性。

  • 也可以分别使用init和deinit进行设置和拆除逻辑。

  • 每个@Test函数实例初始化一次。​​​​​​

@Suite(.tags(.defaultValue))struct SelectedAmountDefaultValueNilPlanTests {
    let dependency = Dependency(plan: nil)

    init() throws {
        ...
    }

    deinit {
        ...
    }

    @Test("Check when there’s initial amount")
    func withInitialAmount() {
        // #expect…
    }

    @Test("Check when there’s no initial amount")
    func withNoInitialAmount() {
        // #expect…
    }
}

参数化测试

当你有一些重复测试时,你可以使用参数化@Test函数。重复测试的一个示例如下:​​​​​​​

// ✖︎ not recommendedstruct CryptoCurrencyTests {

    @Test func includesBTC() async throws {
        let data = try await GetData()
        let currency = try #require(data.first(where: { $0 == "BTC" } ))
        #expect(currency == “BTC”)
    }

    @Test func includesETH() async throws {
        let data = try await GetData()
        let currency = try #require(data.first(where: { $0 == "ETH" } ))
        #expect(currency == “ETH”)
    }

    // ...and more, similar test functions
}

当然,你可以使用for…in循环来重复测试,但不建议这样做。​​​​​​​

// ✖︎ also not recommended - using a for…in loop to repeat a test@Test func includesCryptoNames() async throws {
    let cryptoNames = [
        "BTC",
        "ETH",
        "CryptoA",
        "CryptoB",
    ]

    let data = try await GetData()
    for cryptoName in cryptoNames {
        let currency = try #require(data.first(where: { $0 == cryptoName } ))
        #expect(currency == cryptoName)
    }
}

让我们尝试使用参数化测试函数!将其更改为参数化@Test函数将是这样的:​​​​​​​

// ⚪︎ recommendedstruct CryptoCurrencyTests {
    @Test("Check master contains the correct cryptos", arguments: [
        "BTC",
        "ETH",
        "CryptoA",
        "CryptoB",
    ])

    func includes(cryptoName: String) async throws {
        let data = try await GetData()
        let currency = try #require(data.first(where: { $0 == cryptoName } ))
        #expect(currency == cryptoName)
    }
}

通过命令行运行Swift Testing

就像 XCTest 一样,我们也可以在命令行中使用 Swift Testing,以便它可以在具有 CI/CD 的项目中使用。请使用以下命令:

swift test

从 XCTest 迁移

实际上,我们可以将 Swift Testing 与 XCTests 一起使用。当我们有类似的 XCTests 时,我们可以将它们合并为一个参数化@Test函数。最后,test从测试用例的名称中删除。

结论

就我个人而言,我更喜欢 Swift Testing,而不是 XCTest。与 XCTest 相比,Swift Testing 改进了很多,并且比以前更容易创建单元测试。Swift Testing 只能从 Xcode 16 开始使用,因此如果你尚未更新项目以使用 Xcode 16,你可能需要等待一段时间才能开始使用 Swift Testing。

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- axer.cn 版权所有 湘ICP备2023022495号-12

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务