ios 在执行UI测试期间更新模拟器中的位置

z31licg0  于 2023-05-19  发布在  iOS
关注(0)|答案(4)|浏览(124)

我想在我的UI测试中间更新模拟器的位置,以便我可以检查位置更改时的行为。有没有一种方法可以让UI测试以某种方式“调用”运行一个AppleScript,通过模拟器的Debug/Location菜单项或其他方法来更改模拟器的位置?
如果我不能做到这一点,我正在考虑将我自己的CLLocationManager版本注入到应用程序中,然后从UI测试向该版本发送位置(例如:通过本地web服务器),假设有某种方法可以从UI测试中获取位置信息(例如,通过写入Mac上的文件)。

bvk5enib

bvk5enib1#

选择测试中的一个,它应该是您选择位置时选择的测试目标。在此之前,你应该有gpx文件的任何位置,你想要的。有网站可在线生成一个为您的位置.

sr4lhrrt

sr4lhrrt2#

您可以使用自定义GPX file模拟位置的变化。使用您所需的路径或路线创建一个,然后从产品->方案->编辑方案中选择它...

h22fl7wq

h22fl7wq3#

我知道这应该容易得多,但我找不到一个不那么复杂的解决方案。但它是灵活的和工作。
其基本思想是使用一个helper应用程序,该应用程序将测试位置发送到被测应用程序。
以下是所需的步骤:

1)设置助手应用:

这是一个在tableView中显示测试位置的单视图应用程序:

import UIKit
import CoreLocation

class ViewController: UIViewController {
    struct FakeLocation {
        let name: String
        let latitude: Double
        let longitude: Double
    }

    @IBOutlet weak var tableView: UITableView!
    var fakeLocations: [FakeLocation] = [
        FakeLocation.init(name: "Location 1", latitude: 8.0, longitude: 49.0),
        FakeLocation.init(name: "Location 2", latitude: 8.1, longitude: 49.1)
    ]

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
    }

} // class ViewController

// MARK: - Table view data source

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return fakeLocations.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell")!
        let row = indexPath.row
        let fakeLocation = fakeLocations[row]
        cell.textLabel?.text = fakeLocation.name
        return cell
    }

} // extension ViewController: UITableViewDataSource

extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let cell = tableView.cellForRow(at: indexPath)
        cell?.isSelected = false
        let row = indexPath.row
        let fakeLocation = fakeLocations[row]
        let fakeLocationString = String.init(format: "%f, %f", fakeLocation.latitude, fakeLocation.longitude)
        let urlEncodedFakeLocationString = fakeLocationString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
        let url = URL.init(string: "setFakeLocation://" + urlEncodedFakeLocationString!)
        UIApplication.shared.open(url!) { (urlOpened) in
            guard urlOpened else { 
                print("could not send fake location") 
                return 
            }
        }
    }

} // extension ViewController: UITableViewDelegate

如果点击tableView中的一行,助手应用程序将尝试打开一个包含相应测试位置坐标的自定义URL。

2)准备待测应用处理此自定义URL:

在info.plist中插入以下行:

这将注册自定义URL“setFakeLocation://”。为了让被测应用能够处理此URL,AppDelegate中的以下两个函数必须返回true

func application(_ application: UIApplication, 
                                     willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
        return true
    }

    func application(_ application: UIApplication, 
                                     didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        return true
    }

此外,还必须实现实际打开URL的函数:

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {      
    let urlString = url.absoluteString
    let splittedString = urlString.components(separatedBy: "://")
    let coordinatesURLEncodedString = splittedString[1]
    let coordinateString = coordinatesURLEncodedString.removingPercentEncoding!
    let coordinateStrings = coordinateString.components(separatedBy: ", ")
    let latitude  = Double(coordinateStrings[0])!
    let longitude = Double(coordinateStrings[1])!
    let coordinate = CLLocationCoordinate2D.init(latitude: latitude, longitude: longitude)
    let location = CLLocation.init(coordinate: coordinate, 
                                   altitude: 0, 
                                   horizontalAccuracy: 0, 
                                   verticalAccuracy: 0, 
                                   timestamp: Date())
    let locationManager = LocationManager.shared
    locationManager.location = location
    locationManagerDelegate?.locationManager(locationManager, didUpdateLocations: [location])
    return true
}

本质上,它从URL中提取坐标,并在位置管理器的委托中调用函数didUpdateLocations,就像真实的的位置管理器已经更新了位置一样。

3)设置CLLocationManager的子类:

但是,测试中的应用程序很可能会访问位置管理器的location属性,该属性也必须设置。在CLLocationManager中,此属性是只读的,无法设置。因此,必须使用CLLocationManager的自定义子类,并覆盖此属性:

final class LocationManager: CLLocationManager {

  …

    // Allow location to be set for testing. If it is set, the set value will be returned, else the current location.
    private var _location: CLLocation?
    @objc dynamic override var location: CLLocation? {
        get { 
            let usedLocation = _location ?? super.location
            return usedLocation 
        }
        set {
            self._location = newValue
        }   
    }

  …

} // class LocationManager

在正常操作中,不设置子类的属性location,即nil。所以当它被读取时,它的超类的属性location,真实的的CLLocationManager被读取。但是,如果在测试期间将此属性设置为测试位置,则将返回此测试位置。

4)在被测应用UI测试中选择测试位置:

这可以通过多应用UI测试来实现。它需要访问助手应用程序:
class ShopEasyUITests: XCTestCase的UITests中,定义一个属性

var helperApp: XCUIApplication!

并向其分配通过其bundleIdentifier定义的应用

helperApp = XCUIApplication(bundleIdentifier: "com.yourCompany.Helperapp“)

此外,定义一个函数,通过激活helper应用程序并点击tableView中请求的行来设置所需的测试位置,然后切换回测试中的应用程序:

private func setFakeLocation(name: String) -> Bool {
    helperApp.activate() // Activate helper app
    let cell = helperApp.tables.cells.staticTexts[name]
    let cellExists = cell.waitForExistence(timeout: 10.0)
    if cellExists {
        cell.tap() // Tap the tableViewCell that displays the selected item
    }
    appUnderTest.activate() // Activate the app under test again
    return cellExists
}

最后,调用这个函数:

func test_setFakeLocation() {
    // Test that the helper app can send a fake location, and this location is handled correctly.

    // when
    let fakeLocationWasTappedInHelperApp = setFakeLocation(name: "Location 1")

    // then
    XCTAssert(fakeLocationWasTappedInHelperApp)

    …
}

5)进一步说明:

当然,测试中的应用程序和助手应用程序必须安装在同一模拟器上。否则,两个应用程序无法通信。
可能相同的方法可以用于UI测试推送通知。不需要在func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool中调用locationManagerDelegate?.locationManager(locationManager, didUpdateLocations: [location]),可以在那里调用

func application(_ application: UIApplication, 
               didReceiveRemoteNotification userInfo: [AnyHashable: Any], 
                                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
lc8prwob

lc8prwob4#

从iOS 16.4、macOS 13.3和Xcode 14.3开始,提供了一个新的XCTest API,让您可以非常轻松地完成此操作。
https://developer.apple.com/documentation/xctest/xcuidevice/4111083-location
您可以通过在XCUIDevice上设置location属性来模拟设备的位置。

相关问题