【问题标题】:Programmatically simulate GPS location in iOS tests在 iOS 测试中以编程方式模拟 GPS 位置
【发布时间】:2017-08-25 07:44:59
【问题描述】:

我想用 Swift 编写 UI 测试,在我们的应用程序中制作地图不同位置的屏幕截图。为此,我需要在测试期间模拟假 GPS 数据。

有一些像这样的解决方案 (https://blackpixel.com/writing/2016/05/simulating-locations-with-xcode-revisited.html) 使用 GPX 文件并在 Xcode 中使用Debug > Simulate Location 模拟位置,但我需要完全自动化。理想的是类似于 Android 中的 LocationManager

【问题讨论】:

    标签: ios swift gps automated-tests xctest


    【解决方案1】:

    我在编写 UI 测试时也遇到过类似的问题,因为模拟器/系留设备无法完成您可能想要的一切。我所做的是编写模拟所需行为(我通常无法控制的行为)的模拟。

    将自定义位置管理器替换为 CLLocationManager 将允许您完全控制位置更新,因为您可以通过 CLLocationManagerDelegate 方法以编程方式发送位置更新:locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])

    创建一个类MyLocationManager,使其成为CLLocationManager 的子类,并让它覆盖您调用的所有方法。不要在被覆盖的方法中调用 super,因为 CLLocationManager 永远不会真正接收到方法调用。

    class MyLocationManager: CLLocationManager {
      override func requestWhenInUseAuthorization() {
        // Do nothing.
      }
    
      override func startUpdatingLocation() {
        // Begin location updates. You can use a timer to regularly send the didUpdateLocations method to the delegate, cycling through an array of type CLLocation.
      }
    
      // Override all other methods used.
    
    }
    

    delegate 属性不需要被覆盖(也不能被覆盖),但您可以作为 CLLocationManager 的子类访问它。

    要使用MyLocationManager,您应该传入启动参数,告诉您的应用程序是否是 UITest。在您的测试用例的setUp 方法中插入这行代码:

    app.launchArguments.append("is_ui_testing")
    

    在测试时将 CLLocationManager 存储为 MyLocationManager 属性。不测试时 CLLocationManager 将照常使用。

    static var locationManger: CLLocationManager = ProcessInfo.processInfo.arguments.contains("is_ui_testing") ? MyLocationManager() : CLLocationManager()
    

    【讨论】:

      【解决方案2】:

      仅出于(单元)测试的目的,您可以调动startUpdatingLocation 方法来指示CLLocationManager 实例按照您的意愿行事。

      这是一个可能的实现:

      // Just a wrapper to allow storing weak references in an array
      struct WeakRef<T: AnyObject> {
          weak var value: T?
      }
      
      extension CLLocationManager {
          
          // Replace the `startUpdatingLocation` method with our own
          private static var swizzled = false
          static func swizzle() {
              guard !swizzled else { return }
              swizzled = true
              method_exchangeImplementations(class_getInstanceMethod(self, #selector(startUpdatingLocation))!,
                                             class_getInstanceMethod(self, #selector(swizzledStartUpdatingLocation))!)
          }
          
          // When a CLLocationManager is asked to `startUpdatingLocation`,
          // it adds itself to the list of registered instances
          // Using weak references to allow managers to deallocate when needed
          static var registeredInstances = [WeakRef<CLLocationManager>]()
          @objc func swizzledStartUpdatingLocation() {
              Self.registeredInstances.append(WeakRef(value: self))
              if let mockedLocation = Self.mockedLocation {
                  delegate?.locationManager?(self, didUpdateLocations: [mockedLocation])
              }
          }
          
          // This is the mocked location, when it changes
          // all registered managers notify their delegates with the new mocked location
          static var mockedLocation: CLLocation? {
              didSet {
                  guard let mockedLocation = mockedLocation else { return }
                  registeredInstances.forEach {
                      guard let manager = $0.value else { return }
                      manager.delegate?.locationManager?(manager, didUpdateLocations: [mockedLocation])
                  }
              }
          }
      }
      

      当然,这只是一个基本的实现,stopUpdatingLocation 也应该被调配以移除管理器。 CLLocationMangager 的其他功能也可以在扩展中实现/调配。

      【讨论】:

        【解决方案3】:

        你不能。 CLLocationManager 在委托的帮助下为您提供您的位置,您可以通过任何方式设置此位置。

        您可以创建一个 CLLocationManager 模拟器类,该类会随着时间的推移提供一些位置。 或者,您可以将测试与带时间戳的 GPX 同步。

        【讨论】:

        • 这真的不可能吗?所以我最好的选择是使用带时间戳的 GPX 文件并在每次位置变化时截取屏幕截图?
        猜你喜欢
        • 2018-11-20
        • 2018-04-09
        • 1970-01-01
        • 2011-12-22
        • 2016-08-13
        • 2017-02-05
        • 1970-01-01
        • 2015-05-27
        • 1970-01-01
        相关资源
        最近更新 更多