swift - 如何在 Swift 中对委托(delegate)方法的异步调用进行单元测试?

标签 swift unit-testing asynchronous delegates xctest

我有以下带有静态方法的类,该方法使用 MKDirections 来计算两个坐标之间的自定义路线。完成计算后,该方法使用委托(delegate)将路线(MKPolyline 对象)传递到 View Controller , View Controller 将其作为叠加层添加到 MapView。每条路线都分配有一个标题,该标题决定路线在 map 上呈现的颜色。

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) 
        }
    }
}

委托(delegate)是通过以下协议(protocol)定义的:

protocol RouteDelegate: class {

    func didAddRoute(route: MKPolyline)
    func didAddBoundary(boundary: MKPolygon)

}

View Controller 按如下方式实现委托(delegate):

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 来检查委托(delegate)方法“didAddRoute”是否返回正确的路线

为此,我创建了一个测试类“NavigationTests”,它实现 RouteDelegate 协议(protocol)和一个测试方法,该方法计算路线,然后评估从“didAddRoute”的“NavigationTests”协议(protocol)实现返回的路线:

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()
        }
    }

}

现在,此测试方法从 RouteDelegate 的 MapViewController 实现(而不是 NavigationTests 实现)随机返回路由。

如何避免对 MapViewController 的这些不需要的引用?为什么要创建它,因为我没有在测试中实例化它? 理想情况下,我想防止在运行此测试类时实例化 MapViewController,因为单元测试不需要它。

如何确保仅使用 RouteDelegate 的 NavigationTests 实现?

最佳答案

静态与测试

由于 addRouteFromTo(sourceCoor:destinationCoor:transportTypeString:) 是静态方法,因此您也将 NavigationInterface.routeDelegate 设为静态方法。当您的测试运行时,它们正在设置一个全局变量。这意味着测试具有超出测试范围的副作用。

以下是防止这种情况发生的几种方法:

a) 创建setUp()tearDown()。在 setUp() 中,保存 NavigationInterface.routeDelegate 的旧值,然后将其覆盖到 self。在 tearDown() 中,恢复旧值。

b) 从静态变为对象。一般来说,静态因素会使测试变得更加困难。

更喜欢b)。它更安全,并且可以让可测试性的压力改进您的设计。

…我在您的测试中没有看到任何对 MapViewController 的引用。它是由您的应用程序委托(delegate)创建的吗?

如何测试异步调用?

现在回答你更大的问题。进行实际网络的测试缓慢且脆弱。这取决于您的网络条件。这取决于后端。它引入了时间滞后。

重组代码以便您可以测试以下内容会更好:

  • 您正在创建正确的 MKDirectionsRequest 吗?
  • 您是否正确处理响应?

这将通过至少 2 个测试来表达,但可能更多。一旦您可以独立测试响应处理,那么您就可以测试错误以及成功的响应。

那么如何独立于“处理响应”来测试“创建响应”呢?通过以不同的方法完成这项工作。然后测试就可以调用这些方法。

无需测试 Apple 是否进行网络调用、在后端执行某些操作或发送响应。如果您遵循这种方法,异步测试的需求就会急剧下降。

我希望这会有所帮助。如果您需要澄清,请询问。有关“Apple 向我们展示的编写代码的方式并不是良好的可测试设计”的更多想法,请参阅 https://qualitycoding.org/design-sense/

关于swift - 如何在 Swift 中对委托(delegate)方法的异步调用进行单元测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51001382/

相关文章:

javascript - Jasmine:测试 setTimeout 函数抛出错误

ios - 你如何在 Swift 中风格化字体?

c# - 模拟静态方法

objective-c - iOS - 对 viewController 进行单元测试?

node.js - 如何在 Node.js 中同步调用

c# - 如何在不等待完成的情况下启动异步方法?

ios - 如何在没有层次结构警告的情况下在 modalVC 的父 VC 中呈现一些 VC?

swift - 在我的例子中如何检查两个参数的条件

ios - 在dismissViewController回调中刷新presentingViewController的UI时意外发现nil

unit-testing - 如何通过 mocha-phatomJS 配置资源处理?加载资源文件时出错 :