swift

테스트 더블이란

j2kb 2023. 4. 13. 08:35
반응형

테스트 더블(Test Double)이란 저자인 xUnit Test Patterns의 제라드 메스자로스(Gerard Meszaros)가 만든 용어로 테스트를 진행하기 어려울 때 대신 테스트를 진행할 수 있도록 도와주는 객체를 말합니다.

테스트 객체는 크게 5개의 종류로 나뉩니다요.

 

Dummy

Martin Fowler에 의하면 Dummy는 전달되지만 실제로는 쓰지 않을 객체를 말합니다. 그저 init할 때 parameter를 채우기 위한 용도로만 쓰이구요. 내부 구현은 하지 않습니다.

protocol SomeProtocol {
	func doSomething()
	func logSomething() -> String
}

class DummySomeProtocol: SomeProtocol {
	func doSomething() {}
	func logSomething() -> String {
		return ""
	}
}

Stub

Martin Fowler에 의하면 Stub은 미리 정한 값(예를 들면 더미값)을 테스트 시에 그대로 반환하는 역할을 합니다.

// 예제
class RecentKeywordsStub: RecentKeywordsRepository {
	let keywords = ["a", "b", "c"]
	func getRecentKeywords() -> [String] {
		return keywords
  }
}
// 테스트
func test_getRecentKeywords() {
   XCTAssertEqual(sut.getRecentKeywords(), sut.keywords)
}

Spy

스텁과 같은 역할을 하며 더불어 메소드 호출 여부와 호출 횟수를 기록하는 역할을 합니다. 솔직히 스파이는 써보진 않았습니다. 이 행위를 검증하는 역할을 Mock을 만들어서 검증을 많이 하였습니다. 그래도 일단 아래와 같이 사용할 수 있다고는 하네요. 테스트 마지막 부분을 보면 호출이 정확히 되었는지를 확인하고 있습니다

class QuotesServiceSpy: QuotesService {
    private (set) var quoteCalls: [[String]] = []
    private let stocks: [Stock]
    private let error: Error?
    
    init(stocks: [Stock] = [], error: Error? = nil) {
        self.stocks = stocks
        self.error = error
    }
    
    func getQuotes(symbols: [String], 
                   completion: @escaping (Result<[Stock], Error>) -> Void) {
        quoteCalls.append(symbols)
        if let error = error {
            completion(.failure(error))
        } else {
            completion(.success(stocks))
        }
    }
}

class WatchlistViewModelTests: XCTestCase {    
    func test_getWatchlistWithSymbols_callGetQuotes() {
        let stockSymbols = ["AAPL", "MSFT", "GOOG"]
        let watchlistStore = WatchlistStoreStub(stockSymbols: stockSymbols)
        let quotesService = QuotesServiceSpy()

        let sut = WatchlistViewModel(store: watchlistStore, 
                                     service: quotesService)
        sut.getWatchlist()

				// 여기서 정확히 호출이 되었나를 확인해 테스트 통과/실패
        XCTAssertEqual(quotesService.quoteCalls[0], stockSymbols)
    }
}

Fake

Martin Fowler에 의하면 Fake 객체는 동작하는 구현을 실제로 가지고 있지만 실제 상품으로 내기보다는 테스트를 위해 간편하게 만든 구현을 가지고 있습니다

아래 Fake만보기의 start함수의 구현을 예로 들면 실제 걸음수를 가지고 와서 업데이트 하는 것이 아니라 1초마다 걸음 수를 올리는 것처럼 말이죠. 이렇게 하면 실제 디바이스가 아닌 시뮬레이터에서 테스트를 하더라도 걸음수가 올라가는지 테스트를 할 수 있겠죠?!

import Foundation

class SimulatorPedometer: Pedometer {
  var timer: Timer?
  var distance = 0.0

  func start()
    timer = Timer(timeInterval: 1, repeats: true) { _ in
      self.distance += 1
    }
    RunLoop.main.add(timer!, forMode: RunLoop.Mode.default)
  }
}

Mock

Martin Fowler에 의하면 Mock은 함수들이 원하는데로 호출되었는지를 검증할 때 쓴다고 합니다. 으음.. 저는 여기까지만 읽었을 때에는 Spy와 너무 비슷한 느낌이 드는데요..? 뭐가 다른건지 참.

다음에 네트워크를 Mocking하는 것을 한 번 정리해보려고합니다. 맨 밑에 이미지에 각 테스트 더블들의 범위(?)를 나타낸 사진이 있는데 솔직히 저는 Mock은 행위 검증이든 상태검증이든 다 할 수 있는 것 같은 느낌이 들더라구요.

아무튼 테스트 더블의 종류들의 경계가 살짝 모호한 것 같고 칼로 딱 나눠자를 수 없다는 생각이 들었는데 저는 아래 그림처럼 이해를 해보니까 조금 더 도움이 되더랍니다.ㅎㅎㅎㅎ.. 누군가에게 도움이 되었기를

*https://docs.microsoft.com/en-us/archive/msdn-magazine/2007/september/unit-testing-exploring-the-continuum-of-test-doubles*

Although these types seem distinct in theory, the differences become more blurred in practice. For that reason, I think it makes sense to think of test doubles as inhabiting a continuum, as illustrated in Figure 2. - MSDN Magazine -

 

참고 및 출처

https://medium.com/@pena.fernan/test-doubles-by-example-in-swift-558e9f47de52

https://hudi.blog/test-double/

https://mokacoding.com/blog/swift-test-doubles/

https://www.martinfowler.com/bliki/TestDouble.html

https://codinghack.tistory.com/92

https://sujinnaljin.medium.com/swift-mock-을-이용한-network-unit-test-하기-a69570defb41

https://www.swiftbysundell.com/articles/mocking-in-swift/

반응형