SpecTools 1.0.2

SpecTools 1.0.2

测试已测试
语言编程语言 SwiftSwift
许可证 MIT
发布日期上次发布日期2017年11月
SwiftSwift 版本4.0
SPM支持 SPM

Ondrej Rafaj 维护。



SpecTools 1.0.2






Docs

这是一个库,可以帮助您在 iOS 应用界面测试时编写更少的代码。

实现

在您添加 SpecTools 框架后,可以通过 spec 属性获得一组选项,用于大多数 UI 元素。

这些包括

  • action
    • 模拟特定操作事件的按钮上的点击
    • 模拟手势识别器的点击
  • check
    • 检查视图是否真正可见在屏幕上
    • 视图控制器和导航堆栈
    • 表格视图在其单元格中有正确的内容
  • find
    • 基于内容或类型在屏幕上定位任何元素或元素
    • 任何 UI 元素上的任何文本
  • prepare
    • 以在真实环境中设置的方式准备您的视图控制器
    • 在运行时更改任何设备屏幕上视图的大小,以检查是否所有内容仍然可见
    • 将您的测试视图控制器分配给模拟导航控制器,以跟踪 pushViewControllerpopViewController 方法

这里有许多更多附加功能的空间,因此请随时提出功能请求。

以下是一个示例实现

import Foundation
import UIKit
import Quick
import Nimble
import SpecTools

@testable import SpecToolsExample


class ViewControllerSpec: QuickSpec {
    
    override func spec() {
        
        let subject = ViewController()
        
        describe("basic view controller") {
            beforeEach {
                // Simulate view controller being presented to the screen
                subject.spec.prepare.simulatePresentViewController()
                // Reset the view to specific size
                subject.spec.prepare.set(viewSize: .iPhone6Plus)
            }
            
            it("has a visible label1") {
                // Get your first label
                let element = subject.view.spec.find.first(labelWithText: "My first label")
                // Check if the label is truly visible and print out the entire view structure that is being checked
                expect(element?.spec.check.isVisible(visualize: .text)).to(beTrue())
            }
            
            it("has a visible scrollView") {
                // Get a scroll view
                let element = subject.view.spec.find.firstScrollView()
                // Check visibility
                expect(element?.spec.check.isVisible()).to(beTrue())
            }
            
            it("has a visible label2", closure: {
                // Get a label that contains "second label" and print how we get to it in the console including any text on visible elements
                let element = subject.view.spec.find.first(labelWithText: "My second label", exactMatch: false, visualize: .text)
                // Check if the label is visible on subjects view and print all frames we encounter on the way
                expect(element?.spec.check.isVisible(on: subject.view, visualize: .frames)).to(beTrue())
            })
            
            describe("when we tap on button1") {
                beforeEach {
                    // Simulate button tap
                    button1.spec.action.tap()
                }
            
                it("should have pushed new view controller") {
                    // Check we have new view controller in the navigation stack
                    expect(subject.navigationController?.spec.check.contains(viewControllerClass: TableViewController.self)).to(beTrue())
                }
            }
            
        }
        
    }
    
}

演示应用程序与测试

要运行示例项目

  • 克隆仓库,并从示例目录中运行 pod install
  • Cmd+U 运行示例测试

文档

Jazzy 基于的文档可在此处找到 Docs

在线文档应始终反映 master 分支上的最新代码。

需求

此库可以完全独立运行。它不需要快速和敏捷,尽管我们强烈建议您尝试这些库!

安装

对于 Swift 4 支持请参阅 swift4 分支!

用法

调试

某些方法有可用的调试机制。最常见的一个是 visualize: VisualizationType 参数,它允许您将调试数据(通常是 SpecTools 递归通过元素的树)打印到控制台。

有关可用的可视化选项,请参阅文档:VisualizationType

以下部分应包含所有可用的方法

操作

模拟点击

模拟 UIButton 上的点击

button1.spec.action.tap()
// or
button1.spec.action.tap(event: .touchUpInside)

模拟带有 UITapGestureRecognizer 的视图上的点击

view.spec.action.triggerTap()
// or
view.spec.action.triggerTap(taps: 3, touches: 2)

执行手势识别器

在任何 UIGestureRecognizer 上执行操作

recognizer.spec.action.execute()

从任何 UIGestureRecognizer 获取目标数组

  • 返回 [(target: AnyObject, action: Selector)]
recognizer.spec.action.getTargetInfo()

检查

检查视图可见性

视图是否真正显示在屏幕上?检查元素(及其所有父视图)是否有父视图、是否有透明度、未隐藏并且有一个有效的屏幕帧。

view.spec.check.isVisible() // Example 1
// or
view.spec.check.isVisible(on: viewController.view, visualize: .all) // Example 2

示例 1)忽略最后一个没有父视图的视图,例如,期望它为视图控制器的视图

示例 2)将可见性与另一个视图进行比较,还可以将整个递归视图结构打印到控制台

检查表格视图单元格

检查所有通过 UITableViewDataSource 获取的 UITableViewCell 是否评估正确

// Create an enumerate closure
let doesFitClosure: (UITableViewCell)->Bool = { (cell) -> Bool in
	guard let cell = cell as? CustomTableViewCell else {
		return false
	}
	if cell.customLabel.text?.characters.count == 0 {
		return false
	}
	return true
}

// Test if all cells generated by the data source are ok using your closure
let ok = subject.tableView.spec.check.allCells(fit: doesFitClosure)

您还可以要求一个数组,该数组不满足条件

let indexPaths: [IndexPath] = subject.tableView.spec.check.allCells(thatDontFit: doesFitClosure)

检查集合视图单元格

检查所有通过 UICollectionView 数据源获取的 UICollectionViewCell 是否评估正确

// Create an enumerate closure
let doesFitClosure: (UICollectionViewCell)->Bool = { (cell) -> Bool in
	guard let cell = cell as? CustomCollectionViewCell else {
		return false
	}
	if cell.customLabel.text?.characters.count == 0 {
		return false
	}
	return true
}

// Test if all cells generated by the data source are ok using your closure
let ok = subject.collectionView.spec.check.allCells(fit: doesFitClosure)

您还可以要求一个数组,该数组不满足条件

let indexPaths: [IndexPath] = subject.collectionView.spec.check.allCells(thatDontFit: doesFitClosure)

UIViewController 检查

查找导航堆栈中的特定视图控制器

let ok: Bool = viewController.spec.check.has(siblingInNavigationStack: anotherViewController)

检查视图控制器是否有子视图控制器

let ok: Bool = viewController.spec.check.has(childViewController: childViewController)

检查视图控制器在导航堆栈中是否有特定类类型

let ok: Bool = viewController.spec.check.contains(siblingClassInNavigationStack: AnotherViewController.self)

检查视图控制器是否有子视图控制器的特定类类型

let ok: Bool = viewController.spec.check.contains(childViewControllerClass: ChildViewController.self)

UINavigationController 检查

检查导航视图控制器是否包含某些视图控制器

let ok: Bool = navigationController.spec.check.has(viewController: vc)

检查导航视图控制器是否包含某些类型的视图控制器

let ok: Bool = viewController.spec.check.contains(viewControllerClass: MyCustomViewController.self)

查找

手势识别器

在视图上查找特定类型的手势识别器(泛型方法)

let recognizers: [UISwipeGestureRecognizer] = view.spec.find.all(gestureRecognizersOfType: UISwipeGestureRecognizer.self)

UIKit 元素

在特定视图中查找第一个包含或匹配所需字符串的标签(UILabel)。要按仅部分字符串/内容搜索元素,请使用 exactMatch: Bool 标志。要可视化元素路径,请使用 visualize: VisualizationType。请参阅 调试 部分,获取更多详细信息。

let element = view.spec.find.first(labelWithText: "My first label")
// or
let element = view.spec.find.first(labelWithText: "first", exactMatch: false, visualize: .text)

在特定视图中查找第一个包含或匹配所需字符串的文本字段(UITextField)。要按仅部分字符串/内容搜索元素,请使用 exactMatch: Bool 标志。要可视化元素路径,请使用 visualize: VisualizationType。请参阅 调试 部分,获取更多详细信息。

let element = view.spec.find.first(textFieldWithText: "My first text field")
// or
let element = view.spec.find.first(textFieldWithText: "first", exactMatch: false, visualize: .text)

在特定视图中查找第一个包含或匹配所需字符串的搜索栏(UISearchBar)。要按仅部分字符串/内容搜索元素,请使用 exactMatch: Bool 标志。要可视化元素路径,请使用 visualize: VisualizationType。请参阅 调试 部分,获取更多详细信息。

let element = view.spec.find.first(searchBarWithText: "My first search bar")
// or
let element = view.spec.find.first(searchBarWithText: "first", exactMatch: false, visualize: .text)

在特定视图中查找第一个包含或匹配所需字符串的文本视图(UITextView)。要按仅部分字符串/内容搜索元素,请使用 exactMatch: Bool 标志。要可视化元素路径,请使用 visualize: VisualizationType。请参阅 调试 部分,获取更多详细信息。

let element = view.spec.find.first(textViewWithText: "My first text view")
// or
let element = view.spec.find.first(textViewWithText: "first", exactMatch: false, visualize: .text)

在特定视图中查找第一个包含或匹配所需字符串的表格视图单元格(UITableViewCell)。要按仅部分字符串/内容搜索元素,请使用 exactMatch: Bool 标志。要可视化元素路径,请使用 visualize: VisualizationType。请参阅 调试 部分,获取更多详细信息。

let element = view.spec.find.first(tableViewCellWithText: "My first cell")
// or
let element = view.spec.find.first(tableViewCellWithText: "first", exactMatch: false, visualize: .text)

在特定视图中查找第一个包含或匹配所需字符串的按钮(UIButton)。要按仅部分字符串/内容搜索元素,请使用 exactMatch: Bool 标志。要可视化元素路径,请使用 visualize: VisualizationType。请参阅 调试 部分,获取更多详细信息。

let element = view.spec.find.first(buttonWithText: "My first button")
// or
let element = view.spec.find.first(buttonWithText: "first", exactMatch: false, visualize: .text)

在特定视图中查找第一个包含或匹配所需字符串的表格视图头部或尾部视图(UITableViewHeaderFooterView)。要按仅部分字符串/内容搜索元素,请使用 exactMatch: Bool 标志。要可视化元素路径,请使用 visualize: VisualizationType。请参阅 调试 部分,获取更多详细信息。

let element = view.spec.find.first(tableSectionHeaderFooterViewWithText: "My first table header or footer view")
// or
let element = view.spec.find.first(tableSectionHeaderFooterViewWithText: "first", exactMatch: false, visualize: .text)

在特定的视图中查找第一个UITableView的头或尾(不是分区头或尾),其中包含或匹配所需的字符串。您正在搜索的视图可能包含多个UITableView。为了通过仅部分字符串/内容来搜索元素,请使用exactMatch: Bool标志。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.first(tableHeaderFooterViewWithText: "My first header view")
// or
let element = view.spec.find.first(tableHeaderFooterViewWithText: "first", exactMatch: false, visualize: .text)

在特定的父视图中查找第一个视图(任何UIView),其中包含或匹配所需的字符串。为了仅通过部分字符串/内容搜索元素,请使用exactMatch: Bool标志。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.first(elementWithText: "My first view with some text on a label deep inside")
// or
let element = view.spec.find.first(elementWithText: "deep", exactMatch: false, visualize: .text)

在特定的视图中查找第一个元素(任何UIView子类(Element)),其中包含或匹配所需的字符串。为了仅通过部分字符串/内容搜索元素,请使用exactMatch: Bool标志。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.first(elementOfType: MyCustomView.self, withText: "My first custom view with some text on a label deep inside")
// or
let element = view.spec.find.first(elementOfType: UILabel.self, withText: "custom", exactMatch: false, visualize: .text)

在特定的视图中查找第一个tableView(UITableView)。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.firstTableView()
// or
let element = view.spec.find.firstTableView(visualize: .frames)

在特定的视图中查找第一个collectionView(UICollectionView)。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.firstCollectionView()
// or
let element = view.spec.find.firstCollectionView(visualize: .frames)

在特定的视图中查找第一个scrollView(UIScrollView)。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.firstScrollView()
// or
let element = view.spec.find.firstScrollView(visualize: .frames)

在特定的视图中查找第一个table header或 footer视图(UITableViewHeaderFooterView)。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.firstTableHeaderFooterView()
// or
let element = view.spec.find.firstTableHeaderFooterView(visualize: .frames)

在特定的视图中查找第一个元素(通用方法)。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.first(elementOfType: MyCustomView.self)
// or
let element = view.spec.find.first(elementOfType: UIButton.self, visualize: .frames)

在特定的视图中查找特定类型的所有元素。要可视化元素的路径,请使用visualize: VisualizationType。请参阅调试部分以获取更多详细信息。

let element = view.spec.find.all(elementsOfType: UITextField.self)
// or
let element = view.spec.find.all(elementsOfType: MyCustomView.self, visualize: .frames)

在 UIKit 元素上搜索文本

以重要性顺序从特定视图中获取文本(首先检查文本字段上的第一个文本,如果没有文本则查找占位符)。您可以使用preferablyMatching: String指定要查找的文本。如果没有找到匹配的字符串,则方法将回退到其原始的优先级顺序。如果没有直接可用的文本属性,则方法返回nil。

此方法仅适用于UILabelUITextFieldUISearchBarUITextView

let element = view.spec.find.anyText()
// or
let element = view.spec.find.anyText(preferablyMatching: "Welcome to my fun app!")

准备

准备用于测试的视图控制器

将触摸视图控制器的视图以获取loadView和viewDidLoad的调用,然后手动调用带有禁用动画的viewWillAppear和viewDidAppear

viewController.spec.prepare.simulatePresentViewController()

在运行时为视图控制器的视图设置新的、特定的大小

viewController.spec.prepare.set(viewSize: CGSize(width: 375.0, height: 1500))

在您的视图控制器的视图中设置期望设备的屏幕大小,您可以指定自定义高度。自定义高度可能对于有scrollviews的情况很有用

viewController.spec.prepare.set(viewSize: .iPhone6Plus)
// or
let customHeight: CGFloat = 5000.0
viewController.spec.prepare.set(viewSize: .iPhone6Plus, height: customHeight)

为视图控制器提供导航控制器

viewController.spec.prepare.assignNavigationController()
// or
viewController.spec.prepare.assignNavigationController(ofClass: CustomNavigationViewController.self)

为视图控制器提供模拟导航控制器,这主要允许进行推/弹出功能的测试

viewController.spec.prepare.assignMockNavigationController()

作者

Ondrej Rafaj,[email protected]

许可证

SpecTools仅在MIT许可下可用。有关更多信息,请参阅LICENSE文件。