не может проверить сетевой вызов внутри проекта xcode - PullRequest
0 голосов
/ 14 февраля 2019

Я новичок в ios Unitesting, и я впервые делаю это.

Мое приложение использует инфраструктуру, которая выполняет некоторые сетевые вызовы,

I 'Я пытаюсь проверить бизнес-логику платформы, вызывая одну из этих функций, но функция просто проходит мимо фактического вызова и никогда не переходит в обратный вызов.

здесь - фактическая функция внутри структуры, япытаюсь проверить>

func initSDK () {
        let launchParams: [String:String] = [
            kAWSDKUrl:  WhiteLabeler.localizedStringForKey(key: "baseSDKUrl", comment: "-", defaultValue: "https://isr-lap-tst2.americanwell.com:8443/"),
            kAWSDKKey:  WhiteLabeler.localizedStringForKey(key: "SDKserviceKeyForIos", comment: "-", defaultValue: "TriageApp"),
            kAWSDKBundleID: Bundle.main.bundleIdentifier!
        ]

        AWSDKService.initialize(withLaunchParams: launchParams) {[weak self] (success, error) in
            if success {
                self?.myPresenter?.onSDKInitlized()
                self?.didSdkInitilized = true
            } else {
                self?.myPresenter?.onError(errorText: error?.localizedDescription ?? "")
            }
        }
    }

и вот мой тестовый пример:

import XCTest
@testable import TriageFramework

class Virtual_First_Tests: XCTestCase {

    override func setUp() {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample1() {
        let homeInteractor: HomeInteractor = HomeInteractor();
        var didInitlized = homeInteractor.didSdkInitilized
        homeInteractor.initSDK()

        sleep(2)

        didInitlized = homeInteractor.didSdkInitilized
        XCTAssertTrue(didInitlized)


    }

, но он всегда терпит неудачу, поскольку никогда не переходит в обратный вызов для успеха или неудачи.Что я здесь не так делаю?

Ответы [ 2 ]

0 голосов
/ 22 февраля 2019

В коде, который вы хотите протестировать, нужно указать несколько вещей:

  • Используется неявная зависимость от третьей стороны , службы aws sdk.Поведение компонента с неявными зависимостями сложнее проверить, потому что вы не можете управлять им напрямую
  • . Это делает асинхронный вызов .То есть часть поведения происходит в обратном вызове закрытия метода initialize.Это означает, что тесты должны будут учитывать тот факт, что они должны ждать вызова закрытия.

Как отмечает Энрике Бермудес , вы всегда должны избегать создания сетизвонки в юнит или интеграционные тесты.При попадании на сервер тесты запускаются медленнее, и тесты должны быть быстрыми , чтобы вы могли быстро получить обратную связь.Кроме того, вы хотите, чтобы ваши тесты были предсказуемыми, но выполнение сетевого вызова может привести к сбою по ряду причин, например, из-за тайм-аута, сбоя сервера или сбоя подключения.

Лучший способЯ знаю, как изолировать себя от зависимости от сервера и стороннего кода для AWS SDK - это поставить перед ним протокол и заменить его на двойной тест.

protocol AWSSDKWrapper {

  static func initialize() // this should actually match the signature of the
                           // initialize method on AWSSDKService, but I don't
                           // know how it looks like
}

extension AWSSDKService: AWSSDKWrapper { }

Вы можетезатем вставьте зависимость от AWS в init вашего типа.

class HomeInteractor {

  private let awsSDKWrapper: AWSSDKWrapper

  init(awsSDKWrapper: AWSSDKWrapper = AWSSDKService.self) {
    self.awsSDKWrapper = awsSDKWrapper
  }

  func initSDK () {
    let launchParams: [String:String] = // ...

    awsSDKWrapper.initialize(withLaunchParams: launchParams) {[weak self] (success, error) in
      if success {
        self?.myPresenter?.onSDKInitlized()
        self?.didSdkInitilized = true
      } else {
        self?.myPresenter?.onError(errorText: error?.localizedDescription ?? "")
      }
    }
  }
}

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

struct AWSSDKStub: AWSSDKWrapper {

  let result: Result<Bool, NSError>  // I'm assuming the type of error the AWSSDK
                                     // callback returns is NSError.
                                     // I'm also assuming you have Swift 5 Result
                                     // available, if you don't check this Gist for
                                     // a lightweight drop-in replacement

  init(succeeding: Bool) {
    self.result = .success(succeeding)
  }

  init(error: NSError) {
    self.result = .failure(error)
  }

  func initialize(/* again not sure how the arguments look like */) {
    switch result {
      case .success(let succeeded): callback(succeeded)
      case .failure(let error): error
    }
  }
}

В зависимости от того, какое поведение вы хотите проверить, вы можете или не хотите добавлять значение пробника кзахватите launchParams, переданные для инициализации.

Теперь давайте используем этот объект для управления поведением в тестах.

func testAWSSDKInitializeSuccess() {
  let homeInteractor = HomeInteractor(awsSDWWrapper: AWSSDKStub(succeeding: true))

  // Because the test is asynchronous we need to setup the expectation _before_
  // calling its method.
  // 
  // More here: https://www.mokacoding.com/blog/xctest-closure-based-expectation/
  let predicate = NSPredicate(block: { any, _ in
    return (any as? HomeIterator)?.didSdkInitilized == true
  })
  _ = self.expectation(for: predicate, evaluatedWith: homeIterator, handler: .none)

  homeIntera.initSDK()

  waitForExpectations(timeout: 1, handler: .none)
}

Прелесть этого подхода в том, что мы также можем писать тесты для случаяв котором инициализация AWS SDK возвращает false или ошибку.Мы не смогли бы сделать это, если бы продолжали использовать конечную точку AWS, поскольку у нас нет контроля над тем, как она реагирует.

Дополнительные примечания

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

Вызов протокола оболочки Wrapper - это запах.В идеале вы должны использовать имя, которое фиксирует подмножество используемых вами функций сторонних производителей.

Написанный нами тест проверяет, установлено ли для didSdkInitilized значение true.Вопрос, который я хотел бы задать вам: «Это фактическое поведение HomeInteractor или это просто деталь реализации?»Трудно ответить, не зная больше о том, что должен делать HomeIterator, но я предполагаю, что didSdkInitilized - это только деталь реализации, и реальное поведение метода заключается в вызове onSDKInitlized() или onError(errorText:) для его презентатора.

Всегда лучше написать тест, ориентированный на поведение, а не на детали реализации.Тесты, сфокусированные на деталях реализации, мешают вам, когда вы хотите провести рефакторинг кода, то есть изменить его реализацию, не меняя его поведения.Тест, сфокусированный на поведении, поможет вам в долгосрочной перспективе.

В вашем случае возможный способ проверить, вызывает ли HomeInteractor презентатор при успешной инициализации стороннего SDK, - это использовать двойной тест длядокладчик, а затем проверить, были ли вызваны его методы.Подробнее об этом подходе здесь .


Надеюсь, это поможет.Напишите мне в Твиттере на @ mokagio , если вы хотите больше поговорить о тестировании в Swift.

0 голосов
/ 14 февраля 2019

Вы не должны выполнять сетевые вызовы внутри своих модульных тестов.Модульное тестирование предназначено для тестирования отдельных классов.Таким образом, вы должны заблокировать ваш сетевой вызов или внедрить ваши зависимости в класс, который вы тестируете (в вашем случае HomeInteractor).Кроме того, ваш модульный тест должен выполняться быстро, сетевые вызовы внутри вашего модульного теста сделают обратное.

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

...