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

概述

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

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

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

  • 概述
  • [4. #expect 宏](#expect 宏)
  • [5. #require 宏](#require 宏)
  • [6. Swift Testing 目前的不足](#6. Swift Testing 目前的不足)
  • 总结

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

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


4. #expect 宏

如果说前面介绍的 @Test 宏为测试奠定了基调,那么 #expect 宏则无疑是当之无愧的测试核心所在。

在以往"陈言老套"的 XCTest 体系中,我们往往需要用超多 XCTAssertXXX 之类的方法来验证测试通过与否:

记住这些"聚蚊成雷"般的方法可不是件容易的活。不过这一切在 Swift Testing 都不是事儿了,因为不管现实如何千变万化我们都只需一个 #expect 宏即可统统搞定!

swift 复制代码
import Testing

func add(_ a: Int, _ b: Int) -> Int {
    a + b
}

@Test func verifyAdd() {
    let result = add(1, 2)
    #expect(result == 3)
}

上面是 #expect 宏最简单的形式,即验证结果是否为真。如此这般,多个 #expect 连续助攻自然也是不在话下:

swift 复制代码
@Test func verifyMagicNumber() {
    let number = magicNumber()
    
    #expect(number != 0)
    #expect(number > 10)
    #expect(number <= 100)
}

我们还可以利用 #expect 来验证被测试方法是否抛出错误、以及是否抛出指定错误:

swift 复制代码
enum MyError: Error {
    case invalidInput
}

func throwingFunction() throws {
    throw MyError.invalidInput
}

@Test func verifyThrowingFunction() {
    #expect(throws: MyError.self) {
        try throwingFunction()
    }
}

在上面的代码中,如果 throwingFunction 抛出 MyError 错误则表示测试通过!

我们还可以更进一步,细粒度控制方法抛出错误时的测试行为,比如仅当抛出指定 MyError.invalidInput 错误时才能使测试 Pass:

swift 复制代码
@Test func verifyThrowingFunction() {
    #expect {
        try throwingFunction()
    } throws: { error in
        guard let myError = error as? MyError else {
            return false
        }
        return myError == .invalidInput
    }
}

Swift Testing 中 #expect 的能耐远不止于此,更多它的使用介绍请小伙伴们参考苹果官方开发文档。

5. #require 宏

除了 #expect 之外,在 Swift Testing 中还有另一个不可或缺的"超级助手",那就是 #require 宏:

简单来说,#require 宏的精妙之处在于它可以让我们立即判断条件是否满足,并提早结束测试。具体来说,它会在测试可选值(Optional Value)为 nil (或者其它条件不满足)时让测试失败并抛出错误。

swift 复制代码
@Test func verifyOptionalFunc() throws {
    let result = optionalFunc()
    try #require(result != nil)
    
    #expect(result! > 0)
}

还拿之前博文中 Item 的"栗子"来说,我们可以写一个验证 Model 中第一个 Item 名称的方法,不过由于我们忘记了创建 Model 中的 items,所以下面的测试会在 #require 那行抛出错误,因为此时 items 数组为空:

swift 复制代码
@Test
func firstItemCheck() throws {
    let model = Model.shared
    let firstItem = try #require(model.items.first)
    #expect(firstItem.name == "大熊猫侯佩")
}

失败的测试结果如下图所示:

更进一步,我们还可以利用 Issue 对象来记录测试遇到的任何问题,它们都会在测试日志中分毫不差的反映出来:

swift 复制代码
@Test
func firstItemCheck() {
    let model = Model.shared
    
    do {
        let firstItem = try #require(model.items.first)
        #expect(firstItem.name == "大熊猫侯佩")
    } catch {
        Issue.record("Model 中没有任何数据,忘记创建了???")
    }
}

测试 firstItemCheck() 方法依旧失败,不过此时我们可以在 Xcode 控制台中看到具体失败的信息:

现在,借助于 Swift Testing 中这些宏的"古道热肠",我们编写的单元测试必将大放异彩、拔山盖世!棒棒哒!💯

6. Swift Testing 目前的不足

虽然 Swift Testing 较之以前的 XCTest 测试系统有诸多好处,但我们仍不能完全否定后者。而且, Swift Testing 还缺失测试中至关重要的一环:UI 测试。

对于 WWDC 23 中推出 Swift 宏(Macros)的单元测试,目前我们也无法使用 Swift Testing 来完成,这不能不说是一个遗憾。

这些问题在 Swift Testing 2.0 中是否能够解决?Swift Testing 能否在 WWDC 25 里从 XCTest 中完全凤凰涅槃呢?让我们拭目以待吧!


想要系统学习 Swift 的小伙伴们,请来我的《Swift语言开发精讲》专栏逛一逛哦:


总结

在本篇博文中,我们继续讨论了 Swift Testing 中另外两个非常重要的宏:#expect 和 #require,我们还顺带介绍了目前 Swift Testing 的一个"短板"。至此 Swift Testing 测试大冒险圆满落幕啦!

感谢观赏,再会啦!😎

相关推荐
半亩码田17 小时前
C#转Python第4.5篇:单元测试:pytest vs xUnit/NUnit
python·单元测试·c#
AINative软件工程19 小时前
LLM 应用的测试替身工程实践:用 Fake/Stub/Mock 让 AI 代码真正跑起来 CI
单元测试·llm·ai编程
Java小白笔记1 天前
Java 单元测试怎么写:JUnit 5、Mockito 与 Spring Boot 方法级实战
java·junit·单元测试
sanjiaomao3332 天前
从论文公式到Python实现:用单元测试校验滑动平均算法
python·算法·单元测试
梁辰兴5 天前
软件工程:单元测试
单元测试·软件工程·梁辰兴·‘设计原则·方法技术·tdd驱动·mock隔离
2601_962299888 天前
python脚本如何单元测试
python·单元测试·pytest·unittest·mock对象
牛油果子哥q9 天前
C++大型项目工程精讲:CMake完整实战、静态库&动态库、模块化拆分、单元测试、gdb调试、性能工具、工程踩坑全解
开发语言·c++·单元测试
leoZ23110 天前
AI+前端提效-09 AI赋能前端测试:单元测试、E2E测试自动生成,提升覆盖率
前端·人工智能·opencv·目标检测·数据挖掘·单元测试·语音识别
边吃番茄边敲代码13 天前
从本地工具到 MCP:给 Agent 接入独立工具服务,并补齐自动化测试
人工智能·python·功能测试·ai·单元测试·pytest