【问题标题】:Using Xcode UI tests to test underlying framework behavior使用 Xcode UI 测试来测试底层框架行为
【发布时间】:2016-07-28 06:36:12
【问题描述】:

我正在构建一个 iOS 框架,用于收集有关 iOS 应用程序中各种事件的信息并执行本地和远程分析。其中一些事件无法在应用程序之外进行测试:例如,视图控制器转换。为了测试这些事件,我构建了一个测试 iOS 应用程序并希望使用 Xcode UI 测试来:

  • 通过点击一个将 VC 推送到导航控制器的按钮或通过切换到选项卡控制器中的不同选项卡来启动视图控制器转换

  • 验证框架是否能够检测到视图控制器转换并生成必要的事件(例如,将它们发送到服务器)

问题是 Xcode UI 测试在应用程序之外运行,因此我无法访问任何共享对象来验证框架是否正常运行。我也无法插入任何模拟对象,因为我也无权访问应用进程中加载​​的框架。

我什至尝试以一种测试模式加载框架,并让它更新一个标签,其中包含一些结果,然后 UI 测试将能够读取。但即使这样也很困难,因为XCUIElement 没有给我元素的实际文本;我只能查询带有一些预定义文本的元素。我可以使用它,但对于应该相当琐碎的事情来说,这似乎是一种非常迂回的方法。

是否有更好的替代方法来测试只能在正在运行的应用程序中模拟的事件,然后验证我的框架是否能够检测和处理它们?除了视图控制器转换之外,我还对触摸交互、加速度计事件和其他无法在应用程序之外模拟的功能感兴趣。当然,我可以模拟这些事件进行单元测试,但我也想自动测试当这些事件在实际应用中发生时,在我的框架中生成正确的响应。

【问题讨论】:

  • 您是否考虑过对触摸事件和转换进行 UI 测试?可能是比单元测试更好的选择,具体取决于您的追求。
  • @Robert:我说的是 UI 测试,而不是单元测试。我想使用 UI 测试来点击触发 VC 转换的按钮,然后查看该转换的效果,而不是 UI 元素,而是我框架的内部组件。
  • 您知道 iOS 单元测试 (XCTest) 实际上是在启动应用程序吗?您可以创建“半” UI 测试,因为您不会模拟用户事件,而是直接调用引发转换的方法。
  • @OhadSchneider:是的,但这不是我想要的。我想测试实际的 UI 转换、加速度计事件、点击等。当然,我也有单元测试,它们单独测试逻辑。你的提议处于中间位置,我也不想投资。

标签: ios xcode xcode-ui-testing ios-frameworks


【解决方案1】:

您可以尝试使用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() 方法获取它。

我个人不喜欢在您的应用目标中混合标准代码和测试代码的想法,因此我建议仅在真正需要时才使用它。

编辑

我已经更新了库,从视觉 0.9.23 开始,您现在可以将块中的任何对象传递回测试目标。不再需要解决方法!

【讨论】:

  • 只是为了澄清“反过来”——我可以使用registerCustomCommandNamed 从加载到测试应用程序的框架中的单例对象中检索数据吗?
  • 看起来答案是肯定的。谢谢——这正是我所需要的!
【解决方案2】:

没有太多的自动化方式来完成工作,但确实使用断点作为一种中间方式,至少在你让事情自动开始之前。您可以添加一个符号断点来命中 UI 方法,例如 [UIWindow setRootViewController:]、viewWillAppear 等。

您可以明确定义模块、条件和操作,以便每当有人查看您的 VC 时,都会执行一些可能对您有帮助的操作。

如果您可以在某个 viewDidAppear 上使用简单的“po myEvent”从您的框架中打印一些内容,那么您就可以开始了。我从来没有尝试过使用更高级的方法作为动作,但这些也可以做到。

【讨论】:

  • 当然,我可以使用断点并单步执行代码来进行手动测试。虽然我不是一个大粉丝。我真的希望这能自动发生。
猜你喜欢
  • 2015-12-13
  • 1970-01-01
  • 1970-01-01
  • 2015-11-04
  • 2017-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-03
相关资源
最近更新 更多