iOS 개발 기록

[Test] Concurrency Test 본문

테스트

[Test] Concurrency Test

택꽁이 2023. 10. 11. 10:21
728x90

📄

상황

AsyncSequence를 사용한 Concurrency 메서드를 테스트하는데 테스트가 종료되지 않는 상황이 발생했다. 로그도 뜨지 않고 왜 이런 상황이 발생하는 건지 파악도 잘 안됐다. 아마 테스트를 실행하는 스레드와 비동기 작업을 수행하는 스레드가 작업을 끝냈다는 것을 서로 전달하지 않아 데드락이 걸린게 아닐까 싶었다.

찾아보니 XCTestExpectation를 사용해서 비동기 메서드를 테스트할 때에 사용하는 코드가 있길래 이걸 조금 수정해서 사용하니 다행히 잘 돌아갔다.

해당 메서드 코드

import XCTest

extension XCTestCase {
  /// Test Concurrency method
  func execute(withTimeout timeout: TimeInterval,
               file: StaticString = #filePath,
               line: UInt = #line,
               workItem: @escaping (_ exp: XCTestExpectation) async throws -> Void) async throws {
    let expectation = XCTestExpectation(description: "wait for async function")
    let task = Task {
      do {
        try await workItem(expectation)
      }
      catch {
        XCTFail("\(error)", file: file, line: line)
      }
    }
    
    await fulfillment(of: [expectation], timeout: timeout)
    task.cancel()
  }

}

XCTestExpectation

let expectation = XCTestExpectation(description: "wait for async function")
  • 비동기 작업의 완료를 기다리고 테스트의 성공이나 실패를 판단하기 위해 사용하는 클래스.
  • 해당 클래스의 fulfill() 메서드를 호출하면 기대값을 충족했다고 판단한다.
  • 시간을 지정해서 지정된 시간 내에 기대값이 충족되지 않으면 테스트를 실패시킨다.

Task

let task = Task {
  do {
    try await workItem(expectation)
  }
  catch {
    XCTFail("\(error)", file: file, line: line)
  }
}
  • 파라미터로 받은 async메서드를 Task를 통해 비동기 작업을 수행한다.
  • 해당 작업을 수행할 때에 파라미터로 위에서 선언한 expectation을 넘겨준다. 비동기 작업을 수행한 후 해당 작업이 완료되었음을 파라미터로 전달한 expectation를 통해 알린다.

fulfillment

await fulfillment(of: [expectation], timeout: timeout)
  • 기존에는 wait(for:_, timeout:_)메서드를 통해 expectation의 결과값을 기다리는 시간을 지정했었던 것 같다.
  • 찾아보니 fulfillment메서드가 있어서 concurrency에 대응하게 수정했다.
  • 작업이 끝나고 task.cancel()로 태스크 종료

사용

func testSendThroughConcurrencyAdapter() async throws {
    /// 1. execute 메서드를 통해 테스트 수행, 
    /// 5초의 대기시간 동안 기다린 후 fulfill이 호출되지 않으면 테스트 실패
    try await execute(withTimeout: 5) { exp in
      //given
      let adapter = NuguAdapter(textAgent: StubTextAgent())
      
      //when
      try await adapter.send(message: "안녕")
      for await response in adapter {
        if case let .success(message) = response {
          guard let text = try message?.text?.strippingHTML() else { return }
          // then
          XCTAssertEqual(text, "저를 잊지 않고 찾아줘서 기뻐요! 오늘도 웃음 가득한 하루 되길 바라요.")
          /// 2. 메서드 수행 후에 클로저를 통해 전달받은 expectation의 fulfill메서드 수행.
          exp.fulfill()
        }
      }
    }
  }
  • 사용 흐름은 주석 참고!

Reference

fulfillment(of:timeout:enforceOrder:) | Apple Developer Documentation
Waits on a group of expectations for up to the specified timeout, optionally enforcing their order of fulfillment.
https://developer.apple.com/documentation/xctest/xctestcase/4109476-fulfillment
Handling never finishing async functions in Swift package tests
Why does my CI never finish and post a message to the merge request? Logged in to CI and oh, my merge job had been running for 23 minutes already, although typically it finishes in 4 minutes. What …
https://augmentedcode.io/2022/10/31/handling-never-finishing-async-functions-in-swift-package-tests/


Uploaded by N2T