Unit Test 테스트란?

test

Unit 테스트는 가장 작은 단위(기능 또는 함수)에 대한 검증을 의미합니다.
일반적으로 Unit 테스트는 아래의 세가지 조건을 충족해야합니다.

  • 실제 프로덕션 코드에는 포함되지 않는다.
  • 네트워크와 같은 I/O나 UI등 외부 프레임워크에 의존하지 않는 순수한 로직만 검증한다.
  • 매번 실행할 때 마다 같은 결과가 나와야 한다. (순수함수)

실제 어플리케이션의 동작 전체를 검증하는 것은 통합테스트 또는 UI 테스트라고 하며,
외부 프레임워크에 의존하지 않는 순수한 비즈니스 로직을 검증합니다.



보통 테스트라면 많이 할수록 더 안정되고 꼼꼼해야할 것 같은 느낌이 듭니다.
그래서 Unit 테스트를 많이하면 UI테스트의 부담도 줄고 프로그램이 더 안정될 것 같은 느낌을 받습니다.
그러나 Unit 테스트와 통합테스트는 목적부터가 다릅니다.
그렇다고 UI 테스트가 잇으니까 Unit 테스트가 필요없다는 것은 아닙니다.

이번 포스팅에서는 Unit 테스트에 대한 오해와 필요성에 대해 소개합니다.




Unit 테스트의 함정

it's_a_trap

Unit 테스트가 프로그램의 품질을 결정하지 않는다.

Unit 테스트가 있다하더라도 통합테스트, UI 테스트는 진행되어야 하고 통합테스트의 부담이 줄어들지는 않습니다.
결국 Unit 테스트가 완성된 제품(프로그램)에 품질을 보장하지 않습니다.
역설적으로 Unit 테스트는 코드의 안정성과 품질보다는 생산성에 더 긍정적인 영향을 줄 수도 있습니다.

테스트 커버리지가 코드의 수준과 품질을 의미하지 않는다.

테스트 코드를 처음 짜기 시작한 일부 개발자들이 이 테스트 커버리지에 집착하는 경향이 있습니다.
그러나 이 테스트 커버리지가 소프트웨어의 수준과 품질에 비례하는 것은 아닙니다.
개발자의 실력도 마찬가지로 테스트 커버리지를 높게 짜는 개발자가 실력이 더 좋은것도 아닙니다.

물론 커버리지를 높이기 위한 노력이 도움이 될 수는 있겠으나
이 커버리지에 집착하게되면 불필요한 테스트 코드를 짜거나 중복된 테스트를 하게 되는 등 오히려 독이될 수도 있습니다.

너무 많은 테스트코드는 리팩토링을 방해한다

테스트가 많을 수록 좋을 것 같지만 결과적으로 테스트코드 자체도 리팩토링의 영향을 받기 때문에
테스트코드가 많다는 것은 리팩토링 했을 떄 수정되어야 할 코드 역시 많다는 의미가 됩니다.
결과적으로 과도하게 많은 테스트 코드는 오히려 리팩토링을 부담스럽게 만들 수 있습니다.

그럼에도 필요한 이유?

why

결국엔 위 함정들을 보면 자연스럽게 다음 질문이 떠오릅니다.

Unit 테스트를 작성해도 어차피 테스트 해야할거면 짤 필요 없는거 아닌가?


만약 필요가 없었다면 이렇게 장황하게 떡밥을 깔아가면서 포스팅을 작성할 이유가 없었겠죠?
일단 필요한 이유는 크게 나를 위한 이유우리를 위한 이유 두가지로 나눠 설명할 수 있습니다.




먼저 나를 위한 이유를 봅시다.


코드의 안정성

물론 이것이 프로그램의 품질과 직접 연결되지 않을 수는 있지만 검증을 일단 안하는 것 보다는 당연히 낫습니다.
이것은 지금 내가 작성한 기능을 검증하는 것 뿐 아니라
나중에 수정된 코드로 인한 기존 기능의 영향도를 빨리 파악하고 검증할 수 있다는 장점이 있습니다.
또한 검증 하는 과정에서 요구사항 도출과 버그를 미리 발견 할 수 있습니다.


빠른 빌드로 검증과 테스트

Unit 테스트 코드가 아니더라도 개발하는 과정에서 테스트용 코드를 작성하곤 합니다.
그런데 어차피 로직을 테스트할 것이라면 단위테스트로 짜는 것이 빠릅니다.
통합환경에서 프로젝트 전체를 빌드해서 UI로 테스트하는 것과 비교한다면
프레임워크로부터 독립적인 단위테스트만 빌드하여 검증하는 편이 훨씬 빠를 수 밖에 없습니다.
제가 작업하던 환경 기준으로 본다면 안드로이드 앱을 빌드할 때 매 빌드마다 2~5분 정도 걸린다면
단위테스트는 모든 테스트가 아무리 길어도 1분이내에 모든 테스트를 검증할 수 있었습니다.
이를 CI/CD 에 연동하여 개발/배포 프로세스에 녹여내는 것 또한 충분히 가능합니다.


결과적으로 테스트코드를 안짜는 것보다 짜는 것이 개발속도가 더 빠르다고 할 수 있습니다.



리팩토링에 유리

리팩토링 할 때 기존에 작성되어 있는 로직들의 검증이 쉬워지기 때문에 리팩토링에 유리해집니다.
테스트코드를 작성하는 것으로 리팩토링의 시작하기 때문에 테스트코드가 있는 로직이 리팩토링 하기도 부담이 적습니다.
또한 단위테스트를 작성 하는 과정에서 기능별, 함수별로 나누게 되어 이미 리팩토링 하기 쉬운 구조가 됩니다.




지금까지는 나를 위한 이유를 살펴보았다면
이번에는 내가 아닌 우리를 위한 이유를 소개합니다.

코드의 의도 파악

실제 코드를 보는 것 보다 테스트코드를 볼 때 코드의 의도를 파악하기가 훨씬 쉽습니다.
코드를 보고 로직의 흐름과 예외 케이스를 짐작하는 것 보다 기대 결과값을 확인하는 편이 예상 동작 결과와 코드의 의도를 쉽고 빠르게 확인할 수 있습니다.

그래서 PR 리뷰 시에 리뷰어가 코드 의도를 쉽게 파악하거나
프로젝트에 새로 투입된 인원이 코드 파악할 때도 쉬워지고
3개월 후의 내가 코드를 까먹어도 예상 결과를 확인할 수 있다.

코드의 신뢰성

“이거 동작하는 코드 맞죠?”

jjs


물론 코드를 검증하고 PR 리뷰를 요청하겠지만
리뷰어 입장에서는 어떤 케이스에 대해 어떻게 검증했는지 테스트코드가 없다면 확인할 수가 없습니다.
따라서 테스트코드를 남겨서 어떤 케이스에 대해 어떻게 검증했는지를 남기면 리뷰를 받는 입장에서도 코드를 신뢰하고 리뷰할 수 있습니다.



Unit Test Code를 작성하는 Tip

tip

테스트 짜기 쉬운 것 부터 시작해보기

어렵게 생각하지 말고 유틸 함수나 비즈니스 로직부터 테스트코드를 작성해보기 시작해보는 것을 권장드립니다.

IDE와 테스트 프레임워크를 최대한 활용

IDE를 최대한 활용하면 메소드 추출이나 테스트코드 생성 등 테스트코드를 작성하는 부담과 비용을 줄일 수 있습니다.
또한 XUnit 등의 테스트 프레임워크를 잘 활용하여 중복되는 테스트코드를 줄이는 등 테스트코드 자체를 잘 짜는 것도 중요합니다.

테스트 가능한 로직과 불가능 한 로직 분리

I/O나 UI등 외부 프레임워크에 의존하지 않도록 코드를 테스트 가능한 로직과 불가능한 로직을 분리해야 합니다.
이러한 코드의 분리는 클린 아키텍처를 활용하여 도메인 로직을 분리할 수 있습니다.

모든 함수와 기능을 테스트할 필요는 없다.

존재하는 모든 예외케이스에 대해 테스트할 필요는 없습니다.
오히려 필요한 정도로만, 혹은 본인이 안심되는 수준까지만 검증하는 것을 권장합니다.

또한 private 함수는 테스트 할 필요가 없습니다.
만약 필요하다면 그것은 private 함수이면 안된다는 반증일 수도 있습니다.
오히려 너무 많은 테스트 코드를 작성하려하다 보면 중복된 테스트 코드등이 생기거나 결과적으로는 리팩토링에 부담이 될 수 있기 때문입니다.





결론

단위테스트가 있어도 결국 통합테스트가 필요하기 때문에 단위테스트의 필요성을 못느낄 수 있습니다.
그러나 코드의 구조와 생상선과 신뢰성 측면만 보더라도 충분히 단위테스트를 짜는 데 의미가 있습니다.
또한 리팩토링과 코드의 의도파악이란 측면에서 보면 지금 당장보다도 시간이 지날 수록 더 중요해질 수 밖에 없습니다.
혹시 Unit 테스트를 안짜보셨다면 지금부터라도 한번 짜보시면 어떨까요?