티스토리 뷰
테스트 코드부터 작성하는 것이 감이 안와서 테스트 코드보다 먼저 ArtistAPIService를 작성해버렸다... 🙄
이전 포스팅에서 만들었던 ArtistAPIType을 이용하여...!!!
protocol ArtistAPIServiceProtocol { | |
func fetchArtist(with id: String, completion: @escaping (Result<Artist?, APIError>) -> Void) | |
func fetchArtists(with id: [String], completion: @escaping (Result<[Artist], APIError>) -> Void) | |
} | |
class ArtistAPIService: ArtistAPIServiceProtocol { | |
func fetchArtist(with id: String, completion: @escaping (Result<Artist?, APIError>) -> Void) { | |
let apiType = ArtistAPIType.fetchOne(id: id) | |
guard let request = apiType.request else { return completion(.failure(.notValidRequest))} | |
URLSession.shared.dataTask(with: request) { (data, response, error) in | |
if let data = data { | |
do { | |
let artistResult = try JSONDecoder().decode(ArtistResult.self, from: data) | |
completion(.success(artistResult.artists.first)) | |
} catch { | |
completion(.failure(.notDecodingEnabled)) | |
} | |
} else { | |
completion(.failure(.notHasData)) | |
} | |
}.resume() | |
} | |
func fetchArtists(with id: [String], completion: @escaping (Result<[Artist], APIError>) -> Void) { | |
let apiType = ArtistAPIType.fetchMany(ids: id) | |
guard let request = apiType.request else { return completion(.failure(.notValidRequest))} | |
URLSession.shared.dataTask(with: request) { (data, response, error) in | |
if let data = data { | |
do { | |
let artistResult = try JSONDecoder().decode(ArtistResult.self, from: data) | |
completion(.success(artistResult.artists)) | |
} catch { | |
completion(.failure(.notDecodingEnabled)) | |
} | |
} else { | |
completion(.failure(.notHasData)) | |
} | |
}.resume() | |
} | |
} |
물론 다음과 같이 Artist모델도 만들어줬다...!!!
struct ArtistResult: Decodable { | |
let artists: [Artist] | |
enum CodingKeys: String, CodingKey { | |
case artists = "data" | |
} | |
} | |
struct Artist: Decodable { | |
let attributes: Attributes | |
let relationships: Relationships | |
} | |
struct Attributes: Decodable { | |
let genreNames: [String] | |
let name: String | |
} | |
struct Relationships: Decodable { | |
let albums: Albums | |
} | |
struct Albums: Decodable { | |
let currentAlbums: [Album] | |
let nextAlbumsPath: String | |
enum CodingKeys: String, CodingKey { | |
case currentAlbums = "data" | |
case nextAlbumsPath = "href" | |
} | |
} | |
struct Album: Decodable { | |
let id: String | |
let path: String | |
enum CodingKeys: String, CodingKey { | |
case id | |
case path = "href" | |
} | |
} |
그리고 Apple Music API 홈페이지에 들어가서 해당 API에 해당하는 더미데이터를 복붙하여 넣어준 더미데이터 파일을 만들고 테스트 코드 있는 쪽에 넣어준다 BananaMusicTests 그룹안에 넣어주면 된다 (나의 토이프로젝트 이름은 BananaMusic이기때문...ㅎㅎ!! )
// | |
// ArtistDummyData.swift | |
// BananaMusicTests | |
import Foundation | |
struct ArtistDummyData { | |
static let fetchOneJsonString = """ | |
{ | |
"data": [ | |
{ | |
"attributes": { | |
"genreNames": [ | |
"Rock" | |
], | |
"name": "Bruce Springsteen", | |
"url": "https://itunes.apple.com/us/artist/bruce-springsteen/id178834" | |
}, | |
"href": "/v1/catalog/us/artists/178834", | |
"id": "178834", | |
"relationships": { | |
"albums": { | |
"data": [ | |
{ | |
"href": "/v1/catalog/us/albums/1112053294", | |
"id": "1112053294", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1083046086", | |
"id": "1083046086", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1051321939", | |
"id": "1051321939", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1062111710", | |
"id": "1062111710", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1049523853", | |
"id": "1049523853", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1051327591", | |
"id": "1051327591", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1025011001", | |
"id": "1025011001", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1027452033", | |
"id": "1027452033", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/987471714", | |
"id": "987471714", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/955683364", | |
"id": "955683364", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/918626472", | |
"id": "918626472", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/947755049", | |
"id": "947755049", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/924130802", | |
"id": "924130802", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/928280298", | |
"id": "928280298", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/891847015", | |
"id": "891847015", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/843057439", | |
"id": "843057439", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/857968854", | |
"id": "857968854", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/797522110", | |
"id": "797522110", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/791259770", | |
"id": "791259770", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/741055385", | |
"id": "741055385", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/748097215", | |
"id": "748097215", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/860790154", | |
"id": "860790154", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/635191145", | |
"id": "635191145", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/604717239", | |
"id": "604717239", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/507691647", | |
"id": "507691647", | |
"type": "albums" | |
} | |
], | |
"href": "/v1/catalog/us/artists/178834/albums", | |
"next": "/v1/catalog/us/artists/178834/albums?offset=25" | |
} | |
}, | |
"type": "artists" | |
} | |
] | |
} | |
""" | |
static let fetchManyJsonString = """ | |
{ | |
"data": [ | |
{ | |
"attributes": { | |
"genreNames": [ | |
"Rock" | |
], | |
"name": "Bruce Springsteen", | |
"url": "https://itunes.apple.com/us/artist/bruce-springsteen/id178834" | |
}, | |
"href": "/v1/catalog/us/artists/178834", | |
"id": "178834", | |
"relationships": { | |
"albums": { | |
"data": [ | |
{ | |
"href": "/v1/catalog/us/albums/1112053294", | |
"id": "1112053294", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1083046086", | |
"id": "1083046086", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1051321939", | |
"id": "1051321939", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1062111710", | |
"id": "1062111710", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1049523853", | |
"id": "1049523853", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1051327591", | |
"id": "1051327591", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1025011001", | |
"id": "1025011001", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1027452033", | |
"id": "1027452033", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/987471714", | |
"id": "987471714", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/955683364", | |
"id": "955683364", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/918626472", | |
"id": "918626472", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/947755049", | |
"id": "947755049", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/924130802", | |
"id": "924130802", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/928280298", | |
"id": "928280298", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/891847015", | |
"id": "891847015", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/843057439", | |
"id": "843057439", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/857968854", | |
"id": "857968854", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/797522110", | |
"id": "797522110", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/791259770", | |
"id": "791259770", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/741055385", | |
"id": "741055385", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/748097215", | |
"id": "748097215", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/860790154", | |
"id": "860790154", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/635191145", | |
"id": "635191145", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/604717239", | |
"id": "604717239", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/507691647", | |
"id": "507691647", | |
"type": "albums" | |
} | |
], | |
"href": "/v1/catalog/us/artists/178834/albums", | |
"next": "/v1/catalog/us/artists/178834/albums?offset=25" | |
} | |
}, | |
"type": "artists" | |
}, | |
{ | |
"attributes": { | |
"genreNames": [ | |
"Rock" | |
], | |
"name": "Bob Dylan", | |
"url": "https://itunes.apple.com/us/artist/bob-dylan/id462006" | |
}, | |
"href": "/v1/catalog/us/artists/462006", | |
"id": "462006", | |
"relationships": { | |
"albums": { | |
"data": [ | |
{ | |
"href": "/v1/catalog/us/albums/1099677284", | |
"id": "1099677284", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1107753504", | |
"id": "1107753504", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1110230347", | |
"id": "1110230347", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1100815026", | |
"id": "1100815026", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1041144003", | |
"id": "1041144003", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1041169203", | |
"id": "1041169203", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1053284864", | |
"id": "1053284864", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/1025066054", | |
"id": "1025066054", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/945762927", | |
"id": "945762927", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/963786366", | |
"id": "963786366", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/952097786", | |
"id": "952097786", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/949629284", | |
"id": "949629284", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/935305035", | |
"id": "935305035", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/925180674", | |
"id": "925180674", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/924783871", | |
"id": "924783871", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/940024611", | |
"id": "940024611", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/941888332", | |
"id": "941888332", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/934218391", | |
"id": "934218391", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/933374405", | |
"id": "933374405", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/923147717", | |
"id": "923147717", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/919153202", | |
"id": "919153202", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/902876924", | |
"id": "902876924", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/900485117", | |
"id": "900485117", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/890600867", | |
"id": "890600867", | |
"type": "albums" | |
}, | |
{ | |
"href": "/v1/catalog/us/albums/889903193", | |
"id": "889903193", | |
"type": "albums" | |
} | |
], | |
"href": "/v1/catalog/us/artists/462006/albums", | |
"next": "/v1/catalog/us/artists/462006/albums?offset=25" | |
} | |
}, | |
"type": "artists" | |
} | |
] | |
} | |
""" | |
} |
그리고 실제 서버에서 주는 데이터가 아니라 이 더미데이터를 파싱하는 ArtistAPIServiceStub 을 만든다 (Stub, Mock, Spy 등등 다양한 용어들이 있던데 Stub이 정확한 용어가 아닐 수 도 있지만 일단 Stub이라고 하기!)
import Foundation | |
@testable import BananaMusic | |
struct ArtistAPIServiceStub: ArtistAPIServiceProtocol { | |
func fetchArtist(with id: String, completion: @escaping (Result<Artist?, APIError>) -> Void) { | |
let data = ArtistDummyData.fetchOneJsonString.data(using: .utf8)! | |
do { | |
let artistResult = try JSONDecoder().decode(ArtistResult.self, from: data) | |
completion(.success(artistResult.artists.first)) | |
} catch { | |
completion(.failure(.notDecodingEnabled)) | |
} | |
} | |
func fetchArtists(with id: [String], completion: @escaping (Result<[Artist], APIError>) -> Void) { | |
let data = ArtistDummyData.fetchManyJsonString.data(using: .utf8)! | |
do { | |
let artistResult = try JSONDecoder().decode(ArtistResult.self, from: data) | |
completion(.success(artistResult.artists)) | |
} catch { | |
completion(.failure(.notDecodingEnabled)) | |
} | |
} | |
} |
그다음 이제 Test코드를 만들어서 JSON 디코딩을 잘하는 지 테스트 해보자--!!
import XCTest | |
@testable import BananaMusic | |
class ArtistAPIServiceTest: XCTestCase { | |
var service: ArtistAPIServiceProtocol! | |
override func setUp() { | |
service = ArtistAPIServiceStub() | |
} | |
override func tearDown() { | |
} | |
func testFetchArtistDecoding() { | |
service.fetchArtist(with: "") { (result) in | |
switch result { | |
case .success: | |
XCTAssert(true, "디코딩 성공") | |
case .failure: | |
XCTFail("디코딩 실패") | |
} | |
} | |
} | |
func testFetchArtistsDecoding() { | |
service.fetchArtists(with: []) { (result) in | |
switch result { | |
case .success: | |
XCTAssert(true, "디코딩 성공") | |
case .failure: | |
XCTFail("디코딩 실패") | |
} | |
} | |
} | |
} |
두개의 테스트 모두 테스트 성공이라고 나온다...!!!
** 의문점
- XCTFail은 있는데 왜 XCTSuccess는 없는 것일까?!?!?!
- XCTAssert랑 XCTFail에 적은 메세지는 어디 나오는 것일까?!? 테스트 성공했을때 같이 나오는 것도 아니고 출력되지도 않던딩..?!?
나의 첫 TDD는 테스트 코드가 먼저 되지 못했지만, 제대로 TDD를 했다면
모델만들고 더미데이터를 만들고, ServiceProtocol을 정의 한후 ArtistServiceStub을 먼저 작성하여 테스트를 돌리고 그 다음에 실제 서비스에서 쓸 ArtistService를 만드는 순서일 것이다...!!!
그니까 나는
모델 -> ServiceProtocol -> ArtistService -> 더미데이터 -> ArtistServiceStub -> 테스트
순서으로 만들었지만
TDD로 한다면
모델 -> 더미데이터 -> ServiceProtocol-> ArtistServiceStub -> 테스트 -> ArtistService
이 순서가 되야하지 않을 까 싶다
** 궁금점
- Stub으로 만들어서 테스트 후, Service를 만들면 Stub을 서비스로 바꿔서 다시 테스트를 돌려야되나?!?!?
override func setUp() {
service = ArtistAPIServiceStub()
}
위의 코드를 밑의 처럼 바꿔서..?!?
override func setUp() {
service = ArtistAPIService()
}
'🍏 > TDD' 카테고리의 다른 글
[TDD] 간단한 계산기(나눗셈) 예제로 TDD 느낌을 느껴보자(?) (0) | 2019.07.31 |
---|---|
[TDD] Apple Music API를 이용한 TDD 예제 프로젝트 준비 (0) | 2019.07.25 |
- Total
- Today
- Yesterday
- Django Firebase Cloud Messaging
- 장고 URL querystring
- Python Type Hint
- Flutter Clipboard
- 장고 Custom Management Command
- flutter build mode
- flutter 앱 출시
- 플러터 얼럿
- flutter deep link
- PencilKit
- Flutter Spacer
- cocoapod
- Sketch 누끼
- github actions
- flutter dynamic link
- ipad multitasking
- 플러터 싱글톤
- Watch App for iOS App vs Watch App
- ribs
- METAL
- Django FCM
- Flutter 로딩
- Flutter Text Gradient
- Dart Factory
- Flutter getter setter
- Django Heroku Scheduler
- DRF APIException
- drf custom error
- 구글 Geocoding API
- SerializerMethodField
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |