UnIt 0.0.5

UnIt 0.0.5

魏 Steven杨 Jonathan维护。



UnIt 0.0.5

  • 杨 Jonathan 和 魏 Steven

Platform Cocoapods Carthage compatible License: MIT

unit

哎呀,UnIt 真是太强大了🔥🔥🔥—— 匿名

单元测试非常适合测试业务逻辑和模型。然而,当您尝试测试特定视图的逻辑时,由于样板代码、钻过多个视图层或 UIKit 懒加载,事情会变得有点繁琐。 UnIt 通过对你的最喜欢的 UIKit 类(如 UILabelUIViewController 等)进行有用的扩展,试图缓解一些这种痛苦!通过让你的 UI 能够通过单元测试轻松验证,我们可以在被我们的 QA 部门嘲笑之前捕捉到视觉错误 :(

警告

此库使用魔术般的换油技巧(捕获约束)进行其中一个扩展。请注意,Apple 可以随时更改此扩展更换的 Objective-C 私有方法。此外,如果您将此框架包含在应用程序目标中,您很可能会被 App Store 拒绝。建议将此框架仅保留在测试目标中。

删除 UIViewController 样板代码

在单元测试中创建视图控制器时,UIKit 决定懒散地工作,尽可能少做工作。在单元测试中,视图生命周期方法 viewDidLoadviewWillAppearviewDidAppear 未必会被调用。这导致开发者做了诸如获取关键窗口、将视图控制器附加到关键窗口并使其成为关键和可见等事情——真糟糕。

我们可以使用这个扩展应用到 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()

适用于UIButtonUITableViewCell等!

安装

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"
  1. 运行carthage update
  2. 进入Carthage/Build/iOS文件夹,并将UnIt框架添加到您的测试目标的"链接二进制与库"构建阶段中。
  3. 在您的测试目标中,点击左上角的+按钮,选择"新建复制文件阶段"。
  4. 设置"目标"为"框架",并添加UnIt框架。确保启用"复制时签名"。

维护者

来自开源库

connectedlogo_blue