무엇을 테스트해야 하며 어떻게 시작해야 할까?
* 이 글은 Swift 5를 기준으로 작성했다.
* 글의 목적: "클라이언트 개발자의 TDD" 매거진은 그동안 테스트를 위해 학습을 했던 것을 공유하고자 한다.
테스트를 시작했을 때 가장 첫 번째 고민은 "무엇을 테스트해야 하는가?"였다.
사실 우리가 작성한 코드는 모두 테스트 대상이다. 하지만 아직 테스트에 능숙하지 않다면 아래의 2가지에만 신경 써보자.
뷰를 추상화한 부분과, Model
* 주 : 모델은 데이터, 비즈니스 논리, 서비스 클라이언트 등으로 구성된다.
단순히 데이터 모델만을 이야기하는 것이 아니다.
UI 테스트는 깨지기 쉽고 작성하는 비용과 실행 비용이 많이 든다. EarlGrey, Hammer 등을 사용해서 실행 비용을 낮출 수 있지만 작성 비용은 여전히 높은 편이다. 그래서 테스트 입문자에게 권장하는 테스트 대상은 비교적 테스트 작성이 쉽고 잘 깨지지 않는 뷰를 추상화 한 부분과 Model의 테스트다.
몇몇 아키텍처로 예를 들어보면 아래와 같다.
- MVVM은 ViewModel , Model 이 테스트 대상이다.
- ReactorKit은 Reactor, Model 이 테스트 대상이다.
- RIBs는 Interactor, Router, Presenter, Builder 모두 테스트 대상이지만 Interactor 테스트에 집중하는 것도 좋다. ( RIBs는 테스트 입문자에게는 적합하지 않다고 생각한다. )
기타 다른 아키텍처를 사용한다면 해당 아키텍처의 앱의 핵심 논리를 수행하는 부분을 찾자.
그것이 당신이 테스트해야 할 대상이다.
*주) 각 아키텍처 별로 샘플 테스트 케이스를 비교해 보는 글도 작성해볼 예정이다.
기존의 코드의 테스트를 작성하는 것은 별도의 글로 작성하려 한다.
여기서는 오픈소스 프로젝트를 시작하며 새롭게 작성하는 것을 기준으로 설명하겠다.
Logger 오픈소스를 만들어 보려고 한다.
예전 회사에서 만들어서 쓰던 건데, 기억을 더듬어 좀 더 고도화하고 오픈소스로 만들어 볼까 한다.
코드를 작성하기 전에 먼저 요구사항을 정리해보자.
- 로그의 레벨은 5단계로 나눈다.
- 종류는 Http Network와 일반 로그로 나눈다.
- 레벨, 종류, 키워드에 따라 필터링되도록 한다.
- 종류별로 보이는 UI를 다르게 하고 싶다.
- 로그는 실행 스레드, 시간, 파일, 라인, 함수 이름 등을 보여주고 싶다.
- 로그는 파일로도 남기며 export 하고 싶다.
- Network 로그의 경우 실패 성공 duration 등 좀 더 자세한 부가 정보들을 포함하고 싶다.
요구사항을 작성했다. 이제 시작해보자.
( 테스트 타깃 추가 등 프로젝트 구성 )
SPM으로 TLLogger를 구성하고 워크스페이스를 만들어 local SPM으로 TLLoger를 사용하도록 했다.
이후 테스트 타깃(TLLoggerTest)을 가진 프로젝트 하나를 추가하고 TLLoger 라이브러리를 링크했다.
Edit Scheme...로 들어가서 SPM 라이브러리의 테스트 번들을 설정해주자.
테스트는 testDummy 함수만 하나 추가하고 내용은 아직 작성하지 않은 채로 테스트를 한번 돌려보자.
cmd + U 고고!
오케이! 테스트 작성을 위한 준비가 끝났다.
테스트를 작성할 때에는 먼저 준비, 액션, 검증을 생각하자.
- 준비는 액션을(혹은 검증) 위한 준비 과정이다.
- 때문에 작성할 때에는 주로 액션과 검증에 집중하자.
첫 번째 요구사항에 대한 테스트를 작성해보자.
요구사항: 로그의 레벨은 5단계로 나눈다.
로그 레벨을 5단계로 나눈다는 건 어떤 액션이 있고, 검증이 있을까?
- 액션 : 로그를 남길 때 레벨을 설정하는 것
- 검증 : 설정된 레벨로 출력을 보여주는 것
로그는 debug console 에도 보여주겠지만 오픈소스에서는 UI로도 보여주는 것이 주요 기능이다.
때문에 검증은 테이블 뷰에서 사용할 데이터 소스를 만드는 것으로 검증하자. TDD로 진행해보자
* 주) TDD를 할 때에는 켄트 백이 이야기한 2가지 원칙만 기억하자.
1. 테스트 코드를 작성하기 전에 실제 코드를 작성하지 않는다.
2. 중복을 제거한다.
작성한 테스트 코드다.
sut ( system under test : 테스트 대상 시스템 )는 테스트 대상, 즉 Log이다.
입력으로 5개의 레벨로 로그를 남겨봤고 데이터 소스는 최근의 로그가 상단에 위치시키기 위해 순서는 역전시킨걸 기댓값으로 뒀다.
*주) AAA ( Arrange, Act, Assert )를 주석으로 사용했는데 GWT ( given, when, then ) 와의 차이점은 다른 글에서 설명하려 한다.
테스트를 통과시키기 위해 최소한의 구현으로 dataSources에 고정값을 넣어줬다.
이제 실제 코드를 작성하자
원하는 동작을 구현했다.
구현 코드는 더 이상 리펙토링 할 부분은 없어 보인다.
그런데 테스트 코드가 좀 장황해서 가독성을 높이고 싶다는 생각이 들었다.
검증 범위는 그대로 둔 채 테스트를 수정했다.
* 주) LogData struct의 Equtable 은 테스트 쪽에 작성했는데, 테스트를 위한 코드이기 때문이다.
실제 구현부에서는 현재 Equtable 특성이 필요하지 않기 때문이다.
첫 번째 테스트 작성을 완료했다. 쉽지 않은가?
지금은 아주 작은 코드 조각이지만, 앞으로 만들어가면서 이 테스트 코드가 어떻게 활용되는지,
다른 테스트 코드들은 또 어떻게 작성되는지 알아보자 :)
작성된 코드는 https://github.com/tilltue/TLLogger 에서 확인 가능하다.
커밋 로그는 https://github.com/tilltue/TLLogger/commit/c3d37647dff2b19c8ff9caac5d99be44992b65b7 여기
마침.