哎呀,UnIt 真是太强大了
🔥 🔥 🔥 —— 匿名
单元测试非常适合测试业务逻辑和模型。然而,当您尝试测试特定视图的逻辑时,由于样板代码、钻过多个视图层或 UIKit 懒加载,事情会变得有点繁琐。 UnIt 通过对你的最喜欢的 UIKit 类(如 UILabel、UIViewController 等)进行有用的扩展,试图缓解一些这种痛苦!通过让你的 UI 能够通过单元测试轻松验证,我们可以在被我们的 QA 部门嘲笑之前捕捉到视觉错误 :(
警告
此库使用魔术般的换油技巧(捕获约束)进行其中一个扩展。请注意,Apple 可以随时更改此扩展更换的 Objective-C 私有方法。此外,如果您将此框架包含在应用程序目标中,您很可能会被 App Store 拒绝。建议将此框架仅保留在测试目标中。
删除 UIViewController 样板代码
在单元测试中创建视图控制器时,UIKit 决定懒散地工作,尽可能少做工作。在单元测试中,视图生命周期方法 viewDidLoad、viewWillAppear、viewDidAppear 未必会被调用。这导致开发者做了诸如获取关键窗口、将视图控制器附加到关键窗口并使其成为关键和可见等事情——真糟糕。
我们可以使用这个扩展应用到 UIViewController
func runViewLifecycle(for device: Device)
例子
beforeEach {
// Instantiate view controller (subject) with your custom instantiation code.
subject.runViewLifecycle(for: Device.iPhoneXS)
}
此函数确保视图控制器视图生命周期在给定的 iOS 设备尺寸上正常运行。
寻找UI元素
假设我们想在测试中检查我们的视图控制器是否有一个文本为“Toronto Raptors”的表格视图单元格。这就是我们以传统方式做的。
it ("should have a table view cell that contains the text 'Toronto Raptors'") {
expect(subject.tableView.visibleCells[0].textLabel?.text).to(equal("Toronto Raptors"))
}
虽然我们为这个测试写了一行,但这行代码犯了什么错?需要钻多个视图层,并且需要对视图控制器的实现有深入的了解。
相反,我们可以使用我们的UIView扩展。
func firstVisibleTableViewCell(with text: String) -> UITableViewCell?
让我们再次写我们的测试
it ("should have a table view cell that contains the text 'Toronto Raptors'") {
expect(subject.view.firstVisibleTableViewCell(with: "Toronto Raptors")).notTo(beNil())
}
这同样是一行代码,但我们解决了之前例子中犯下的严重错误。此外,它读起来更像是一次对话,更容易理解函数的请求——“在主题视图的第一个表格视图单元格中找到包含文本'Toronto Raptors'。”。
还有更多适用于UILabel、UIButton和UICollectionViewCell的便利扩展方法。如果你想要进行视图子类搜索,可以使用这个方法。
func firstView<T: UIView>(ofType type: T.Type, passing test: (T) -> Bool) -> T?
例子
return myView.firstView(ofType: MyCustomView.self, passing: { $0.titleLabel.text == "Drake is the greatest" && $0.backgroundColor = UIColor.blue } )
上面的例子说明,在myView中,返回第一个标题为“Drake is the greatest”且背景颜色为蓝色的MyCustomView类子视图。
捕捉冲突约束
约束冲突是iOS开发者最不喜欢的五种经历之一。我们只能看到系统日志中所有冲突的约束,它羞辱我们,很难阅读。
我们可以使用我们的UIViewController扩展。
public var conflictingConstraints: [[NSLayoutConstraint]]
例子
context("When the view controller has finished laying out") {
it("should have no conflicting constraints") {
expect(subject.conflictingConstraints).to(beEmpty())
}
}
它可能并不容易阅读,但我们可以让我们的单元测试/持续集成捕捉到冲突约束,这使得它非常方便。
注意:为了捕捉约束,我们参与了偷工减料的技术。请自行承担风险。Apple可能随时更改或弃用私有方法。
捕捉重叠
你有多少次遇到这样的问题:你的应用在一个屏幕尺寸上看起来很棒,但在较小的屏幕尺寸或开启了辅助功能后,看起来像是重叠、杂乱的一团?
我们可以使用我们的UIViewController扩展。
public func overlappingSubviews(whiteList: [UIView]) -> [Set<UIView>:CGRect]
例子
context("When the view controller has finished laying out") {
it("should have no views that overlap") {
expect(subject.overlappingSubviews()).to(beEmpty())
}
}
这个功能检查视图控制器的子视图以确定是否有重叠的视图。我们得到的是一个字典,其中每个键是重叠视图的一对,值是它们在屏幕坐标空间中重叠的CGRect。这是一个智能函数,它会根据以下参数过滤视图:
- 视图显然与父视图重叠,因此不应包括重叠部分。
- 如果视图C与视图B重叠,但视图A(视图C的父视图)也与视图B重叠,则只记录视图A和视图B之间的重叠部分。
- 忽略来自白名单参数的视图的重叠。
还有另一个具有相同功能的扩展,它工作于UIView,如果您想检查某个视图内的重叠。
捕获文本截断
有时我们的标签并不像我们想象的那样布局,尤其是在较小的屏幕尺寸上。
我们可以在UILabel上使用我们的扩展。
var isTruncated: Bool
捕获越界视图
当自动布局或我们的帧逻辑没有按照我们期望的方式运行时,有时视图不会正确定位。
我们可以在UIViewController上使用我们的扩展。
public func viewsOffScreen() -> [UIView]
public func viewsPartiallyOffScreen() -> [UIView]
public func viewsEntirelyOffScreen() -> [UIView]
例子
context("When the view controller has finished laying out") {
it("should have no views that are out of bounds") {
expect(subject.viewsOffScreen()).to(beEmpty())
}
}
还有另一个具有相同功能的扩展,它工作于UIView,如果您想检查某个视图内的越界子视图。
模拟用户行为
我们有一些扩展可以模拟用户与iOS应用程序的交互。
UITextField的扩展,按字符逐个输入字符串
func type(with text: String)
例子
context("When the user types '1a2b3c' into a text field that only accepts numbers") {
beforeEach {
subject.numericTextField.type(with: "1a2b3c")
}
it("should have text: '123'") {
expect(subject.numericTextField.text).to(be("123"))
}
}
UITextField的扩展,粘贴字符串
func paste(with text: String)
例子
context("When the user pastes '1a2b3c' and then '12345' into a text field that only accepts numbers") {
beforeEach {
subject.numericTextField.paste(with: "1a2b3c")
subject.numericTextField.paste(with: "12345")
}
it("should have text: '12345'") {
expect(subject.numericTextField.text).to(be("12345"))
}
}
UIControl的扩展,模拟点击
func tap()
适用于UIButton、UITableViewCell等!
安装
Cocoapods
要在iOS应用中使用UnIt,请将UnIt添加到Podfile中,并在Podfile中添加use_frameworks!
行以启用CocoaPods对Swift的支持。您应该在Podfile中仅将其添加到单元测试目标中。
# Podfile
use_frameworks!
target 'MyUnitTests' do
pod 'UnIt'
end
然后运行pod install
。
Carthage
要在您的应用中使用Carthage,请将UnIt添加到Cartfile中。
github "connected-io/UnIt"
- 运行
carthage update
。 - 进入
Carthage/Build/iOS
文件夹,并将UnIt框架添加到您的测试目标的"链接二进制与库"构建阶段中。 - 在您的测试目标中,点击左上角的+按钮,选择"新建复制文件阶段"。
- 设置"目标"为"框架",并添加UnIt框架。确保启用"复制时签名"。
维护者
- [email protected] - Jonathan Yeung
- [email protected] - Steven Wu
来自开源库