์ฐธ๊ณ ์ฌ์ดํธ ๋ฐ ์ถ์ฒ
์๋ฌธ 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
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์ ํตํด ๋น๋๊ธฐ ํ ์คํธ๋ฅผ ๋๋
'๐ iOS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[iOS/Swift] Alamofire ํ๋ก์ ํธ๋ฅผ ์ํ UnitTest (0) | 2023.04.21 |
---|---|
[Unit Test] Test Double (0) | 2023.04.14 |
[iOS/Swift] ๋๊ธฐ(sync) ํ ์คํธ (0) | 2023.04.13 |
[iOS/Swift] XCTestCase ์์ฑํ๊ธฐ (0) | 2023.04.13 |
[iOS/Swift] Unit Test ๋? (0) | 2023.04.10 |
๋๊ธ