大多数浏览器和
Developer App 均支持流媒体播放。
-
了解 Swift Testing
为你介绍 Swift Testing — 使用 Swift 测试代码的全新软件包。探索 Swift Testing 全新强大 API 的构建块,学习如何将它应用到常见测试流程中,并了解它与 XCTest 和开源 Swift 有何关联。
章节
- 0:00 - Introduction
- 0:59 - Agenda
- 1:20 - Building blocks
- 1:58 - Building blocks: @Test functions
- 3:07 - Building blocks: Expectations (#expect and #require)
- 6:02 - Building blocks: Traits
- 6:49 - Building blocks: @Suite types
- 8:34 - Building blocks: Designed for Swift
- 9:14 - Common workflows
- 9:29 - Common workflows: Tests with conditions
- 10:56 - Common workflows: Tests with common characteristics
- 13:13 - Common workflows: Tests with different arguments
- 17:35 - Swift Testing and XCTest
- 21:52 - Open source
- 23:29 - Wrap up
资源
- Adding tests to your Xcode project
- Forum: Developer Tools & Services
- Improving code assessment by organizing tests into test plans
- Running tests and interpreting results
- Swift Testing
- Swift Testing GitHub repository
- Swift Testing vision document
相关视频
WWDC24
-
下载
嗨!我叫 Stuart Montgomery 很高兴能为大家介绍 Swift Testing 质量和可靠性对于提供 出色的用户体验至关重要 而自动化测试则是 一种行之有效的办法 有助于长期实现并维持 软件质量 因此 我们于今年推出了 一套全新的工具 它可让 Swift 代码测试 比以往更为简便和强大 为你介绍 Swift Testing 这是一款使用 Swift 测试代码的全新开源软件包 其中包括用于 描述和组织测试的强大功能 它可在出现故障时提供 切实可行的详细信息 同时还可从容地 扩展到大型代码库
Swift Testing 专为 Swift 而设计 它融入了许多现代功能 比如并发和宏 同时它支持所有主流平台 包括 Linux 和 Windows 它还采用开放式开发流程 让你有机会 与社区中的其他成员 共同促进社区发展 在本次讲座中 我们首先会聊聊 Swift Testing 的构建块 它们也是你需要了解的核心概念 然后 我们会讨论 几个常见的工作流程 其中包括自定测试 或用不同参数来重复运行测试 我们将介绍 Swift Testing 与 XCTest 有何关联 最后 我们将讨论 这一新项目在开源社区中 所扮演的角色 首先 我们来了解一下 Swift Testing 的构建块 如果你以前从未为 App 编写过测试 第一步则需向你的项目 添加一个测试包目标 选择 File > New > Target
然后在 Test (测试) 部分搜索 Unit Testing Bundle
Swift Testing 现已成为 Xcode 16 中针对这个模板的 测试系统默认选项 只需为新目标选择一个名称 然后点按 Finish (完成) 就行了 不过 这个 App 已有一个测试目标 于是我们可在其中编写首个测试 首先 我们需导入 Testing 模块
然后 编写一个全局函数
接着 向它添加 @Test 属性
@Test 属性是第一个构建块 它表示某一函数是一项测试 添加属性后 Xcode 可对它进行识别 并在旁边显示一个运行按钮 测试函数就是普通的 Swift 函数 只是附带了 @Test 属性 这种函数可以是全局函数 也可以是某一类型的方法 如果需要 也可将测试函数 标记为 async 或 throws 或将它限制在某一全局 Actor 内 接下来 我们将通过填充 这个函数的主体 让这个测试对某些对象 进行实际验证 我们将通过这个测试来确保 某个视频文件的元数据 符合我们的预期 首先 对要检查的视频 及预期元数据 进行初始化
这时 我们会收到一个错误 因为 这些类型是在 App 的模块中声明的 因此 我们需要先导入这个模块
请注意 我们会对这个导入使用 小写的 @testable 属性
这是一个常规语言功能 它不属于 Swift Testing 但它可用于引用 访问级别为“内部”的 那些类型 接下来 我们要使用 #expect 宏 来确保视频元数据正确无误
#expect 宏会执行一个预期操作 而这便是 Swift Testing 的 第二个构建块
你可使用类似 #expect 宏的预期操作 来确保预期条件为 true 它支持普通表达式和语言运算符 同时它还可捕获源代码 以及失败时子表达式的值 现在 我们来首次运行测试 看看结果如何
看起来它失败了 红色 X 图标表明存在这个情况 我们可点按测试失败消息 然后选择 Show (显示) 来深入了解 这行代码存在哪些错误
结果视图显示了 与传递给 #expect 宏的表达式 有关的详细信息 其中还包括子值 通过展开元数据 我们可以比较它们的属性
duration 和 resolution 字段似乎都不相等 通过观察这里 我想到: Video 类型 或许并未在元数据初始化后将它载入 要解决这个问题 我们可以转到 分隔式编辑器中的 Video 构造器 然后确保已对这个属性赋值
现在 我们重新运行这个测试
成功了!太棒了 #expect 宏非常灵活 你可以传递任意表达式 包括运算符或方法调用 它在失败时也会显示详细结果 下面举几个例子 你可以使用 == 运算符 如果失败 系统就会捕获 并显示左右两侧的内容 你可以访问 .isEmpty 这一类的属性 你甚至还可对数组调用 .contains 这一类的方法 请注意这个错误会如何 自动显示 numbers 数组的内容 要完成这类任务 你无需学习专门的 API 只需使用 #expect 宏
有时 你可能想在预期操作 失败时提前结束测试 为此 你可以使用 #require 宏 必要预期操作与常规预期操作类似 但它们包含 try 关键字并且会在 表达式失败时抛出错误 从而导致测试失败 且无法继续进行 使用 #require 宏的 另一种方法是 以安全的方式尝试对 可选值进行解包 并在解包的值为 nil 时停止测试
本示例显示了如何使用 #require 宏 来访问某一集合的 .first 属性 然后检查这个元素的某一属性 “first”属性为可选属性 但我们测试的第二行代码 依赖于这个值 于是这个测试会提前停止 因为如果解包的值为 nil 继续运行就毫无意义 必要预期操作 是针对这个模式的绝佳工具
在将这个测试提交到项目之前 我们进一步明确一下测试用途 为此 我们需在 @Test 属性中 传递一个自定显示名称 这个名称随后将显示在 测试导航器内以及 Xcode 中的其他位置
显示名称是特征的一个示例 而它也是第三个构建块
特征有几个用途: 它们可添加关于测试的描述性信息 可以自定何时运行测试 以及是否运行测试 还可修改测试的运行方式
以下是几个示例 除了添加带显示名称的信息 还可参考相关错误或添加自定标签 如果在某些情况下只想运行测试 则可使用特征来进行控制 某些特征会影响 测试的实际运行方式 例如施加时间限制 或一次仅允许执行一个测试
我们已编写完第一个测试 现在我们再添加一个测试 来验证 Video 类型的其他方面 这一次 我们会用 Xcode 16 中的内置测试代码段 来快速添加一个空测试函数
我们将这个测试命名为 rating
在主体部分 和以前一样 我们会创建一个视频 然后使用 #expect 将 contentRating 设为默认值
如果能将这两个测试编入一组 效果会更好 这样我们就能在项目中 更轻松地找到它们 为此 可将它们 打包到一个结构体中 我们将它称为 VideoTests
完成这个操作后 这个层次结构 便会反映在测试导航器中 我们甚至还可通过点按 按组运行两个测试 包含测试的这种类型 被称为“测试套件” 而它也是第四个 即最后一个构建块 套件可用于对相关的 测试函数或其他套件进行分组 你可显式使用 @Suite 属性 对它进行标注 虽然系统本来就会将包含 @Test 函数或 @Suites 的任意类型 隐式视为 @Suite 套件可包含存储实例属性 并可使用 init 或 deinit 在每个测试之前或之后执行逻辑 对于 @Test 函数包含的每个实例 都会为它创建单独的 @Suite 实例 以免发生意外状态共享
这两个测试的启动方式相同: 它们用于创建视频 的第一行代码完全相同 由于这些测试位于一个套件中 我们可以将这行代码提取出来 封装成一个存储属性 来降低重复率 就像这样
现在 我们可以从第二个测试中 删除这行代码
由于每个测试函数都会 在所含套件类型的新实例上 进行调用 因此每个函数 都会有自己的视频实例 而绝不会意外共享状态
现在 我们来回顾一下这些构建块 我们讨论了测试函数、 预期操作、特征和套件 它们旨在带来自然流畅的 Swift 使用体验 具体通过下面几种方式:
测试函数支持 async/await 和 Actor 隔离 因此可与 Swift 并发无缝集成
预期操作也可使用 async/await 并且接受所有内置语言运算符
预期操作和特征均会利用 Swift 宏 从而允许查看详细的失败结果 并直接在代码中按测试指定信息
而套件则支持值语义 从而鼓励使用结构体来隔离状态
现在 我们将这些构建块 应用于测试中的某些常见问题 探讨一下解决这些问题的工作流程
我们将讨论对测试运行时间的控制 如何将具有共同点的测试关联起来 以及如何多次重复运行测试 但每次都使用不同的参数
首先是附带条件的测试
某些测试只应在某些情况下运行 例如在特定设备上或特定环境中 对于这类情况 你可应用条件特征 例如 .enabled(if:...)
你可向它传递一个条件 在测试运行前对这个条件进行评估 如果这个条件为 false 则会将这个测试标记为 skipped 其他时候 你可能希望永不运行某一测试 为此 你可使用 .disabled(...) 特征 相对于其他技巧 停用测试更为可取 例如注释掉测试函数 因为它会验证这个测试中的代码 是否仍在进行编译
.disabled (...) 特征 可接受一个注释 而你可用它来解释停用测试的原因 此外 注释始终会 显示在结构化结果中 因此它们可在 CI 系统中显示以供查看
通常 停用测试的原因在于 错误跟踪系统中所跟踪的某一问题 除注释之外 还可包含 .bug(...) 特征 以及任意其他特征 以便通过 URL 来引用相关问题 然后 你可在 Xcode 16 的 Test Report 中看到这个错误特征 点按就能打开相应的 URL
当测试的整个主体只能在 某些操作系统版本上运行时 你可为这个测试赋予 @available(...) 属性 以便控制测试会在哪些版本上运行 应使用 @available(...) 属性 而不是使用 #available 在运行时进行检查 @available(...) 属性可让测试库 了解某一测试需要满足 操作系统版本条件 以便在结果中更准确地体现这一点
接下来 我们来聊聊如何关联 具有共同特征的测试 即使它们位于不同的套件或文件中 Swift Testing 支持 为测试分配自定标签 我已开始在这个项目中使用标签 测试导航器会在底部显示所有标签
要查看其中每个标签应用到的测试 可改用新的 Group By:Tag 模式
现在 我们将一个标签应用到 之前编写的某一测试中
为此 我们可通过 @Test 属性 将标签特征添加到测试中
这个测试会验证 某些数据格式设置逻辑 而这个项目中已存在另一个测试 与格式设置有关
我们来将格式设置标签 同样添加到这个测试
一旦完成这个操作 它便会显示在 测试导航器中的相应标签下
我还编写了一个测试 它也用于验证数据格式设置 在这里 我也同样 为它添加一个标签
由于这两个测试都涉及 Video 信息的格式设置 于是我们将它们分入同一子套件中
现在 我们可将格式设置标签 上移到 @Suite 这样其中包含的所有测试 都会继承这个标签
最后 我们可从每个 @Test 函数中删除这些标签 因为它们现在已被继承
你可将标签与具有 共同特征的测试相关联 比如 你可将一个通用标签应用到 所有用于验证 特定功能或子系统的测试 这样一来 你便可运行附带 特定标签的所有测试 此外 它还允许你在 Test Report 中筛选测试 甚至还可查看相关洞察信息 例如 附带同一标签的多个测试 何时开始失败
标签自身可应用于不同文件、 套件或目标中的测试 它们甚至还可在多个项目之间共享
使用 Swift Testing 时 若要向测试计划添加或排除某些测试 最好使用这些测试的标签 而不是具体测试名称
为获得最佳结果 请针对每种情况始终使用 最适合的特征类型 并非每个场景都应使用标签 例如 如果你试图表述 某一运行时条件 则应像我们之前讨论的那样 使用 .enabled(if ...)
要进一步了解如何在 Xcode 中使用测试标签 请观看“利用 Swift Testing 进一步优化测试”
我想展示的最后一个工作流程 堪称我的最爱 那便是重复运行测试 但每次都用不同的参数 以下示例说明了 为何这种流程十分有用 在这个项目中 有几个测试会用于检查 不同视频所提及的大洲数量 其中每个测试都遵循类似的模式: 创建一个全新的 videoLibrary
按名称查找视频 然后使用 #expect 宏来检查 视频中提及的大洲数量
这些测试都可成功运行 但它们却极其重复 而为测试添加的视频越多 测试维护就越发困难 因为相关代码都是重复的 此外 使用这个模式时 我们还须为每个测试 提供一个唯一的函数名称 但这些名称又很难看懂 并且可能会与它们所测试的 视频名称无法同步 为解决这个问题 我们可利用名为参数化测试的功能 将所有这些测试编写为一项测试 下面 我们将第一个测试 转换为参数化测试
为此 第一步是 为测试签名添加一个参数
完成这个操作后会立即出现一个错误 告诉我们必须指定 要传递给这个测试的参数 于是 我们来修复这个问题
现在 我们来添加三个视频的名称
我喜欢将参数分配到多行 这样更方便查看 但你也可按个人喜好设置格式 最后一步则是用传入的参数 替换当前所查找 视频的名称
由于这个测试现在涵盖多个视频 于是我们来概括一下它的名称
这个测试的全名现在包含参数标签
但我们仍可按需为它指定 显示名称或其他特征 具体方法是在参数之前进行传递
现在 我们来运行这个测试 看看结果如何
太棒了!它成功了 而测试导航器 也会在其中显示各个视频 就好像它是一个单独的测试 有了这个结构 可以非常轻松地添加 更多参数并扩大测试覆盖范围 现在 我们将所有 其余视频添加到这个列表中 甚至还可添加新的视频
这时 我们可删除旧的 @Test 函数 因为它们已无需存在
接着 我们再运行一次这个测试 确保测试仍可成功
嗯 这次看来我们在 临近末尾时添加的 某个新视频导致了失败 通过点按这个参数 我们可看到相关详细信息 以及失败的预期操作
为调查这个问题 最好使用调试器重新运行这个测试 但为节省时间 我更想 只重新运行失败的那个参数 在 Xcode 16 中 我们现在可运行单个参数 具体方法是在测试导航器中 点按相应的运行按钮 但在执行这个操作之前 让我们 在测试的开头部分添加一个断点
现在 重新运行这个测试
调试器中显示的 videoName 为“Scotland Coast” 于是我们知道运行这个测试时 使用的正是我们关注的参数 这时 我们可继续进一步进行调试 确定失败的原因 从概念上讲 参数化测试 与使用 for…in 循环 多次重复执行的单个测试类似 我们来看一个示例: 这里有一个 videoNames 数组 它会循环遍历这些对象来执行测试 但是 像这样使用 for... in 循环存在一些缺点
参数化测试可让你在结果中 清晰看到每个参数的详细信息 这些参数可独立重新运行 以便实现更精细的调试 此外 通过并行运行每个参数 可以更高效地执行它们 于是你可以更快获得结果
参数化测试可用于 比这里看到的更高级的其他用例 例如 测试两组输入的所有组合 欢迎观看“利用 Swift Testing 进一步优化测试”来了解更多信息
每当你看到使用这个模式的测试时 最好都将它转换为参数化测试函数 只需向这个函数添加一个参数
去除 for...in 循环
再将参数上移到 @Test 属性 即可大功告成!
接下来 我们来聊聊 Swift Testing 如何与 XCTest 相互关联 如果你已编写过 XCTest 则可能想知道这一全新的 测试系统相比之下表现如何 或是如何迁移测试 Swift Testing 与 XCTest 有一些相似之处 但也有一些需注意的重要区别 为此 我们来比较一下 刚才介绍的三个构建块 即测试函数、预期操作和套件
XCTest 中的测试是指名称 以“test”开头的任意方法 但 Swift Testing 会使用 @Test 属性来显式表示它们 以免出现任何歧义
Swift Testing 支持更多种类的函数 因此你仍可使用 某一类型的多个实例方法 而如果你愿意 则还可使用 静态函数或全局函数 与 XCTest 不同 Swift Testing 支持特征 以便按测试或套件来指定信息
此外 Swift Testing 采用 不一样的方法来实现并行化: 它使用 Swift 并发在进程内运行 并支持 iPhone 和 Apple Watch 等物理设备
这两个系统之间的预期操作大不相同 XCTest 将这个概念称为“断言” 它会使用多个以 XCTAssert 开头的函数来表示这些断言 Swift Testing 则采用了另一种方法: 它只有两个基本宏: #expect 和 #require
无需众多专用函数 你可将普通表达式 和语言运算符传入 #expect 或 #require
例如 你可使用双等号来检查相等性 或使用大于运算符来比较两个值
此外 你可轻松使用 相反运算符来否定任意预期操作
在测试失败后停止测试 的处理方式也各不相同 在 XCTest 中 你需将 continueAfterFailure 属性赋值为 false 随后 之后失败的所有断言 都会导致这个测试停止
而在 Swift Testing 中 只需使用 #require 而不是 #expect 就可将任意预期操作变成必要操作 它会在失败时抛出错误 这样你便可选择 应通过哪些预期操作来停止这个测试 甚至还可随着测试的推进而交替使用
对于套件类型 XCTest 仅支持类 而且这些类必须继承自 XCTestCase
在 Swift Testing 中 你可以使用结构体、Actor 或类 我们建议使用结构体 因为它采用值语义 可以避免因意外状态共享 而导致的错误
套件或许可通过 @Suite 属性 进行显式表示 虽然它对于包含测试函数 或嵌套套件的任意类型来说 均为隐式表示 仅当指定显示名称 或其他特征时才需使用它
为方便在每个测试运行前执行逻辑 XCTest 提供了几个 setUp 方法 但 Swift Testing 则会使用 类型的构造器来实现这个目的 而且你可将它设为 async 或 throws
如需在每个测试后执行逻辑 则可包含析构器 析构器只能在套件类型 为 Actor 或类时使用 而这也是需要使用引用类型 而不是套件结构体 的最常见原因
最后 在 Swift Testing 中 你可通过嵌套类型 将测试分入多个子组
XCTest 测试和 Swift Testing 测试 可在单个目标中共存 因此如果你选择进行迁移 则可循序渐进地完成这个操作 而无需先创建一个新目标
迁移具有类似结构的 多个 XCTest 方法时 可将它全部合并到一个参数化测试中 就像我们刚才讨论的那样 对于仅附带一个测试方法的 任意 XCTest 类 都应考虑将它迁移到 全局 @Test 函数 而在命名测试时 开头已无需再使用“test”一词
但对于有些测试 需要继续使用 XCTest 其中包括使用 XCUIApplication 等 UI 自动化 API 的测试 或使用 XCTMetric 等 性能测试 API 的测试 因为 Swift Testing 不支持这些测试
对于只能用 Objective-C 编写的测试 也必须使用 XCTest 但是 可使用 Swift Testing 在 Swift 中编写测试 以便验证用其他语言编写的代码 最后 请避免从 Swift Testing 测试 调用 XCTest 断言函数 或是进行相反的操作 也就是 从 XCTest 调用 #expect 宏
请查看我们文档中的 “从 XCTest 迁移测试” 其中包含大量关于如何转换断言、 处理异步等待条件等操作的详细说明
我们已讨论了 Swift Testing 的各项功能 并展示了几种相关使用方法 而这只是拉开了这个新软件包的序幕 而我也很高兴它能在社区中不断发展 Swift Testing 现已开源 并托管在 GitHub 上 不久 它便会迁移到 最近公布的 swiftlang 组织 Swift Testing 适用于支持 Swift 并发的所有 Apple 操作系统 以及 Linux 和 Windows 系统 非常重要的一点是 它拥有一个通用代码库 可跨所有这些平台使用! 这是相对于 XCTest 的重大改进 因为 XCTest 有多种不同的实现形式 这意味着 你的测试 在切换到不同平台时 可实现更具一致性的表现 也会跨平台提供更出色的功能对等性
Swift Testing 现已集成到 Swift 生态系统中的 多个主流工具和 IDE 其中包括采用命令行的 Swift Package Manager 以及 Xcode 16 和附带最新版 Swift 扩展的 VS Code
现在 我们来看看 Swift Testing 的命令行体验 这是我创建的简易软件包 它使用了 Xcode 16 中提供的 New Package 模板 在终端中输入 swift test 就能运行这个软件包的测试
这个操作会触发 XCTest 测试 和 Swift Testing 测试同时运行 控制台会以彩色输出来显示 通过的结果和失败的结果 同时还会包含类似于 Xcode 中 所示消息的详细失败消息 Swift Testing 有一个开放的 功能建议流程 我们会在 Swift 论坛上 讨论这方面的进展 我们邀请你也参与进来 无论是编写或参与功能建议、 改进相关文档 还是提交错误报告 各种参与方式我们都热烈欢迎! 以上便是 Swift Testing 的 相关介绍 众多强大的功能 例如预期操作 和参数化测试都能助你提高代码质量 还可使用特征来定制测试 欢迎访问 GitHub 和相关论坛 与我们一道塑造这款软件包的未来 别忘了观看“利用 Swift Testing 进一步优化测试” 进一步了解优化测试的更多途径 非常感谢大家观看
-
-
1:54 - Write your first @Test function
import Testing @Test func videoMetadata() { // ... }
-
2:35 - Validate an expected condition using #expect
import Testing @testable import DestinationVideo @Test func videoMetadata() { let video = Video(fileName: "By the Lake.mov") let expectedMetadata = Metadata(duration: .seconds(90)) #expect(video.metadata == expectedMetadata) }
-
4:24 - Fix a bug in the code being tested
// In `Video.init(...)` self.metadata = Metadata(forContentsOfUrl: url)
-
6:06 - Add a display name to a @Test function
@Test("Check video metadata") func videoMetadata() { let video = Video(fileName: "By the Lake.mov") let expectedMetadata = Metadata(duration: .seconds(90)) #expect(video.metadata == expectedMetadata) }
-
6:58 - Add a second @Test function
@Test func rating() async throws { let video = Video(fileName: "By the Lake.mov") #expect(video.contentRating == "G") }
-
7:18 - Organize @Test functions into a suite type
struct VideoTests { @Test("Check video metadata") func videoMetadata() { let video = Video(fileName: "By the Lake.mov") let expectedMetadata = Metadata(duration: .seconds(90)) #expect(video.metadata == expectedMetadata) } @Test func rating() async throws { let video = Video(fileName: "By the Lake.mov") #expect(video.contentRating == "G") } }
-
8:04 - Factor a common value into a stored property in the suite
struct VideoTests { let video = Video(fileName: "By the Lake.mov") @Test("Check video metadata") func videoMetadata() { let expectedMetadata = Metadata(duration: .seconds(90)) #expect(video.metadata == expectedMetadata) } @Test func rating() async throws { #expect(video.contentRating == "G") } }
-
9:32 - Specify a runtime condition trait for a @Test function
@Test(.enabled(if: AppFeatures.isCommentingEnabled)) func videoCommenting() { // ... }
-
9:49 - Unconditionally disable a @Test function
@Test(.disabled("Due to a known crash")) func example() { // ... }
-
10:15 - Include a bug trait with a URL along with other traits
@Test(.disabled("Due to a known crash"), .bug("example.org/bugs/1234", "Program crashes at <symbol>")) func example() { // ... }
-
10:33 - Conditionalize a test based on OS version
@Test @available(macOS 15, *) func usesNewAPIs() { // ... }
-
10:42 - Prefer @available on @Test function instead of #available within the function
// ❌ Avoid checking availability at runtime using #available @Test func hasRuntimeVersionCheck() { guard #available(macOS 15, *) else { return } // ... } // ✅ Prefer @available attribute on test function @Test @available(macOS 15, *) func usesNewAPIs() { // ... }
-
11:22 - Add a tag to a @Test function
@Test(.tags(.formatting)) func rating() async throws { #expect(video.contentRating == "G") }
-
11:48 - Add another data formatting-related test with the same tag
@Test(.tags(.formatting)) func formattedDuration() async throws { let videoLibrary = try await VideoLibrary() let video = try #require(await videoLibrary.video(named: "By the Lake")) #expect(video.formattedDuration == "0m 19s") }
-
11:56 - Group related tests into a sub-suite
struct MetadataPresentation { let video = Video(fileName: "By the Lake.mov") @Test(.tags(.formatting)) func rating() async throws { #expect(video.contentRating == "G") } @Test(.tags(.formatting)) func formattedDuration() async throws { let videoLibrary = try await VideoLibrary() let video = try #require(await videoLibrary.video(named: "By the Lake")) #expect(video.formattedDuration == "0m 19s") } }
-
12:05 - Move common tags trait to @Suite attribute, so the suite's @Test functions will inherit the tag
@Suite(.tags(.formatting)) struct MetadataPresentation { let video = Video(fileName: "By the Lake.mov") @Test func rating() async throws { #expect(video.contentRating == "G") } @Test func formattedDuration() async throws { let videoLibrary = try await VideoLibrary() let video = try #require(await videoLibrary.video(named: "By the Lake")) #expect(video.formattedDuration == "0m 19s") } }
-
13:27 - Example of some repetitive tests which can be consolidated into a parameterized @Test function
struct VideoContinentsTests { @Test func mentionsFor_A_Beach() async throws { let videoLibrary = try await VideoLibrary() let video = try #require(await videoLibrary.video(named: "A Beach")) #expect(!video.mentionedContinents.isEmpty) #expect(video.mentionedContinents.count <= 3) } @Test func mentionsFor_By_the_Lake() async throws { let videoLibrary = try await VideoLibrary() let video = try #require(await videoLibrary.video(named: "By the Lake")) #expect(!video.mentionedContinents.isEmpty) #expect(video.mentionedContinents.count <= 3) } @Test func mentionsFor_Camping_in_the_Woods() async throws { let videoLibrary = try await VideoLibrary() let video = try #require(await videoLibrary.video(named: "Camping in the Woods")) #expect(!video.mentionedContinents.isEmpty) #expect(video.mentionedContinents.count <= 3) } // ...and more, similar test functions }
-
14:07 - Refactor several similar tests into a parameterized @Test function
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) } }
-
// Using a for…in loop to repeat a test (not recommended) @Test func mentionedContinentCounts() async throws { let videoNames = [ "A Beach", "By the Lake", "Camping in the Woods", ] let videoLibrary = try await VideoLibrary() for videoName in videoNames { let video = try #require(await videoLibrary.video(named: videoName)) #expect(!video.mentionedContinents.isEmpty) #expect(video.mentionedContinents.count <= 3) } }
-
17:15 - Refactor a test using a for…in loop into a parameterized @Test function
@Test(arguments: [ "A Beach", "By the Lake", "Camping in the Woods", ]) 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) }
-
22:47 - A newly-created Swift package with two simple @Test functions
import Testing @testable import MyLibrary @Test func example() throws { #expect("abc" == "abc") } @Test func failingExample() throws { #expect(123 == 456) }
-
22:56 - Running all tests for the package from Terminal
> swift test
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。