๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ iOS

[iOS/Swift] ๋น„๋™๊ธฐ(async) ํ…Œ์ŠคํŠธ

by ํ‹ด๋”” 2023. 4. 14.
728x90
๋ฐ˜์‘ํ˜•

์ฐธ๊ณ  ์‚ฌ์ดํŠธ ๋ฐ ์ถœ์ฒ˜

์›๋ฌธ Kodeco์˜ iOS Unit Testing and UI Testing Tutorial์„ ๋ฒˆ์—ญํ•œ yoonbumatae์˜ ๊ธ€์„ ์ •๋ฆฌํ•œ ํฌ์ŠคํŒ…์ž…๋‹ˆ๋‹ค. ๋” ์ž์„ธํ•œ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•˜์‹œ๋ ค๋ฉด ์•„๋ž˜ ๋‘ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”!

https://www.kodeco.com/21020457-ios-unit-testing-and-ui-testing-tutorial

 

iOS Unit Testing and UI Testing Tutorial

Learn how to add unit tests and UI tests to your iOS apps, and how you can check on your code coverage.

www.kodeco.com

http://yoonbumtae.com/?p=4020 

 

Swift(์Šค์œ„ํ”„ํŠธ): iOS ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit test) ๋ฐ UI ํ…Œ์ŠคํŠธ ํŠœํ† ๋ฆฌ์–ผ - BGSMM

์›๋ฌธ iOS Unit Testing and UI Testing Tutorial   ๋ฒ„์ „ Swift 5, iOS 14, Xcode 12   iOS ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit test) ๋ฐ UI ํ…Œ์ŠคํŠธ ํŠœํ† ๋ฆฌ์–ผ iOS ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ๊ฑฐ์ฐฝํ•˜์ง€ ์•Š์ง€๋งŒ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ์•ฑ์ด

yoonbumtae.com

 

Intro

  • ์œ ๋‹› ํ…Œ์ŠคํŠธ๋Š” ์•ˆ์ •์ ์œผ๋กœ ์ง„ํ–‰๋˜์–ด์•ผ ํ•˜๋ฉฐ ํ…Œ์ŠคํŠธ ๋™์•ˆ request๊ฐ€ ์ผ์–ด๋‚  ๊ฒฝ์šฐ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ด์œ ๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์˜ํ–ฅ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ

Tutorial

  • ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ๋ฒ•์€ URLSession์„ ํ™•์žฅํ•˜๊ณ  ํ”„๋กœํ† ์ฝœ์„ ์ƒ์„ฑํ•ด์„œ URLSession์ด ์ฑ„ํƒํ•˜๋„๋ก ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๊ธฐ์กด์— URLSession์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ URLSession๊ณผ ๊ฐ™์€ ๋ชจ์–‘์˜ Mock ์—ญํ™œ์„ ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
typealias DataTaskCompletionHandler = (Data?, URLResponse?, Error?) -> Void

(ํ•ด๋‹น ์ฝ”๋“œ๋Š” ๊ตฌ raywenderlich ํ˜„ kodeco์—์„œ ์ฐธ๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ํ•ด๋‹น ํฌ์ŠคํŒ… ์ƒ๋‹จ์— ์žˆ์Šต๋‹ˆ๋‹ค)

 

  • typealias๋กœ ์ •์˜ํ•œ DataTaskCompletionHandler๋Š” URLSession์˜ dataTask์˜ ํด๋กœ์ ธ ๋ฐ˜ํ™˜๊ฐ’๊ณผ ๋™์ผํ•œ ํ˜•ํƒœ. ์ •์˜ ํ•ด์ค˜๋„ ๋˜๊ณ  ์•ˆํ•ด์ค˜๋„ ๋จ. (์•„๋ž˜ ์ฒ˜๋Ÿผ ์ฝ”๋“œ๊ฐ€ ์ข€๋” ๊น”๋”ํ•จ)
protocol URLSessionProtocol {
  func dataTask(
    with url: URL,
    completionHandler: @escaping DataTaskCompletionHandler
  ) -> URLSessionDataTask
}
  • URLSession์˜ dataTask ํ•จ์ˆ˜์™€ ๋™์ผํ•œ ํ˜•ํƒœ์˜ protocol์„ ์ •์˜ํ•ด ์คŒ
extension URLSession: URLSessionProtocol { }
var urlSession: URLSessionProtocol = URLSession.shared
  • URLSession์ด URLSessionProtocol์„ ์ฑ„ํƒํ•˜๋ฉด URLSession์€ ๊ธฐ์กด์— dataTask๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
  • ํ…Œ์ŠคํŠธํ•  ๊ฐ์ฒด์˜ URLSession์„ ์„ค์ •ํ•  ๋•Œ ํƒ€์ž…์„ URLSessionProtocol ํƒ€์ž…์œผ๋กœ ์ง€์ •ํ•˜๋ฉด ๊ธฐ์กด์˜ URLSession์„ ์‚ฌ์šฉํ•˜๋ฉด์„œ๋„  URLSession ์ฒ˜๋Ÿผ ํ–‰๋™ํ•˜๋Š” ํ…Œ์ŠคํŠธ์šฉ ๊ฐ€์งœ URLSession์„ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ์Œ
class URLSessionStub: URLSessionProtocol {
  private let stubbedData: Data?
  private let stubbedResponse: URLResponse?
  private let stubbedError: Error?

  public init(data: Data? = nil, response: URLResponse? = nil, error: Error? = nil) {
    self.stubbedData = data
    self.stubbedResponse = response
    self.stubbedError = error
  }

  public func dataTask(
    with url: URL,
    completionHandler: @escaping DataTaskCompletionHandler
  ) -> URLSessionDataTask {
    URLSessionDataTaskStub(
      stubbedData: stubbedData,
      stubbedResponse: stubbedResponse,
      stubbedError: stubbedError,
      completionHandler: completionHandler
    )
  }
}
  • URLSessionStub์€ URLSession๊ณผ ๊ฐ™์€ ์—ญํ™œ์„ ํ•˜์ง€๋งŒ ์‹ค์ œ request๋Š” ํ•˜์ง€ ์•Š๋Š” ๊ฐ์ฒด
  • UnitTest์—์„œ URLSession์„ ์ฃผ์ž…ํ•˜์ง€ ์•Š๊ณ  URLSessionStub์„ ์ฃผ์ž…ํ•ด์„œ ์‚ฌ์šฉ

UnitTest

  func testStartNewRoundUsesRandomValueFromApiRequest() {
    // given
    // 1
    let stubbedData = "[1]".data(using: .utf8)
    let urlString = "http://www.randomnumberapi.com/api/v1.0/random?min=0&max=100&count=1"
    let url = URL(string: urlString)!
    let stubbedResponse = HTTPURLResponse(
      url: url,
      statusCode: 200,
      httpVersion: nil,
      headerFields: nil)
    let urlSessionStub = URLSessionStub(
      data: stubbedData,
      response: stubbedResponse,
      error: nil)
    sut.urlSession = urlSessionStub
    let promise = expectation(description: "Completion handler invoked")

    // when
    sut.startNewRound {
      // then
      // 2
      XCTAssertEqual(self.sut.targetValue, 1)
      promise.fulfill()
    }
    wait(for: [promise], timeout: 5)
  }

(ํ•ด๋‹น ์ฝ”๋“œ๋Š” ๊ตฌ raywenderlich ํ˜„ kodeco์—์„œ ์ฐธ๊ณ  ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ํ•ด๋‹น ํฌ์ŠคํŒ… ์ƒ๋‹จ์— ์žˆ์Šต๋‹ˆ๋‹ค)

 

    let stubbedData = "[1]".data(using: .utf8)
    let urlString = "http://www.randomnumberapi.com/api/v1.0/random?min=0&max=100&count=1"
  • urlString๋Š” url์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ๋ณด๋‚ธ ๊ฐ’ ๋Œ€๋กœ ์ตœ์†Œ 0, ์ตœ๋Œ€ 100์˜ ํ•œ ๊ฐœ์˜ ๋žœ๋ค ์ˆซ์ž๋ฅผ ์–ด๋ ˆ์ด์— ๋‹ด์•„ ๋ณด๋‚ด ์คŒ
  • stubbedData์— ์ „๋‹ฌํ•˜๋Š” ์ŠคํŠธ๋ง ๊ฐ’์ด stub ๋ฐ์ดํ„ฐ๋กœ, ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์˜ˆ์ƒํ•œ ๋™์ž‘์„ ์œ„ํ•ด ํ•„์š”ํ•œ ๊ฐ’. ์ด๋ฅผ Data๋กœ ๋ฐ”๊พธ์–ด ์ฃผ๋Š”๋ฐ, ์ด ๊ฐ’์„ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๊ฐ’๊ณผ ๋น„๊ตํ•˜๊ฒŒ ๋˜๊ณ  ์‹ค์ œ request๋Š” ์ผ์–ด๋‚˜์ง€ ์•Š์Œ
    let urlSessionStub = URLSessionStub(
      data: stubbedData,
      response: stubbedResponse,
      error: nil)
    sut.urlSession = urlSessionStub
class BullsEyeGame {
	.. ์ƒ๋žต
  var urlSession: URLSessionProtocol = URLSession.shared
  	.. ์ƒ๋žต
}
  • URLSessionStub์€ URLSessionProtocol์„ ์ฑ„ํƒํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธํ•  ํด๋ž˜์Šค์ธ BullsEyeGame์˜ urlSession์œผ๋กœ ์ฃผ์ž…ํ•ด ์ค„ ์ˆ˜ ์žˆ์Œ
    let promise = expectation(description: "Completion handler invoked")
    wait(for: [promise], timeout: 5)
  • expectation์„ ์‚ฌ์šฉํ•ด์„œ ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ ํ•  ์ˆ˜ ์žˆ์Œ. ๋น„๋™๊ธฐ ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” XCTestExpectation ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•จ
  • 5์ดˆ ๋™์•ˆ ํ…Œ์ŠคํŠธ ์‹คํ–‰์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆผ
  • fulfill()์„ ํ˜ธ์ถœํ•ด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚ฌ์Œ์„ ์•Œ๋ฆด ์ˆ˜ ์žˆ์Œ
    sut.startNewRound {
      // then
      // 2
      XCTAssertEqual(self.sut.targetValue, 1)
      promise.fulfill()
    }
  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด์„œ ๋‚˜์˜จ ๊ฐ’๊ณผ ์˜ˆ์ƒํ•œ ๊ฐ’์ด ๊ฐ™๋‹ค๊ณ  ์ •์˜ ํ•œ ๋’ค fulfill์„ ํ†ตํ•ด ๋น„๋™๊ธฐ ํ…Œ์ŠคํŠธ๋ฅผ ๋๋ƒ„

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€