概览
强大的 iOS 终端到终端 UI 测试框架。
目前它被 Avito 使用,我们拥有 700 多个终端到终端 UI 测试,其中约 90% 都通过了绿检,并且帮助我们减少了 2 年多的手动测试工作。我们大约在 25% 的 PR 上运行测试,我们正在努力将 100% 的测试执行在 PR 上,部分是通过从黑盒终端测试转向灰盒测试。我们在 3 个平台上运行这些测试,平均耗时约 40 分钟(总测试时间约为 30+ 小时),因为我们使用了 Emcee,一个在多台机器上运行测试的测试运行器(注意,Mixbox 不需要 Emcee)。我们也正在编写灰盒测试(我们将模拟类、网络等一切),但刚刚开始。
如果您想在您的公司中使用它,请向我们提交问题。我们正在努力使其成为社区可用,但这并非我们的主要目标。
功能
-
操作和检查(显而易见)
-
像素级可见性检查
-
一切都有轮询
-
全自动滚动
-
黑盒和灰盒测试!
- 黑盒测试在单独的应用中运行,并且可以启动应用。您可以使用这种类型的测试测试更多应用功能。您可以使用模拟,但是您需要编写更多代码(使用启动参数或实现进程间通信)。
- 灰盒测试在应用内运行(如 EarlGrey 一样),但测试与黑盒测试兼容,因此您可以共享您的测试模型、页面对象、测试助手等。由于测试在相同进程中执行,您可以模拟任何东西。您不能在没有使用模拟的情况下测试启动您的应用。
大部分代码在灰盒测试和黑盒测试之间共享,因为大多数功能都是共同的。两种方法都有自己的优点。使用两种方法来获得良好的测试金字塔。
-
页面对象
- 可以在其中包含任何代码的函数
- 页面对象元素可以嵌套元素(但需要一些不太美观的模板代码)
- 一切(操作/检查)都是完全可扩展的。如果您实现了自定义操作或检查,它将自动适用于黑盒和灰盒测试(参见
SwipeAction
,所有内置操作都是真正的扩展)。
-
测试中的UICollectionView中的每个单元格都可见(包括屏幕外的单元格)
-
跨应用和测试的可配置进程间通信
-
测试中可见的视图的自定义值
-
网络模拟(通过 NSURLSessionProtocol)
-
设置权限(相机/地理位置/通知等)
-
推送通知的模拟(局限性:仅在其活动应用中!)
-
从测试中打开URL
-
地理位置模拟
-
硬件键盘(定义的关键代码很少,但可以轻松实现)
-
无需分叉存储库即可自定义
-
Swift & Objective-C
-
经过测试
- 在3种设备配置上有176个黑盒UI测试
- 在3种设备配置上有155个灰盒UI测试
- 在4种设备配置上有100个单元测试
- SwiftLint + 自定义lint器
- Mixinbox每次拉取请求都执行所有测试,并且通常1个PR等于1个提交。
- 使用5个版本的Xcode(10.0、10.1、10.2.1、10.3、11.0)测试了2个示例
-
可配置的报告(例如:
Tests
项目使用了与Allure的集成,Allure是一个具有Web界面的开源报告系统,在Avito中我们使用的是内部解决方案;您可以编写自己的实现)
开发中/尚未开源
- 页面对象的代码生成
- 获取应用中的所有断言失败
- 与Springboard一起工作的封装器
- 在发布和测试构建之间切换无障碍值
安装
使用 Mixbox 有两种方法。
第一种在 演示 中描述,它过于简化,基本上您只需使用 pod SomePod
。
第二种是我们用于 Avito 的方法,它看起来像这样: 测试(查看那里的 Podfile)。
文档还不够完整,因此您可以尝试使用简单的链接方法(演示),但请使用测试中的代码示例。
支持的iOS/Xcode/Swift版本
- Xcode 11
- Swift 5
- iOS 10.3、iOS 11.4、iOS 12.1,中间版本可能工作也可能不工作,提到的版本已在CI上测试
- CocoaPods 1.8.4
- Mac OS 10.14.6
Xcode 9/10和旧版本不再支持。如果您计划在不同的环境中使用项目并遇到问题,请告知我们。
已知问题
- 在iOS 11.2上崩溃(在iOS 11.3、iOS 11.4上运行良好)
- 在物理设备上设置权限不起作用(可能还有其他一些,我们在物理设备上没有进行测试;基本功能是工作的)
- 设备旋转没有进行测试,我认为我们可能存在一些与此相关的虫子
- 未测试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的web视图中发现了一个bug。然后几年后,在构建时我们遇到了一些不稳定的问题。修复方法非常原始——复制粘贴所有内容以避免问题。我们本可以深入研究,但由于将来计划使用原生IPC解决方案,所以我们没有麻烦。
- CocoaImageHashing。由于向后兼容性原因(ameingast/cocoaimagehashing#13),我们不能合并修复。由于我们不需要向后兼容性,我们使用了复制粘贴的代码。
用于测试
- https://github.com/akveo/eva-icons:用于测试图像相似性代码的各种图标(MIT许可)。