Skip to content

Unit Testing

Sue Cho edited this page Dec 19, 2020 · 3 revisions

MVVM ๊ตฌ์กฐ Testing ํ•˜๊ธฐ

  • View์™€ ViewModel์ด Binding ๋˜์–ด ์žˆ๋Š” ๊ตฌ์กฐ๋กœ View์—์„œ ๋ฐœ์ƒํ•œ ํŠน์ • ์ด๋ฒคํŠธ๋กœ ์ธํ•ด ViewModel์˜ State ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด ๊ทธ ๊ฐ’์— ์˜ํ•ด View๊ฐ€ ์—…๋ฐ์ดํŠธ ๋จ
    • ๋”ฐ๋ผ์„œ, View๊ฐ€ ๊ทธ๋ ค์ง€๋Š” ๋กœ์ง ์—ญ์‹œ testing์ด ๊ฐ€๋Šฅํ•จ
  • ๊ฐ์ฒด ๊ฐ„ ์˜์กด๊ด€๊ณ„๊ฐ€ ๋†’์•„์ง€๋ฉด testing์ด ํž˜๋“ค์–ด์ง€๋ฏ€๋กœ ์ถ”์ƒํ™”๊ฐ€ ํ•„์š”ํ•จ. ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์—์„œ ์˜์กด๊ด€๊ณ„๋ฅผ ์‚ดํŽด๋ณด๋ฉด ViewModel - UseCase(+ EventLogger) - NetworkService ์ด๋ฏ€๋กœ,
    • ViewModel์„ test ํ•˜๊ธฐ ์œ„ํ•ด์„  UseCase๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๊ณ 
    • UseCase๋ฅผ test ํ•˜๊ธฐ ์œ„ํ•ด์„  NetworkService๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•จ.
    • ๋”ฐ๋ผ์„œ UseCase์™€ NetworkService๋ฅผ ์ถ”์ƒํ™”ํ•˜์—ฌ Unit Test๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ๋Š” ๋ชจ์˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰

Test ์˜ˆ์‹œ : ํŠน์ • ํŠธ๋ž™ '์ข‹์•„์š”' ํ•˜๊ธฐ (์„ฑ๊ณต)

TrackUseCase Testing

  • UseCaes๋Š” NetworkService์— ์˜์กด์„ฑ์„ ๊ฐ€์ง€๋ฏ€๋กœ ์ถ”์ƒํ™”๋œ NetworkServiceType์„ ์ฑ„ํƒํ•˜์—ฌ ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ ์„ฑ๊ณต์ ์ธ response๋ฅผ ๋Œ๋ ค์ฃผ๋Š” MockSuccessNetworkService ๊ตฌํ˜„
struct MockSuccessfulNetworkService: NetworkServiceType {
   let data: Data
   
   func request(url: String, request type: RequestType, body: Data?) -> AnyPublisher<Data, NetworkError> {
      return Just(data)
            .setFailureType(to: NetworkError.self)
            .eraseToAnyPublisher()
   }
}
  • UseCase๊ฐ€ '์ข‹์•„์š”' ์š”์ฒญ์„ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ๋ณด๋‚ด๊ณ  ๋Œ์•„์˜จ ์‘๋‹ต์ด ์˜ˆ์ƒ ํ–ˆ๋˜ ์‘๋‹ต๊ณผ ์ผ์น˜ํ•˜๊ณ  ์ตœ์ข…์ ์œผ๋กœ ์‹คํŒจํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๋ณด์žฅ๋˜์–ด์•ผ ํ•จ
func test_like_track_success() {
   let expectations = XCTestExpectation(description: "track like success test")
   defer { wait(for: [expectations], timeout: 5) }
        
   let response = TrackUseCase.LikeTrackResponse(success: true)
   let data = try? JSONEncoder().encode(likeTrack)
   let usecase = TrackUseCase(network: MockSuccessNetworkService(data: data!))

   usecase.likeTrack(id: 0)
      .sink { result in
         switch result {
         case .finished:
            expectations.fulfill() // ์„ฑ๊ณตํ–ˆ์œผ๋ฏ€๋กœ expectation fulfill
         case let .failure(error):
            XCTFail(error.localizedDescription) // ์„ฑ๊ณตํ•ด์•ผ ๋  ์š”์ฒญ์ด ์‹คํŒจํ•œ๋‹ค๋ฉด XCTFail
         }
      } receiveValue: { receivedResult in
         XCTAssertEqual(receivedResult, response.success) // ๋ฐ›์•„์˜จ ๊ฐ’์ด ์˜ˆ์ƒํ–ˆ๋˜ success = true์™€ ์ผ์น˜ํ•ด์•ผ ํ•จ
      }
      .store(in: &cancellables)
}

TrackViewModel Testing

  • ViewModel์€ UseCase์— ์˜์กด์„ฑ์„ ๊ฐ€์ง€๋ฏ€๋กœ TrackUseCase๊ฐ€ ์ถ”์ƒํ™”๋œ TrackuseCaseType์„ ์ฑ„ํƒํ•˜์—ฌ ์›ํ•˜๋Š” ๊ทธ๋Œ€๋กœ ๋ฐฉ์ถœํ•ด์ฃผ๋Š” MockTrackUseCase ๊ตฌํ˜„
struct MockTrackuseCase: TrackuseCaseType {
   let track: TrackInfo
   ...
   func likeTrack(id: Int) -> AnyPublisher<Bool, UseCaseError> {
      Just(true) // UseCase๋ฅผ ํ†ตํ•œ ์š”์ฒญ ๋ฐ ์‘๋‹ต์ด ์„ฑ๊ณต์ ์ด์—ˆ์Œ
         .setFailureType(to: UseCaseError.self)
         .eraseToAnyPublisher()
   }
}
  • ViewModel์˜ State ๊ฐ’์ด ๋ฐ”๋€” ์ˆ˜ ์žˆ๋„๋ก View์—์„œ ์ผ์–ด๋‚  ์ด๋ฒคํŠธ๋ฅผ ๋„ฃ์–ด์ฃผ์–ด ๊ฐ’์˜ ๋ณ€ํ™”๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋˜์—ˆ๋Š”์ง€ ๊ด€์ฐฐ
func test_like_trac() {
   let expectation = XCTestExpectation(description: "Like track test")
   defer { wait(for: [expectation], timeout: 5) }

   let usecase = MockTrackuseCase(track: track) // track.liked == false
   let eventLogger = MockEventLogger { } // EventLogger๋„ ๋ชจ์˜๋กœ ์ƒ์„ฑํ–ˆ์œผ๋ฉฐ, ์„œ๋ฒ„์—๊ฒŒ ๋ณด๋‚ด์งˆ ์ด๋ฒคํŠธ๊ฐ€ ๋งž๊ฒŒ ๋ถˆ๋ ธ๋Š”์ง€ ํ™•์ธํ•ด์•ผํ•จ (์šฐ์„  ์ƒ๋žต)
   let viewModel = TrackViewModel(track: track, usecase: usecase, eventLogger: eventLogger)

   viewModel.$state
      .sink { state in
         if state.track.liked { // track.liked == true ๋กœ ๋ฐ”๋€Œ์–ด์•ผ expectation fulfill
            expectation.fulfill()
         }
      }
      .store(in: &cancellables)

   viewModel.send(.like) // View์—์„œ ๋ฐœ์ƒํ•  ์ด๋ฒคํŠธ๋ฅผ ์คŒ
}
Clone this wiki locally