如何对 Swift 中的委托方法进行 UnitTest 异步调用?
How to UnitTest asynchronous calls to delegate methods in Swift?
我有以下 class 静态方法,该方法使用 MKDirections 计算两个坐标之间的自定义路线。完成计算后,该方法使用委托将路线(MKPolyline object)传递给视图控制器,视图控制器将其作为叠加层添加到 MapView。每条路线都分配有一个标题,用于确定路线在地图上呈现的颜色。
class NavigationInterface {
weak static var routeDelegate: RouteDelegate!
static func addRouteFromTo(sourceCoor: CLLocationCoordinate2D, destinationCoor: CLLocationCoordinate2D, transportTypeString: String)
{
let sourcePlacemark = MKPlacemark(coordinate: sourceCoor)
let destinationPlacemark = MKPlacemark(coordinate: destinationCoor)
//var route = MKRoute()
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: sourcePlacemark)
request.destination = MKMapItem(placemark: destinationPlacemark)
request.requestsAlternateRoutes = false
//get MKDirectionsTransportType based on String identifier
request.transportType = getTransportType(transportTypeString: transportTypeString)
let directions = MKDirections(request: request)
directions.calculate { (response, error) in
if let directionResponse = response?.routes.first {
let route = directionResponse.polyline
route.title = transportTypeString
print("Got Here")
self.routeDelegate!.didAddRoute(route: route)
}
}
}
委托是通过以下协议定义的:
protocol RouteDelegate: class {
func didAddRoute(route: MKPolyline)
func didAddBoundary(boundary: MKPolygon)
}
视图控制器按如下方式实现委托:
class MapViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
...
override func viewDidLoad() {
super.viewDidLoad()
NavigationInterface.routeDelegate = self
}
extension MapViewController: RouteDelegate {
// delegate Method
// called in Navigation Interface
func didAddRoute(route: MKPolyline) {
mapView.add(route)
}
func didAddBoundary(boundary: MKPolygon) {
mapView.add(boundary)
}
}
现在我尝试编写一个 UnitTest 来检查委托方法 "didAddRoute" returns 是否正确
为此,我创建了一个测试 class "NavigationTests",它实现了 RouteDelegate 协议和一个计算路由然后评估从 "NavigationTests" 返回的路由的测试方法"didAddRoute" 的协议实现:
class NavigationTests: XCTestCase, RouteDelegate {
var routes = [MKPolyline]()
var asyncExpectation: XCTestExpectation?
func didAddRoute(route: MKPolyline) {
routes.append(route)
asyncExpectation?.fulfill()
}
...
func testaddRouteFromTo(){
NavigationInterface.routeDelegate = self
asyncExpectation = expectation(description: "routes returned from delegate method")
NavigationInterface.addRouteFromTo(sourceCoor: CoordinateA, destinationCoor: CoordinateB, transportTypeString: "roadTravel")
let result = XCTWaiter.wait(for: [self.asyncExpectation!], timeout: 2.0)
if result == XCTWaiter.Result.completed {
let route = self.routes.first
XCTAssert(route!.title == "roadTravel", "failed to retrieve correct route")
print(route!.title)
} else {
XCTFail()
}
}
}
现在这个测试方法随机 returns 从 RouteDelegate 的 MapViewController 实现而不是 NavigationTests 实现路由。 W
如何避免对 MapViewController 的这些不需要的引用以及为什么要创建它,因为我没有在测试中实例化它?
理想情况下,我想阻止 MapViewController 在 运行 此测试 class 时被实例化,因为单元测试不需要它。
如何确保只使用 RouteDelegate 的 NavigationTests 实现?
静态与测试
因为 addRouteFromTo(sourceCoor:destinationCoor:transportTypeString:)
是静态方法,所以您也将 NavigationInterface.routeDelegate
设为静态。当您的测试 运行 时,它们正在设置一个全局变量。这意味着测试具有超出测试范围的副作用。
这里有几种方法可以防止这种情况发生:
a) 创建 setUp()
和 tearDown()
。在 setUp()
中,在覆盖 self
之前保存 NavigationInterface.routeDelegate
的旧值。在tearDown()
中,恢复旧值。
b) 从静态变为对象。作为一般规则,静态使事情更难测试。
更喜欢 b)。它更安全,并让可测试性的压力改善您的设计。
…我在您的测试中没有看到任何对 MapViewController 的引用。它是由您的应用程序委托创建的吗?
如何测试异步调用?
现在回答你的大问题。进行实际联网的测试缓慢且脆弱。这取决于您的网络状况。这取决于后端。它引入了时间滞后。
重构代码以测试以下内容会更好:
- 您是否创建了正确的 MKDirectionsRequest?
- 您是否正确处理响应?
这将在至少 2 次测试中表达,但可能更多。一旦您可以独立测试响应处理,您就可以测试错误以及成功的响应。
那么如何独立于 "handle the response" 测试 "create the response"?通过以不同的方法完成这项工作。然后测试可以调用这些方法。
无需测试Apple 进行网络调用,或在后端执行某些操作,或发送响应。如果您采用这种方法,则对异步测试的需求会急剧下降。
希望对您有所帮助。如果您需要澄清,请询问。有关 "the way Apple shows us to write code isn't good testable design," 的更多想法,请参阅 https://qualitycoding.org/design-sense/
我有以下 class 静态方法,该方法使用 MKDirections 计算两个坐标之间的自定义路线。完成计算后,该方法使用委托将路线(MKPolyline object)传递给视图控制器,视图控制器将其作为叠加层添加到 MapView。每条路线都分配有一个标题,用于确定路线在地图上呈现的颜色。
class NavigationInterface {
weak static var routeDelegate: RouteDelegate!
static func addRouteFromTo(sourceCoor: CLLocationCoordinate2D, destinationCoor: CLLocationCoordinate2D, transportTypeString: String)
{
let sourcePlacemark = MKPlacemark(coordinate: sourceCoor)
let destinationPlacemark = MKPlacemark(coordinate: destinationCoor)
//var route = MKRoute()
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: sourcePlacemark)
request.destination = MKMapItem(placemark: destinationPlacemark)
request.requestsAlternateRoutes = false
//get MKDirectionsTransportType based on String identifier
request.transportType = getTransportType(transportTypeString: transportTypeString)
let directions = MKDirections(request: request)
directions.calculate { (response, error) in
if let directionResponse = response?.routes.first {
let route = directionResponse.polyline
route.title = transportTypeString
print("Got Here")
self.routeDelegate!.didAddRoute(route: route)
}
}
}
委托是通过以下协议定义的:
protocol RouteDelegate: class {
func didAddRoute(route: MKPolyline)
func didAddBoundary(boundary: MKPolygon)
}
视图控制器按如下方式实现委托:
class MapViewController: UIViewController {
@IBOutlet weak var mapView: MKMapView!
...
override func viewDidLoad() {
super.viewDidLoad()
NavigationInterface.routeDelegate = self
}
extension MapViewController: RouteDelegate {
// delegate Method
// called in Navigation Interface
func didAddRoute(route: MKPolyline) {
mapView.add(route)
}
func didAddBoundary(boundary: MKPolygon) {
mapView.add(boundary)
}
}
现在我尝试编写一个 UnitTest 来检查委托方法 "didAddRoute" returns 是否正确
为此,我创建了一个测试 class "NavigationTests",它实现了 RouteDelegate 协议和一个计算路由然后评估从 "NavigationTests" 返回的路由的测试方法"didAddRoute" 的协议实现:
class NavigationTests: XCTestCase, RouteDelegate {
var routes = [MKPolyline]()
var asyncExpectation: XCTestExpectation?
func didAddRoute(route: MKPolyline) {
routes.append(route)
asyncExpectation?.fulfill()
}
...
func testaddRouteFromTo(){
NavigationInterface.routeDelegate = self
asyncExpectation = expectation(description: "routes returned from delegate method")
NavigationInterface.addRouteFromTo(sourceCoor: CoordinateA, destinationCoor: CoordinateB, transportTypeString: "roadTravel")
let result = XCTWaiter.wait(for: [self.asyncExpectation!], timeout: 2.0)
if result == XCTWaiter.Result.completed {
let route = self.routes.first
XCTAssert(route!.title == "roadTravel", "failed to retrieve correct route")
print(route!.title)
} else {
XCTFail()
}
}
}
现在这个测试方法随机 returns 从 RouteDelegate 的 MapViewController 实现而不是 NavigationTests 实现路由。 W
如何避免对 MapViewController 的这些不需要的引用以及为什么要创建它,因为我没有在测试中实例化它? 理想情况下,我想阻止 MapViewController 在 运行 此测试 class 时被实例化,因为单元测试不需要它。
如何确保只使用 RouteDelegate 的 NavigationTests 实现?
静态与测试
因为 addRouteFromTo(sourceCoor:destinationCoor:transportTypeString:)
是静态方法,所以您也将 NavigationInterface.routeDelegate
设为静态。当您的测试 运行 时,它们正在设置一个全局变量。这意味着测试具有超出测试范围的副作用。
这里有几种方法可以防止这种情况发生:
a) 创建 setUp()
和 tearDown()
。在 setUp()
中,在覆盖 self
之前保存 NavigationInterface.routeDelegate
的旧值。在tearDown()
中,恢复旧值。
b) 从静态变为对象。作为一般规则,静态使事情更难测试。
更喜欢 b)。它更安全,并让可测试性的压力改善您的设计。
…我在您的测试中没有看到任何对 MapViewController 的引用。它是由您的应用程序委托创建的吗?
如何测试异步调用?
现在回答你的大问题。进行实际联网的测试缓慢且脆弱。这取决于您的网络状况。这取决于后端。它引入了时间滞后。
重构代码以测试以下内容会更好:
- 您是否创建了正确的 MKDirectionsRequest?
- 您是否正确处理响应?
这将在至少 2 次测试中表达,但可能更多。一旦您可以独立测试响应处理,您就可以测试错误以及成功的响应。
那么如何独立于 "handle the response" 测试 "create the response"?通过以不同的方法完成这项工作。然后测试可以调用这些方法。
无需测试Apple 进行网络调用,或在后端执行某些操作,或发送响应。如果您采用这种方法,则对异步测试的需求会急剧下降。
希望对您有所帮助。如果您需要澄清,请询问。有关 "the way Apple shows us to write code isn't good testable design," 的更多想法,请参阅 https://qualitycoding.org/design-sense/