5.3 KiB
Mocks 테스트 (모의 테스트)
Test doubles
객체 간 종속성으로 인해 테스트를 작성할 때 문제가 발생할 수 있습니다. 예를 들어, Tire
에 의존하거나 사용하는 Car
클래스가 있다고 가정해봅시다.
CarTests
는 Tire
라고 불리는 Car
를 테스트합니다. 이제 Tire
의 버그로 인해 CarTests
가 실패할 수 있습니다. (Car
는 괜찮습니다) 이는 "무엇이 망가졌는가?" 라는 질문에 대답하기 어려울 수 있습니다.
이러한 문제를 피하려면, CarTests
의 Tire
용 stand-in 객체를 사용할 수 있습니다. 이런 경우, PerfectTire
라는 Tire
용 stand-in 객체를 만듭니다.
PerfectTire
는 Tire
와 모두 동일한 public 함수와 프로퍼티를 가질 겁니다. 하지만, 이러한 일부 또는 전체의 함수 또는 프로퍼티에 대한 구현은 다를 수 있습니다.
PerfectTire
와 같은 객체는 "test doubles"로 불립니다. Test doubles는 관련 객체의 기능을 독립적으로 테스트하기 위한 "stand-in objects"로 사용됩니다. 여기 몇 가지 종류의 test doubles가 있습니다:
- Mock 객체: 테스트 객체에서 결과물을 받기 위해 사용됩니다.
- Stub 객체:테스트 객체의 입력을 제공하는 데 사용됩니다.
- Fake 객체: 원래 클래스와 비슷하지만, 단순화된 방식으로 작동합니다.
Mock 객체의 사용법부터 알아봅시다.
Mock
Mock 객체는 다른 객체와의 정확한 상호 작용을 열거하고, 무언가가 잘못되었을 때를 감지하는 데 초점을 둡니다. Mock 객체는 테스트가 진행되는 도중에 호출되어야 하는 메소드와 mock 객체가 반환해야 하는 값을 (미리) 알아야 합니다.
Mock 객체는 다음과 같은 이유로 훌륭합니다:
- 훨씬 더 빨리 테스트합니다.
- 인터넷에 연결되어 있지 않은 경우에도 테스트를 실행합니다.
- 클래스를 종속성과 분리하여 테스트하는 데 중점을 둡니다.
Swift에서 Mock 객체로 테스트 작성하기
Sample app
예를 들어, 인터넷에서 데이터를 검색하는 앱을 만들어 봅시다:
- 인터넷의 데이터는
ViewController
에 표시되어야 합니다. - 사용자 정의 클래스는 데이터를 가져오는 메서드를 지정하는
DataProviderProtocol
을 상속합니다.
DataProviderProtocol
은 다음과 같이 정의됩니다:
protocol DataProviderProtocol: class {
func fetch(callback: (data: String) -> Void)
}
fetch()
는 인터넷에서 데이터를 가져와 callback
클로저를 사용하여 데이터를 반환합니다.
다음은, DataProviderProtocol
프로토콜을 따르는 DataProvider
클래스 입니다.
class DataProvider: NSObject, DataProviderProtocol {
func fetch(callback: (data: String) -> Void) {
let url = URL(string: "http://example.com/")!
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) {
(data, resp, err) in
let string = String(data: data!, encoding: .utf8)
callback(data: string)
}
task.resume()
}
}
이 시나리오에서, fetch()
는 ViewController
의 viewDidLoad()
메소드에서 호출됩니다.
class ViewController: UIViewController {
// MARK: Properties
@IBOutlet weak var resultLabel: UILabel!
private var dataProvider: DataProviderProtocol?
// MARK: View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
dataProvider = dataProvider ?? DataProvider()
dataProvider?.fetch({ [unowned self] (data) -> Void in
self.resultLabel.text = data
})
}
}
DataProviderProtocol
의 Mock을 사용하여 테스트
ViewController
는 DataProviderProtocol
을 신뢰합니다. 뷰 컨트롤러를 독립적으로 테스트하려면, DataProviderProtocol
을 따르는 mock 객체를 만들 수 있습니다.
class MockDataProvider: NSObject, DataProviderProtocol {
var fetchCalled = false
func fetch(callback: (data: String) -> Void) {
fetchCalled = true
callback(data: "foobar")
}
}
fetch()
가 호출되면, fetchCalled
프로퍼티가 true
로 설정되어, 테스트에서 호출되었음을 확인할 수 있습니다.
다음 테스트에서는 ViewController
가 로드될 때, dataProvider.fetch()
를 호출하는지 확인합니다.
override func spec() {
describe("view controller") {
it("fetch data with data provider") {
let mockProvider = MockDataProvider()
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController") as! ViewController
viewController.dataProvider = mockProvider
expect(mockProvider.fetchCalled).to(beFalse())
let _ = viewController.view
expect(mockProvider.fetchCalled).to(beTrue())
}
}
}
테스트를 작성하는 것에 관해 관심이 있어서 더 배우고 싶다면, 다음을 참조하세요. https://realm.io/news/testing-in-swift/.