使用 Xcode UI 测试来测试底层框架行为
Using Xcode UI tests to test underlying framework behavior
我正在构建一个 iOS 框架,用于收集有关 iOS 应用程序中各种事件的信息并执行本地和远程分析。其中一些事件无法在应用程序之外进行测试:例如,视图控制器转换。为了测试这些事件,我构建了一个测试 iOS 应用程序并希望使用 Xcode UI 测试来:
启动视图控制器转换,例如,点击将 VC 推送到导航控制器的按钮,或者切换到选项卡控制器中的不同选项卡
验证框架能够检测到视图控制器转换并生成必要的事件(例如,将它们发送到服务器)
问题是 Xcode UI 在应用程序外部测试 运行,所以我无法访问任何共享对象来验证框架是否正常运行。我也无法插入任何模拟对象,因为,同样,我无权访问应用进程中加载的框架。
我尝试以一种测试模式加载框架,并让它用一些结果更新标签,然后 UI 测试就可以读取这些结果。但即使那样也很困难,因为 XCUIElement
没有给我元素的实际文本;我只能查询带有一些预定义文本的元素。我可以解决这个问题,但对于一些应该相当微不足道的事情来说,这似乎是一种非常迂回的方法。
是否有更好的替代方法来测试只能在 运行ning 应用程序中模拟的事件,然后验证我的框架是否能够检测和处理它们?除了视图控制器转换之外,我还对触摸交互、加速度计事件和其他无法在应用程序外部模拟的功能感兴趣。当然,我可以模拟这些事件进行单元测试,但我也想自动测试当这些事件在实际应用程序中发生时,在我的框架中生成正确的响应。
您可以尝试使用 SBTUITestTunnel。该库扩展了 UI 测试的功能,添加了一些在像您这样的情况下可能会派上用场的功能。
网络监控
该库允许您的测试目标收集应用中调用的网络调用。用法非常简单:
func testThatNetworkAfterEvent() {
// Use SBTUITunneledApplication instead of XCUIApplication
let app = SBTUITunneledApplication()
app.launchTunnelWithOptions([SBTUITunneledApplicationLaunchOptionResetFilesystem]) {
// do additional setup before the app launches
// i.e. prepare stub request, start monitoring requests
}
app.monitorRequestsWithRegex("(.*)myserver(.*)") // monitor all requests containing myserver
// 1. Interact with UI tapping elements that generate your events
// 2. Wait for events to be sent. This could be determined from the UI (a UIActivitiIndicator somewhere in your app?) or ultimately if you have no other option with an NSThread.sleepfortimeinterval
// 3. Once ready flush calls and get the list of requests
let requests: [SBTMonitoredNetworkRequest] = app.monitoredRequestsFlushAll()
for request in requests {
let requestBody = request.request!.HTTPBody // HTTP Body in POST request?
let responseJSON = request.responseJSON
let requestTime = request.requestTime // How long did the request take?
}
}
网络监控的好处是所有测试代码和逻辑都包含在您的测试目标中。
自定义代码块
还有其他用例,您需要执行自定义代码以便在测试目标中方便地调用,您也可以这样做。
在应用目标中注册一段代码
SBTUITestTunnelServer.registerCustomCommandNamed("myCustomCommandKey") {
injectedObject in
// this block will be invoked from app.performCustomCommandNamed()
return "any object you like"
}
并从测试目标调用它
func testThatNetworkAfterEvent() {
let app = ....
// at the right time
let objFromBlock = app.performCustomCommandNamed("myCustomCommand", object: someObjectToInject)
}
目前只能从测试目标->应用目标注入数据。如果您需要相反的方式,您可以将该数据存储在 NSUserDefaults 中并使用 SBTUIApplication 的 userDefaultsObjectForKey()
方法获取它。
我个人不喜欢在您的应用程序目标中混合使用标准代码和测试代码的想法,因此我建议仅在真正需要时才使用它。
编辑
我更新了库,从 vision 0.9.23 开始,您现在可以将块中的任何对象传回测试目标。不再需要解决方法!
完成任务的自动化方式不多,但请务必使用断点作为中间方式,至少在您自动启动任务之前是这样。您可以添加一个符号断点来命中 UI 方法,例如 [UIWindow setRootViewController:]、viewWillAppear 等
您可以明确定义模块、条件和操作,这样每当有人查看您的 VC 时,就会执行一些可能对您有帮助的操作。
如果您可以在某些 viewDidAppear 上使用简单的 "po myEvent" 从您的框架中打印一些内容,那么您就可以开始了。我从来没有尝试过使用更奇特的方法作为动作,但这些也可以完成。
我正在构建一个 iOS 框架,用于收集有关 iOS 应用程序中各种事件的信息并执行本地和远程分析。其中一些事件无法在应用程序之外进行测试:例如,视图控制器转换。为了测试这些事件,我构建了一个测试 iOS 应用程序并希望使用 Xcode UI 测试来:
启动视图控制器转换,例如,点击将 VC 推送到导航控制器的按钮,或者切换到选项卡控制器中的不同选项卡
验证框架能够检测到视图控制器转换并生成必要的事件(例如,将它们发送到服务器)
问题是 Xcode UI 在应用程序外部测试 运行,所以我无法访问任何共享对象来验证框架是否正常运行。我也无法插入任何模拟对象,因为,同样,我无权访问应用进程中加载的框架。
我尝试以一种测试模式加载框架,并让它用一些结果更新标签,然后 UI 测试就可以读取这些结果。但即使那样也很困难,因为 XCUIElement
没有给我元素的实际文本;我只能查询带有一些预定义文本的元素。我可以解决这个问题,但对于一些应该相当微不足道的事情来说,这似乎是一种非常迂回的方法。
是否有更好的替代方法来测试只能在 运行ning 应用程序中模拟的事件,然后验证我的框架是否能够检测和处理它们?除了视图控制器转换之外,我还对触摸交互、加速度计事件和其他无法在应用程序外部模拟的功能感兴趣。当然,我可以模拟这些事件进行单元测试,但我也想自动测试当这些事件在实际应用程序中发生时,在我的框架中生成正确的响应。
您可以尝试使用 SBTUITestTunnel。该库扩展了 UI 测试的功能,添加了一些在像您这样的情况下可能会派上用场的功能。
网络监控
该库允许您的测试目标收集应用中调用的网络调用。用法非常简单:
func testThatNetworkAfterEvent() {
// Use SBTUITunneledApplication instead of XCUIApplication
let app = SBTUITunneledApplication()
app.launchTunnelWithOptions([SBTUITunneledApplicationLaunchOptionResetFilesystem]) {
// do additional setup before the app launches
// i.e. prepare stub request, start monitoring requests
}
app.monitorRequestsWithRegex("(.*)myserver(.*)") // monitor all requests containing myserver
// 1. Interact with UI tapping elements that generate your events
// 2. Wait for events to be sent. This could be determined from the UI (a UIActivitiIndicator somewhere in your app?) or ultimately if you have no other option with an NSThread.sleepfortimeinterval
// 3. Once ready flush calls and get the list of requests
let requests: [SBTMonitoredNetworkRequest] = app.monitoredRequestsFlushAll()
for request in requests {
let requestBody = request.request!.HTTPBody // HTTP Body in POST request?
let responseJSON = request.responseJSON
let requestTime = request.requestTime // How long did the request take?
}
}
网络监控的好处是所有测试代码和逻辑都包含在您的测试目标中。
自定义代码块
还有其他用例,您需要执行自定义代码以便在测试目标中方便地调用,您也可以这样做。
在应用目标中注册一段代码
SBTUITestTunnelServer.registerCustomCommandNamed("myCustomCommandKey") {
injectedObject in
// this block will be invoked from app.performCustomCommandNamed()
return "any object you like"
}
并从测试目标调用它
func testThatNetworkAfterEvent() {
let app = ....
// at the right time
let objFromBlock = app.performCustomCommandNamed("myCustomCommand", object: someObjectToInject)
}
目前只能从测试目标->应用目标注入数据。如果您需要相反的方式,您可以将该数据存储在 NSUserDefaults 中并使用 SBTUIApplication 的 userDefaultsObjectForKey()
方法获取它。
我个人不喜欢在您的应用程序目标中混合使用标准代码和测试代码的想法,因此我建议仅在真正需要时才使用它。
编辑
我更新了库,从 vision 0.9.23 开始,您现在可以将块中的任何对象传回测试目标。不再需要解决方法!
完成任务的自动化方式不多,但请务必使用断点作为中间方式,至少在您自动启动任务之前是这样。您可以添加一个符号断点来命中 UI 方法,例如 [UIWindow setRootViewController:]、viewWillAppear 等
您可以明确定义模块、条件和操作,这样每当有人查看您的 VC 时,就会执行一些可能对您有帮助的操作。
如果您可以在某些 viewDidAppear 上使用简单的 "po myEvent" 从您的框架中打印一些内容,那么您就可以开始了。我从来没有尝试过使用更奇特的方法作为动作,但这些也可以完成。