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

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

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

person Sajal Gupta    schedule 02.11.2020    source источник
comment
В своем примере performUIUpdate поделитесь идеей, о которой вы подумали. Но не могли бы вы поделиться чем-то похожим на реальный код, который вы хотите протестировать? В противном случае мы предлагаем ответы на расплывчатый вопрос. Конкретизируйте вопрос.   -  person Jon Reid    schedule 03.11.2020
comment
Мне нужно написать тест для следующего метода func setLabelText(msg: String) { DispatchQueue.main.async { self.label.text = msg } }   -  person Sajal Gupta    schedule 05.11.2020
comment
Можете ли вы отредактировать свой вопрос, чтобы поместить это туда?   -  person Jon Reid    schedule 06.11.2020
comment
@JonReid Обновлено   -  person Sajal Gupta    schedule 26.11.2020


Ответы (3)


Предположим, что ваша тестовая установка уже создала контроллер представления и вызывает 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")
}
person Jon Reid    schedule 26.11.2020

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

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

person nishith Singh    schedule 02.11.2020

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

person RealNmae    schedule 02.11.2020