Как выполнить UnitTest асинхронные вызовы для делегирования методов в Swift? - PullRequest
0 голосов
/ 23 июня 2018

У меня есть следующий класс со статическим методом, который использует MKDirections для вычисления пользовательских маршрутов между двумя координатами. Как только он завершает вычисление, метод использует делегат для передачи маршрута (объект MKPolyline) в View Controller, который добавляет его в 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)

}

View Controller реализует делегат следующим образом:

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" правильный маршрут

Для этой цели я создал тестовый класс "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()
        }
    }

}

Теперь этот метод тестирования случайным образом возвращает маршруты из реализации MapViewController в RouteDelegate вместо реализации NavigationTests. W

Как я могу избежать этих нежелательных ссылок на MapViewController и почему он вообще создается, поскольку я не создаю его в тесте? В идеале я хотел бы предотвратить создание экземпляра MapViewController при запуске этого тестового класса, поскольку он не требуется для модульного теста.

Как я могу убедиться, что используется только реализация NavigationTests RouteDelegate?

1 Ответ

0 голосов
/ 23 июня 2018

Статика против испытаний

Поскольку addRouteFromTo(sourceCoor:destinationCoor:transportTypeString:) является статическим методом, вы также сделали NavigationInterface.routeDelegate статическим. Когда ваши тесты запускаются, они устанавливают глобальную переменную. Это означает, что тесты имеют побочные эффекты, которые выходят за рамки тестов.

Вот несколько способов предотвратить это:

а) Создайте setUp() и tearDown(). В setUp() сохраните старое значение NavigationInterface.routeDelegate перед тем, как перезаписать его на self. В tearDown() восстановите старое значение.

б) Переход от статики к объекту. Как правило, статика усложняет тестирование.

Предпочитают б). Это безопаснее и позволяет проверке улучшить ваш дизайн.

… Я не вижу ссылок на MapViewController в вашем тесте. Это было создано вашим делегатом приложения?

Как проверить асинхронный вызов?

Теперь к вашему большему вопросу. Тест, который действительно работает в сети, медленный и хрупкий. Это зависит от условий вашей сети. Это зависит от серверной части. Это вводит временную задержку.

Вы бы лучше обслужили реструктуризацию своего кода, чтобы вы могли проверить следующее:

  • Вы создаете правильный запрос MKDirectionsRequest?
  • Правильно ли вы обрабатываете ответ?

Это будет выражено как минимум в 2 тестах, но, вероятно, больше. Как только вы сможете самостоятельно протестировать обработку ответов, вы сможете тестировать как ошибки, так и успешные ответы.

Итак, как вы тестируете «создать ответ» независимо от «обработки ответа»? Делая эту работу в отдельных методах. Затем тесты могут просто вызывать эти методы.

Нет необходимости проверять, что Apple выполняет сетевой вызов, что-то делает на бэкэнде или отправляет ответ. Если вы будете следовать этому подходу, потребность в асинхронных тестах резко упадет.

Надеюсь, это поможет. Если вам нужно разъяснение, пожалуйста, спросите. Дополнительные мысли о том, что «способ, которым Apple показывает нам писать код, не является хорошим тестируемым дизайном», см. https://qualitycoding.org/design-sense/

...