我想在我的UI测试中更新模拟器的位置,以便我可以在位置更改时检查行为。有没有办法让UI测试以某种方式“调用”来运行可以通过模拟器的Debug / Location菜单项或其他方法更改模拟器位置的AppleScript?
如果我不能这样做,我正在考虑将自己的CLLocationManager版本注入应用程序,然后从UI测试(例如通过本地Web服务器)向该版本发送位置,假设有某种方式我可以从UI测试中“获取”位置信息(例如,通过写入Mac上的文件)。
答案 0 :(得分:2)
您可以使用自定义GPX file模拟位置更改。使用您所需的路径或路线创建一个,然后从Product - >中选择它。方案 - >编辑方案......
答案 1 :(得分:2)
答案 2 :(得分:0)
我知道这应该更容易,但我找不到一个不那么复杂的解决方案。但它很灵活且有效 基本思想是使用帮助应用程序将测试位置发送到被测试的应用程序 以下是必需的步骤:
1)设置帮助应用程序:
这是一个在viewView中显示测试位置的单一视图应用程序:
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)准备要测试的应用以处理此自定义网址:
在info.plist中插入以下行:
注册自定义网址“setFakeLocation://
”。为了使受测试的应用可以处理此网址,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测试实现。它需要访问帮助应用程序:
在UITests中,在class ShopEasyUITests: XCTestCase
中,定义属性
var helperApp: XCUIApplication!
并为其分配通过其bundleIdentifier
定义的应用程序helperApp = XCUIApplication(bundleIdentifier: "com.yourCompany.Helperapp“)
此外,定义一个函数,通过激活帮助应用程序并点击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测试推送通知。不是在locationManagerDelegate?.locationManager(locationManager, didUpdateLocations: [location])
中调用func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
,而是可以在那里调用
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)