지난 번에 Phase 1에서는 그저 기능구현에 집중했습니다. 중간에 몇번 View에 대한 리팩토링을 하기는 했지만 코드 퀄리티는 전혀 신경 쓰지 않고 일단 기능을 구현하는데 집중했습니다.
원래는 아주 필수적인 기능인 단어 추가와 단어 공부 기능만 추가하고 바로 Phase 2로 넘어가려고 했습니다만 실제로 제가 일본어 공부를 하면서 앱을 사용하다가 보니 많은 기능이 추가적으로 필요했습니다.
따라서 단어-뜻의 구조가 아니라 한자-가나-뜻의 3단 구조를 가지도록 개선해야했고 단어 샘플을 가지고 있다가 해당 단어를 검색해서 바로 저장할 수 있는 기능도 필요했습니다.
이렇게 많은 기능을 추가하다보니 정말 수 많은 버그를 마주쳤고 버그를 수정하는 시간도 오래 걸렸습니다. 중간에 리팩토링을 할 때도 리팩토링 때문에 코드가 제대로 동작하지 않을 수도 있다는 두려움이 생기기도 했습니다.
하지만 기능 구현의 속도에만 집중한 코드였기 때문에 전혀 테스트할 수 없는 코드가 되고 말았습니다.
아래 코드는 ViewModel에 정의된 메소드의 예시입니다.
일종의 싱글톤을 구현하기 위한 방법으로 Service 안에 있는 메소드들을 전부 type method로 정의해두고 인스턴스를 생성하지 않고 바로 가져다가 사용하고 있습니다.
이렇게 하게 되면 Service layer와 ViewModel이 분리되지 않습니다. 따라서 ViewModel만 별도로 unit test를 할 수 없게 됩니다.
func saveBook() {
WordService.saveBook(title: bookName) { [weak self] error in
if let error = error { print("디버그 \(error.localizedDescription)"); return }
self?.bookName = ""
}
}
아래 Service 안에 구현된 메소드를 보면 Service 안에서 바로 Firebase의 객체를 가져오고 있습니다. 이렇게 하면 위 case의 ViewModel과 Service의 관계와 마찬가지로 Service가 Firebase에 의존하게 됩니다.
extension Constants {
enum Collections {
static let wordBooks =
Firestore.firestore()
.collection("develop")
.document("data")
.collection("wordBooks")
// ... 이하 생략 ...
}
static func saveBook(title: String, completionHandler: FireStoreCompletion) {
let data: [String : Any] = [
"title": title,
"timestamp": Timestamp(date: Date())]
Constants.Collections.wordBooks.addDocument(data: data, completion: completionHandler)
}
Phase 2의 목적은 코드를 Testable하게 수정하고 Unit test를 작성하는 것이 목표입니다. 원래 계획은 Unit test 외에도 UI Test와 Acceptance Test까지 추가하는 것이 목표였습니다만… 아직 구현하고 싶은 기능이 많아서요ㅎㅎ 빨리 Unit test에 집중하도록 하겠습니다.