用于辅助功能体验测试的文本格式化快照
语言切换:한국어 (韩语).
辅助功能用户体验是单元测试的最佳切入点。AXSnapshot
让它变得非常简单。
func testMyViewController() async throws {
let viewController = MyViewController()
await viewController.doSomeBusinessLogic()
XCTAssert(
viewController.axSnapshot() == """
------------------------------------------------------------
Final Result
button, header
Double Tap to see detail result
Actions: Retry
------------------------------------------------------------
The question is, The answer to the Life, the Universe, and Everything
button
------------------------------------------------------------
The answer is, 42
button
------------------------------------------------------------
"""
)
}
如果你的项目使用 CocoaPods,请将 pod 添加到 Podfile 中任何适用的测试目标
target 'MyAppTests' do
pod 'AXSnapshot'
end
如果你想在任何使用 SwiftPM 的其他项目中使用 AXSnapshot,请将该包添加为 Package.swift 中的一个依赖项
dependencies: [
.package(
url: "https://github.com/banksalad/AXSnapshot.git",
from: "1.0.2"
),
]
接下来,将 AXSnapshot 添加为你的测试目标的一个依赖项
targets: [
.target(name: "MyApp"),
.testTarget(
name: "MyAppTests",
dependencies: [
"MyApp",
.product(name: "AXSnapshot", package: "AXSnapshot"),
]
)
]
许多人认为 UI 层很难进行单元测试。
例如,如果你有如下所示的 ViewController,
class MyViewController {
private let headerView = MyHeaderView()
private let contentView = MyContentView()
}
你不能像下面这样测试它
func testMyViewController() async throws {
let viewController = MyViewController()
let viewModel = MyViewModel()
viewController.bind(with: viewModel)
await viewModel.doSomeBusinessLogic()
// This Test cannot be build, because `headerView` property is private
// To build this test, you have to give up some of encapsulation
XCTAssert(viewController.headerView.headerText == "Final Result")
}
因为即使使用 @testable import
注释,大多数可以测试的属性都无法访问。
此外,即使你将属性的访问级别更改为 internal
,仍然存在一些问题。 例如,如果你想进行快速重构,更改属性的名称,即使最终用户可以感知到的规范没有变化,测试也必须更改。 多么烦人!
但是,如果你测试 Accessibility Experience
(辅助功能体验),而不是视觉 UI 层的实现细节,那么大多数这些问题都可以解决。 因为无论视觉 ui 层的实现如何,任何 view/viewController 最终都可以表示为 accessibilityElements 的一维列表
。 了解这意味着什么的最佳方法是在 VoiceOver 中使用 项目选取器
,并使用“双指,三击”手势。
此外,如果你使用像下面这样的“MVVM”模式
你很可能会测试 ViewModel
,因为通过测试 ViewModel
,你不仅可以测试 ViewModel
,还可以测试 Model
以及 Model-ViewModel 之间的绑定,如下图所示。
这种策略有效,但它仍然无法覆盖 View-ViewModel 之间的绑定
,其中可能会发生许多潜在的错误。
但是,如果你测试 UIView/UIViewController
的 Accessibility Experience
(辅助功能体验),你可以将测试覆盖范围扩展到 View-ViewModel 之间的绑定
,以及至少一部分 View
逻辑。
最后但并非最不重要的是,辅助功能很重要。 辅助功能不是锦上添花。 这是底线。 如果你的应用程序对任何人都无法访问,那么你的应用程序根本就没有为生产做好准备。 因为你的任何用户,无论其是否有残疾,都与任何其他用户一样重要,因为他们都和任何人一样是人。
但是,在手动测试期间,辅助功能仍然是最容易被忽略的事情之一。 很难期望你所有的测试人员、同事都熟悉 VoiceOver。 更难期望你的项目继任者熟悉辅助功能。
因此,为了确保在相当长的一段时间内辅助功能没有倒退,自动测试它非常重要。
当一个 UIView
是整个响应链中的第一个accessibilityElement(辅助功能元素)时,它可以暴露给辅助技术。
因此,考虑到这个概念,我们可以构建像这样的 isExposedToAssistiveTech
逻辑
extension UIResponder {
var isExposedToAssistiveTech: Bool {
if isAccessibilityElement {
if allItemsInResponderChain.contains(where: { $0.isExposedToAssistiveTech }) == true {
return false
} else {
return true
}
} else {
return false
}
}
}
这就是要点!
剩下的就是遍历所有的 UIView 树,并过滤掉那些 exposedToAssistiveTech
的视图,并格式化它为辅助技术(例如accessibilityLabel)的信息
public extension UIView {
/// Generate text-formatted snapshot of accessibility experience
func axSnapshot() -> String {
let exposedAccessibleViews = allSubViews().filter { $0.isExposedToAssistiveTech }
let descriptions = exposedAccessibleViews.map { element in
// Do some formatting on each element
element.accessibilityDescription
}
// Do some formatting on whole `descriptions`
return description
}
每个项目的默认格式化行为在generateAccessibilityDescription 闭包中声明。 要自定义格式化行为,你可以随时替换此闭包!
MIT License
Copyright (c) 2022 Banksalad Co., Ltd.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.