Как протестировать метод, который отправляет работу асинхронно в Swift

#ios #swift #xctest #xctestcase #xctestexpectation

#iOS #swift #xctest #xctestcase #xctestexpectation

Вопрос:

Мне нужно написать модульный тест для следующего метода

 func setLabelText(msg: String) {
     DispatchQueue.main.async {
          self.label.text = msg
      }
  }
  

Комментарии:

1. В вашем performUIUpdate примере поделитесь идеей, о которой вы подумали. Но не могли бы вы поделиться чем-то похожим на фактический код, который вы хотите протестировать? В противном случае мы предлагаем ответы на неопределенный вопрос. Сделайте вопрос более конкретным.

2. Мне нужно написать тест для следующего метода func setLabelText(msg: String) { DispatchQueue.main.async { self. label.text = сообщение } }

3. Можете ли вы отредактировать свой вопрос, чтобы разместить это там?

4. Обновлено @JonReid

Ответ №1:

Давайте предположим, что ваша тестовая настройка уже создает контроллер представления и вызывает loadViewIfNeeded() подключение любых розеток. (Это из главы 5 «Загрузка контроллеров представления».) И что этот контроллер представления находится в свойстве, которое я назову sut (имеется в виду тестируемая система).

Если вы напишете тестовый пример для вызова setLabelText(msg:) , а затем немедленно проверьте метку контроллера представления, это не сработает.

Если у вас была отправка в фоновый поток, тогда нам нужно было бы, чтобы тест дождался завершения потока. Но это не относится к этому коду.

Ваш производственный код вызывает setLabelText(msg:) из фона. Но тестовый код выполняется в основном потоке. Поскольку он уже находится в основном потоке, все, что нам нужно сделать, это выполнить цикл выполнения еще раз. Вы можете выразить это с помощью вспомогательной функции, которую я представляю в главе 10 «Тестирование навигации между экранами»:

 func executeRunLoop() {
    RunLoop.current.run(until: Date())
}
  

С этим, вот тест, который работает:

 func test_setLabelText_thenExecutingMainRunLoop_shouldUpdateLabel() throws {
    sut.setLabelText(msg: "TEST")
    executeRunLoop()
    
    XCTAssertEqual(sut.label.text, "TEST")
}
  

Это успешно тестирует метод и быстро завершается. Но что, если придет другой программист и изменится setLabelText(msg:) , вытащив self.label.text = msg вызов за пределы DispatchQueue.main.async ? Я описываю эту проблему в главе 13 «Тестирование сетевых ответов (и замыканий)», в разделе «Сохранение асинхронного кода в его закрытии». В принципе, мы хотим проверить, что метка не меняется, когда отправленное закрытие не выполняется. Мы можем сделать это со вторым тестом:

 func test_setLabelText_withoutExecutingMainRunLoop_shouldNotUpdateLabel() throws {
    sut.label.text = "123"
    
    sut.setLabelText(msg: "TEST")
    
    XCTAssertEqual(sut.label.text, "123")
}
  

Ответ №2:

Здравствуйте, на мой взгляд, использование XCTestExpectation может принести пользу, поскольку этот API предназначался для тестирования асинхронных операций (подробнее об этом вы можете прочитать здесь)

Что касается сравнения между ними, единственное, о чем я мог подумать, это то, что с XCTestExpectation вами вы сможете протестировать тайм-ауты сервера (скажем, ваш api не отвечает в ожидаемое время. URLSession имеет время ожидания по умолчанию 60 секунд) с определенным кодом ошибки и сообщением.

Ответ №3:

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