考虑以下结构
struct MainView: View {
// The `Model` the `User` shall be read from
@EnvironmentObject private var model: Model
var body: some View {
TabView {
AccountsOverview()
.tabItem {
Image(systemName: "rectangle.stack")
Text("Accounts")
}
TransactionOverview()
.tabItem {
Image(systemName: "list.dash")
Text("Transactions")
}
}
}
}
如何编写一个 XCTest 来测试第二个选项卡项
感谢您的意见!
您要测试/验证的东西是
View
并且要测试 View
应该使用 SnapshotTests
或 UITests
。在这里,我建议从 SnapshotTests
开始验证您的视图组件。
考虑以下是您要测试的观点
class Model: ObservableObject {}
struct MainView: View {
@EnvironmentObject private var model: Model
var body: some View {
TabView {
AccountsOverview()
.tabItem {
Image(systemName: "rectangle.stack")
Text("Accounts")
}
TransactionOverview()
.tabItem {
Image(systemName: "list.dash")
Text("Transactions")
}
}
}
}
struct AccountsOverview: View {
var body: some View {
Text("AccountsOverview")
}
}
struct TransactionOverview: View {
var body: some View {
Text("TransactionOverview")
}
}
现在有很多框架可以实现快照测试,但是你可以使用 PointFree 的 SnapshotTesting 库。参考Kodeco的这篇文章开始SnapshotTesting。
以下是截图测试给大家查看:
import XCTest
import SnapshotTesting
@testable import <your project target>
class MainViewSnapshotTests: XCTestCase {
func testExample() throws {
let view = MainView()
assertSnapshot(matching: view, as: .image)
}
}
ViewInspector 是用于单元测试 SwiftUI 的第三方库。我们可以找到第二个选项卡项
let tabItem = try MainView().inspect().tabView().view(TransactionOverview.self, 1).tabItem()
这证实了
MainView
包含一个TabView
。它寻找第二个孩子(即索引 1)并确认它是一个TransactionOverview
。然后它返回 TabItem
. 的可检查表示
然后我们可以在这个
TabItem
中搜索一个Text
并得到它的字符串:
let text = try tabItem.find(ViewType.Text.self).string()
最后,我们可以检查此文本是否匹配:
XCTAssertEqual(text, "Transactions")
检查图像有点棘手,因为没有办法问一个标准的
Image
,“你是用系统名称创建的吗?它是什么?”就其本身而言,Image
不可检查。
但是俗话说,我们可以通过引入一个额外的层来解决问题。让我们围绕
Image
定义我们自己的包装器。它会完全一样,但它会让我们检查系统名称:
struct ExaminableImage: View {
let systemName: String
var body: some View {
Image(systemName: systemName)
}
}
用它代替
Image
使其可测试。现在我们可以编写第二个测试。它以相同的方式开始,找到第二个选项卡项。由于这是重复的代码,让我们使用 Extract Method 重构来创建一个 helper:
private func secondTabItem() throws -> InspectableView<ViewType.ClassifiedView> {
try MainView().inspect().tabView().view(TransactionOverview.self, 1).tabItem()
}
现在我们的新测试在第二个选项卡项中寻找
ExaminableImage
。我们使用 find
就像我们在 Text
测试中所做的那样,因为我们不关心图像在内部指定的位置。然后我们使用 actualView()
将这个可检查的表示转换回实际视图。现在我们可以查询它的系统名称:
func test_secondTabItem_image() throws {
let image = try secondTabItem().find(ExaminableImage.self).actualView()
XCTAssertEqual(image.systemName, "list.dash")
}
为什么要这样做而不是快照测试?快照是确认外观与先前批准的图像匹配的好方法。但是为了使快照测试足够快以进行重构,它们必须逐像素匹配。如果出现以下情况,图像将不匹配:
这些差异通常很小。有一种方法可以对快照进行模糊匹配以忽略微小差异。但这会使快照测试慢得多。
我想要测试的原因是为了在经过验证的小步骤中启用重构。为此,测试必须尽可能快。在他们最快的逐像素匹配中,快照测试比常规单元测试慢一个数量级。它们通常仍可用于以该速度进行重构。但是模糊匹配将它们的速度降低了另一个数量级。
UITests 太慢了,它们是不可能的。它们也不允许您用假货来隔离系统的各个部分以便于测试。
ViewInspector 满足我的需求:测试快速稳定