티스토리 뷰

728x90
반응형

테스트 코드부터 작성하는 것이 감이 안와서 테스트 코드보다 먼저 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"
}
}
view raw Artist.swift hosted with ❤ by GitHub

 

 

그리고 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()
    }

 

반응형
댓글

eungding님의
글이 좋았다면 응원을 보내주세요!