테스트 코드의 맹점
이처럼 큰 가치를 주는 테스트코드에도 몇가지 맹점은 있습니다.
1.개발자의 상상력에 의존합니다.
개발자가 상상하지 못한 테스트케이스는 테스트코드로 구현할 수 없습니다. 이는 생각보다 큰 맹점입니다. 따라서, 다양한 테스트케이스를 생각해내기 위해 노력해야합니다. 미래 가치를 위하여는 더욱 그렇습니다.
2.테스트가 가능한 구조로 개발하기 어렵습니다.
제가 경험한 안에서 테스트코드를 작성할때 가장 어려웠던 부분은 기존에 테스트 불가능하게 만들어진 구조를 테스트 가능한 구조로 변경하는 것이었습니다.
신규 피쳐를 개발 할 때도, 정도의 차이는 있지만, 어려운 건 마찬가지입니다. 테스트 가능한 구조를 생각하지 않고 코드를 작성하는 것에 비해 체감상 20% 정도는 시간이 더 들어가는 것처럼 느껴집니다.
3.테스트코드를 개발하는데는 약간의 추가 시간이 필요합니다.
테스트가 가능한 구조를 확립했다면, 테스트코드를 작성해야합니다. 이는 상당한 시간을 필요로합니다. 최초 테스트코드를 작성할 때는, 기존 개발 대비 2배 가까운 시간을 소비하기도 했습니다.
현재는 테스트 코드 작성에 처음보다 훨씬 적은 시간이 들지만 그럼에도 “테스트코드 작성 중이니 추가 시간이 필요하다”라고 양해를 구해야 할 정도의 시간은 필요합니다.
4.깨지기 쉬운 테스트코드는 유지보수에 많은 노력이 듭니다.
테스트 코드는 서비스의 유지보수를 돕는 것입니다. 테스트코드 자체가 목적이 돼서는 안 됩니다. 따라서, 테스트 코드를 유지보수하느라 시간과 노력을 낭비한다면, 오히려 본질을 놓치는 일이 됩니다.
테스트코드를 작성할때는 최대한 테스트하고자하는 로직을 집중하고 폐쇄적으로 작성해야 합니다.
즉, 테스트코드가 테스트하고자하는 로직을 제외한 다른 부분이 변경되더라도 테스트코드는 영향받지 않아야 합니다.
안녕하세요.한국신용데이터(KCD) 앱서비스팀에서 안드로이드 개발자로 근무하고있는 hyun 이라고 합니다.
오늘은 저희 앱 서비스팀에서 운영중인 테스트코드 작성가이드를 중심으로 테스트코드가 주는 가치와 방법론에 대해서 이야기 해볼까 합니다.
테스트 코드의 가치
저는 테스트코드의 작성은 2가지 시점에서 빛을 발휘한다고 생각합니다.
1.현재 시점
개발자는 모든 경우의 수를 테스트하고 싶어합니다. 하지만 이는 실질적으로 불가능할 때가 많습니다. 예를 들어, 특정 상태(State)에서만 받을 수 있는 응답이라던가, 변칙적인 상황에 대한 상태(State)는 인위적으로를 만들기 어렵기 때문입니다. 잘 만든 테스트 코드는 이러한 상황을 완벽하게 커버할 수 있습니다. 생각할 수 있는 모든 상태와 행위에 대해서 테스트 가능하기 때문에 서비스의 품질 또한 안정적으로 좋아질 수 있는 구조입니다.
2.미래 시점
가끔 리팩토링에 대한 생각이 들때가 있습니다. 이때 리팩토링을 망설이게 하는 요인 중 하나는 어떻게 기존과 같은 정상동작을 보장할 것인가 하는 부분입니다. 과거에 작성해놓은 품질좋은 테스트코드가 있다면, 리팩토링 혹은 유지보수가 훨씬 용이해집니다. 특정 비즈니스 로직을 수정하더라도, 테스트코드에서 검증이 되기 때문에 코드 수정도 더 자신있게 할 수 있습니다.
게다가 리팩토링 뿐만 아니라, 모든 코드 수정에 대해서 안정성을 보장받게 됩니다. 테스트코드가 코드 수정으로 인한 결과물을 검증해주기 때문입니다.
테스트 코드의 맹점
이처럼 큰 가치를 주는 테스트코드에도 몇가지 맹점은 있습니다.
1.개발자의 상상력에 의존합니다.
개발자가 상상하지 못한 테스트케이스는 테스트코드로 구현할 수 없습니다. 이는 생각보다 큰 맹점입니다. 따라서, 다양한 테스트케이스를 생각해내기 위해 노력해야합니다. 미래 가치를 위하여는 더욱 그렇습니다.
2.테스트가 가능한 구조로 개발하기 어렵습니다.
제가 경험한 안에서 테스트코드를 작성할때 가장 어려웠던 부분은 기존에 테스트 불가능하게 만들어진 구조를 테스트 가능한 구조로 변경하는 것이었습니다.
신규 피쳐를 개발 할 때도, 정도의 차이는 있지만, 어려운 건 마찬가지입니다. 테스트 가능한 구조를 생각하지 않고 코드를 작성하는 것에 비해 체감상 20% 정도는 시간이 더 들어가는 것처럼 느껴집니다.
3.테스트코드를 개발하는데는 약간의 추가 시간이 필요합니다.
테스트가 가능한 구조를 확립했다면, 테스트코드를 작성해야합니다. 이는 상당한 시간을 필요로합니다. 최초 테스트코드를 작성할 때는, 기존 개발 대비 2배 가까운 시간을 소비하기도 했습니다.
현재는 테스트 코드 작성에 처음보다 훨씬 적은 시간이 들지만 그럼에도 “테스트코드 작성 중이니 추가 시간이 필요하다”라고 양해를 구해야 할 정도의 시간은 필요합니다.
4.깨지기 쉬운 테스트코드는 유지보수에 많은 노력이 듭니다.
테스트 코드는 서비스의 유지보수를 돕는 것입니다. 테스트코드 자체가 목적이 돼서는 안 됩니다. 따라서, 테스트 코드를 유지보수하느라 시간과 노력을 낭비한다면, 오히려 본질을 놓치는 일이 됩니다.
테스트코드를 작성할때는 최대한 테스트하고자하는 로직을 집중하고 폐쇄적으로 작성해야 합니다.
즉, 테스트코드가 테스트하고자하는 로직을 제외한 다른 부분이 변경되더라도 테스트코드는 영향받지 않아야 합니다.
Given-When-Then Pattern
본격적으로 테스트코드를 작성하는 방법론에 대해서 기술 해보도록 하겠습니다.
테스트코드도 특정 패턴을 채택해서 진행할경우 가독성도 증가하고 테스트코드의 생산성도 증가합니다.
Given
테스트를 위해 준비를 하는 과정입니다.
테스트에 사용될 변수 등에 상태를 부여합니다.
ex> 서버로부터 특정 데이터를 수신하는 상태를 부여합니다. 예시에는 null 을 수신합니다.
When
테스트 하고자 하는 액션을 추가합니다.
ex> 수신한 null 을 처리합니다.
Then
마지막은 테스트를 검증하는 과정입니다.
예상한 값, 실제 실행을 통해서 나온 값을 검증합니다.
ex> 예외처리가 잘 되었는지 확인합니다.
Architecture Diagram
Given-when-then pattern 과 같은 맥락으로 팀차원에서 운영하는 공통된 아키택쳐가 있다면 더 나은 테스트 코드를 작성할 수 있습니다.
Get Hyun’s stories in your inbox
Join Medium for free to get updates from this writer.
현재 안드로이드에서는 가이드에 맞는 아키택쳐를 적용시키기 위해 노력중이며 그 구조는 아래와 같습니다.
How to Test DataSource → Remote(Api)
아래의 샘플코드들은 실제 프로젝트에 작성된 코드를 약간 변형하여 만든 샘플입니다.
테스트코드 작성하기
DataSource는 Remote에 해당하는 영역에서 데이터를 가져옵니다.
이 구간에서의 테스트 목적은 추상화된 Remote를 이용하여 구체화된 DataSource 를 테스트 하는것 입니다.
1. 추상화된 Remote 만들기
interface AbstractApi {fun getFab(id: String?): Single<Response<String?>>
}
2. Given- 추상화 된 Remote Mocking 하여 State 만들기
AbstractApi를 상속받는 구체화된 Mocking class(TestApi) 를 만들 수 있습니다.
ConcreteDataSource는 구체화된 DataSource 입니다. ConcreteDataSource에 TestApi라는 Mocking된 데이터를 이용하여 초기화를 시킵니다.
class TestApi : AbstractApi {override fun getFab(id: String?): Single<Response<String?>> {
return Single.just(
Response(
success = true,
response = null
)
)
}
}
val api: TestApi = TestApi()
val dataSource = ConcreteDataSource(api)
3. When- 구체화된 ConcreteDataSource 에서, Mocking된 Remote 이용하기
테스트 하고자 하는 행위를 실행합니다.
4. Then- 결과 확인하기
assertTrue, assertEquals 등등을 이용하여 결과를 확인합니다.
전체 코드
Conclusion
테스트 커버리지보다 더 중요한 것은 테스트의 다양성입니다. 테스트코드 커버리지 100%를 달성하는 것보다, 40%의 커버리지로 테스트의 다양성을 확보하는 쪽이 서비스의 안정성에 더 기여한다고 생각합니다. 물론 너무 낮은 커버리지는 테스트코드로써 의미를 지니지 않을 수도 있습니다. 그럼에도, 일정 수준 이상의 커버리지를 확보했다면 다양성에 더 중점을 두어야합니다. 저희 앱서비스팀에서는 60% 이상의 테스트 커버리지를 권장중입니다.
테스트코드를 작성하는 현재 관점에서 상당한 투자로 느껴질 수 있습니다. 하지만, 테스트코드가 주는 미래안정성까지 고려한다면 충분히 가치있는 행위입니다.
저희의 생각이 정답은 아닙니다. 상기 기술한 아키택쳐, 패턴보다 더 다양한 방법론이 많은것으로 알고 있습니다. 이에 다양한 의견을 가지고, 발전시키고자 하는 많은 분들과 함께 하고 싶습니다.
읽어주셔서 감사합니다.