概览
强大的 iOS 端到端 UI 测试框架。
当前它在 Avito 中使用,我们有 700+ 的端到端 UI 测试,其中约 90% 是绿色的,它们帮助我们减少了 2 年的手动测试。我们在 PR 上运行约 25%,并正朝着在 pull request 上执行 100% 测试的目标努力,部分是通过将黑盒端到端测试切换到灰盒测试。我们在 3 个平台上运行这些测试,花费时间约为 40 分钟(测试的总时长为 30 小时以上),因为我们使用了 Emcee,一个可以在多台机器上运行测试的测试运行器(注意,Mixbox 不需要 Emcee)。我们在编写灰盒测试(其中我们模拟类、网络和一切)也是如此,但我们才刚开始。
如果您对在您的公司使用它表示热情,请为我们提交问题。我们使它可用,但并不是我们的主要目标。
特性
-
动作和检查(显然)
-
像素级可见性检查
-
所有内容都有轮询
-
全自动化滚动
-
黑盒和灰盒测试!
- 黑盒测试在独立的应用程序中运行,并能够启动应用程序。你可以使用这种类型的测试测试更多的应用程序功能。你可以使用模拟,但需要编写更多代码(使用启动参数或实现进程间通信)。
- 灰盒测试在应用程序中运行(例如,在EarlGrey中),但测试与黑盒测试兼容性,因此您可以共享您自己的测试模型、页面对象、测试助手等。由于测试在同一个进程中执行,您可以轻松地对任何事物进行模拟。不使用模拟无法测试启动应用程序。
大部分代码在灰盒测试和黑盒测试之间共享,因为大部分功能。两者都有各自的优点。使用这两种方法来获取良好的测试金字塔。
-
页面对象
- 可以包含任何代码的函数
- 页面对象元素可以嵌套元素(然而,它需要一些不太美观的 boilerplate)
- 所有(操作/检查)都是完全可扩展的。如果您实现了自定义操作或检查,它们将自动适用于黑盒和灰盒测试(请参阅
SwipeAction
,所有内置操作都是真的扩展)。
-
UICollectionView中的每个单元格都在测试中可见(包括屏幕外的单元格)
-
应用程序和测试之间的进程间通信可定制
-
在测试中可见的视图的可定制值
-
网络模拟(通过NSURLSessionProtocol)
-
设置权限(相机/地理位置/通知等)
-
模拟推送通知(限制:只能在活动应用中!)
-
从测试中打开url
-
地理位置模拟
-
硬件键盘(定义了很少的关键代码,但是可以轻松实现)
-
无需分叉存储库即可进行定制
-
Swift & Objective-C
-
经过测试
- 3种设备配置上的176个黑盒UI测试
- 3种设备配置上的155个灰盒UI测试
- 4种设备配置上的100个单元测试
- SwiftLint +自定义lint
- 所有测试都在向Mixbox的每个拉取请求执行,并且通常一个PR等于一个提交。
- 使用5个版本的Xcode(10.0,10.1,10.2.1,10.3,11.0)测试了两个演示。
-
可定制的报告(例如:
Tests
项目与开源的具有Web UI的报告系统Allure集成,Avito使用内部解决方案进行报告;您可以为报告编写自己的实现)
开发中/尚未开源
- 页面对象的代码生成
- 从应用程序获取所有断言失败
- 用于与Springboard工作的外观
- 在生产构建和测试构建之间切换可访问性值
安装
使用Mixbox有两种方法。
第一种方法在演示中描述,它过于简化,基本上您只需使用pod SomePod
。
我们在Avito使用的第二种方法如下: 测试(请参阅那里的Podfile)。
目前文档不足,因此您可以尝试将Mixbox链接到简单方法(演示),但请使用测试中的代码示例。
支持的 iOS/Xcode/Swift 版本
- Xcode 11
- Swift 5
- iOS 10.3,iOS 11.4,iOS 12.1,中间版本可能有效也可能无效,所述版本已在 CI 上进行过测试
- Cocoapods 1.8.4
- macOS 10.14.6
不再支持 Xcode 9/10 及更早版本。如果您计划在不同的环境下使用该项目并遇到问题,请告知我们。
已知问题
- 在 iOS 11.2 上崩溃(在 iOS 11.3 和 iOS 11.4 上工作得很好)
- 物理设备上设置权限不起作用(可能还有其他,我们没有在物理设备上进行测试;基本功能正常工作)
- 未测试设备旋转,我认为我们在它上面有 bug
- 未测试 iPad
- 报告中出现俄语(将很快修复)
示例
对于实际示例,请参阅 Tests
项目。它是最新开源的示例,展示了如何使用它,但它缺乏现实感(不显示如何为一个真实应用编写测试)。
显示基本功能的测试示例
func test() {
// Setting permissions
permissions.camera.set(.allowed)
permissions.photos.set(.notDetermined)
// Functions are useful in page objects and allows
// reusing code, for example, for transitions between states of the app
pageObjects.initial
.authorize(user: testUser)
.goToCvScreen()
// Aliases for simple assertions (you can add your own):
pageObjects.simpleCv.view.assertIsDisplayed()
pageObjects.simpleCv.title.assertHasText("My CV")
// Fully customizable assertions
pageObjects.simpleCv.addressField.assertMatches { element in
element.text != addressFieldInitialText && element.text.isNotEmpty
}
// Network stubbing.
networking.stubbing
.stub(urlPattern: ".*?example.com/api/cv")
.thenReturn(file: "cv.json")
// There is also a monitoring feature, including recording+replaying feature that
// allows to record all network and replay in later, so your tests will not require internet.
// Actions
pageObjects.simpleCV.occupationField.setText("iOS developer")
pageObjects.simpleCV.createCVButton.tap()
}
声明页面对象
public final class MapScreen:
BasePageObjectWithDefaultInitializer,
ScreenWithNavigationBar // protocol extensions are very useful for sharing code
{
// Basic locator
public var mapView: ViewElement {
return element("Map view") { element in
element.id == "GoogleMapView"
}
}
// You can use complex checks
// Note that you can create your own matchers like `element.isFooBar()`
public func pin(coordinates: Coordinates, deltaInMeters: Double = 10) -> ViewElement {
return element("Pin with coordinates \(coordinates)") { element in
element.id == "pin" && element
.customValues["coordinates", Coordinates.self]
.isClose(to: coordinates, deltaInMeters: deltaInMeters)
}
}
}
声明自定义页面对象元素
public final class RatingStarsElement:
BaseElementWithDefaultInitializer,
ElementWithUi
{
public func assertRatingEqualsTo(
_ number: Int,
file: StaticString = #file,
line: UInt = #line)
{
assertMatches(file: file, line: line) { element in
element.customValues["rating"] == number
}
}
}
复制粘贴的代码
这个库包含了从其他库粘贴过来的部分代码。有时是单个文件,有时是因为我们需要在源代码中内建条件编译以防止在发布构建中链接代码。
- EarlGrey(使用了
EarlGrey
的所有源文件中都有EarlGrey
子字符串)。许可是在这里(Apache):许可协议。它用于可见性检查、设置辅助功能等。原始仓库:https://github.com/google/EarlGrey - AnyCodable。增加了
#if
语句。原始仓库:https://github.com/Flight-School/AnyCodable - SBTUITestTunnel。在使用版本号大于3.0.6的网页视图中存在一个错误。然后在几年后,我们在构建它时遇到了不可靠的问题。修复方法非常原始——复制粘贴所有内容以避免问题。我们可以深入研究,但因为有一个计划在未来使用原生的IPC解决方案,所以没有费心处理。原始仓库:https://github.com/Subito-it/SBTUITestTunnel
- CocoaImageHashing。由于向后兼容性问题(ameingast/cocoaimagehashing#13),我们不能合并修复,但我们不需要向后兼容,所以我们使用了复制粘贴的代码。原始仓库:https://github.com/ameingast/cocoaimagehashing
用于测试
- https://github.com/akveo/eva-icons:用于测试图像相似性代码的各种图标(MIT许可证)。