用接地气的例子趣谈 WWDC 24 全新的 Swift Testing 入门(二)

概述

从 WWDC 24 开始,苹果推出了全新的测试机制:Swift Testing。利用它我们可以大幅度简化之前"老态龙钟"的 XCTest 编码范式,并且使得单元测试更加灵动自由,更符合 Swift 语言的优雅品味。

在这里我们会和大家一起初涉并领略 Swift Testing 的测试之美。

在本篇博文中,您将学到如下内容:

    1. @Test 宏
  • 3.1 按条件启用和禁用测试
  • 3.2 使用 Tag 逻辑分组测试方法
  • 3.3 使用 arguments 避免重复测试

测试为先,质量为王!无测试,不软件!

那还等什么呢?Let's testing!!!;)


3. @Test 宏

从上篇的示例代码中,我们应该可以体会到新 Swift Testing 的些许妙处。接下来我们着重谈谈其中几个常用的宏。

首先是 @Test 宏,我们可以使用它将任意方法变为可测试方法。我们还可以在类型(结构或类)内部使用它们以便将相关的测试"聚散成组":

swift 复制代码
struct ModelTests {
    
    var model = Model.shared
    
    @Test("测试创建 Itmes")
    mutating func createItems() {
        model.createItems()
        #expect(!model.items.isEmpty, "应该成功创建若干 Items!")
    }
    
    @Test("测试删除所有 Item")
    mutating func delItem(){
        model.createItems()
        #expect(!model.items.isEmpty)
        model.deleteAllItems()
        #expect(model.items.isEmpty, "应该成功删除所有 Items!")
    }
}

在上面的代码中,我们将两个测试方法放到 ModelTests 结构里,从左边侧边栏的内容可以看到,这使得它们形成了一个新的"测试组" ModelTests:

这样做的好处主要有两个:

  1. 将测试方法分门别类,便于管理;
  2. 可以将测试的构造和析构操作统一放到类型对象的析构和析构方法中去,避免冗余;

除此之外,利用 @Test 宏的不同重载形式,我们还可以实现一些有趣的测试特性。

3.1 按条件启用和禁用测试

我们可以利用 enabled 这个@Test ConditionTrait 来实现按指定条件开启和禁止对应的测试方法。

如下代码所示,我们仅在 testSettings.needTestDeleting 属性值为 true 的情况下才进行 Item 的删除测试:

swift 复制代码
class TestSettings {
    static let shared = TestSettings()
    
    var needTestDeleting = false
}

let testSettings = TestSettings.shared

struct ModelTests {
    
    var model = Model.shared
    
    @Test("测试创建 Itmes")
    mutating func createItems() {...}
    
    // 仅在 testSettings.needTestDeleting 为真时使能 delItem 测试
    @Test("测试删除所有 Item", .enabled(if: testSettings.needTestDeleting))
    mutating func delItem(){
        
        model.createItems()
        #expect(!model.items.isEmpty)
        model.deleteAllItems()
        #expect(model.items.isEmpty, "应该成功删除所有 Items!")
    }
}

当对应条件值为假时,Xcode 会跳过对应的 delItem() 测试方法:

当然,如果我们希望的话还可以使用 disabled 条件彻底禁止某个测试方法:

swift 复制代码
@Test("测试删除所有 Item", .disabled("可能会导致系统挂起的找不到北"))
    mutating func delItem(){...}

3.2 使用 Tag 逻辑分组测试方法

除了通过结构或类来"物理的"组合测试方法以外,我们还可以通过标签(Tag)以逻辑的方式安排类似的测试。

为了达到这一目的,我们首先必须实现一个遵守 CodingKey 协议的实体类型:

swift 复制代码
struct MyCoding: CodingKey {
    
    var codingValue: String
    
    init?(intValue: Int) {
        codingValue = "\(intValue)"
    }
    
    init?(stringValue: String) {
        codingValue = stringValue
    }
    
    var stringValue: String {
        codingValue
    }
    
    var intValue: Int? {
        Int(codingValue)
    }
}

当然,我们也可以让现有类型(比如 String)遵守 CodingKey 协议来简化代码。

接着为 Tag 类型扩展我们自定义的标签:

swift 复制代码
extension Tag {
    static let createItem = Tag(codingKey: MyCoding(stringValue: "CreateItem")!)!
}

最后,在对应的测试方法上设置指定的 Tag 即可:

swift 复制代码
@Test(.tags(.createItem))
func checkItemsCount() async throws {
    var model = Model.shared
    model.createItems()
    #expect(model.items.count == 3)
}

struct ModelTests {
    
    var model = Model.shared
    
    @Test("测试创建 Itmes", .tags(.createItem))
    mutating func createItems() {...}
    
    @Test("测试删除所有 Item", .disabled("可能会导致系统挂起找不到北"))
    mutating func delItem(){...}
}

现在,我们可以按标签(例如 createItem)来组织和集中测试海量 Testing 方法啦:

3.3 使用 arguments 避免重复测试

@Test 还有一个非常有用的特性:简化测试重复代码。在下面的实现中我们就使用 arguments 特性向 mentionedContinentCounts 方法传入了若干测试参数,从而仅用一个测试方法就搞定了所有相似的测试:

swift 复制代码
struct VideoContinentsTests {

    @Test("Number of mentioned continents", arguments: [
        "A Beach",
        "By the Lake",
        "Camping in the Woods",
        "The Rolling Hills",
        "Ocean Breeze",
        "Patagonia Lake",
        "Scotland Coast",
        "China Paddy Field",
    ])
    func mentionedContinentCounts(videoName: String) async throws {
        let videoLibrary = try await VideoLibrary()
        let video = try #require(await videoLibrary.video(named: videoName))
        #expect(!video.mentionedContinents.isEmpty)
        #expect(video.mentionedContinents.count <= 3)
    }
}

限于篇幅,关于 Swift Testing 参数化测试(Refactor several similar tests into a parameterized @Test function)的介绍这里就不再展开说明了。

想要详细了解相关内容的小伙伴们请到之前列出的 WWDC 24 官方 Testing 开发视频中进一步观赏吧。

在下一篇博文中,我们将介绍 Swift Testing 另一个不可或缺的宏:#expect,不见不散!

总结

在本篇博文中,我们讨论了 Swift Testing 测试中至关重要的宏:@Test,我们随后还分别介绍了它的 3 种重载形式,小伙伴们值得拥有。

感谢观赏,我们下一篇见!8-)

相关推荐
season_zhu16 小时前
iOS开发:关于日志框架
ios·架构·swift
RainbowJie118 小时前
Spring Boot 使用 SLF4J 实现控制台输出与分类日志文件管理
spring boot·后端·单元测试
iOS阿玮19 小时前
苹果2024透明报告看似更加严格的背后是利好!
uni-app·app·apple
大熊猫侯佩20 小时前
SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效
swiftui·swift·apple
大熊猫侯佩1 天前
SwiftData 共享数据库在 App 中的改变无法被 Widgets 感知的原因和解决
swiftui·swift·apple
大熊猫侯佩1 天前
使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用
数据库·swift·apple
大熊猫侯佩1 天前
SwiftUI 在 iOS 18 中的 ForEach 点击手势逻辑发生改变的解决
swiftui·swift·apple
iOS阿玮2 天前
苹果审核被拒4.1-Copycats过审技巧实操
uni-app·app·apple
大熊猫侯佩2 天前
SwiftUI 如何取得 @Environment 中 @Observable 对象的绑定?
swiftui·swift·apple