업데이트:

저번 포스팅에 이어 이번에는 단위 테스트에 대해 좀 더 다뤄보도록 하겠습니다.

💡제가 아무래도 Spring을 다루는 개발자다 보니 관련 단위 테스트에서 많이 쓰이는

Mockito, JUnit을 기반하여 글을 작성하는 점 참고 부탁드립니다.

단위 테스트


먼저 단위 테스트의 정의에 대해 알아보겠습니다.

다음은 위키피디아에서 말하는 단위 테스트, Unit Test에 대한 내용입니다.

유닛 테스트(unit test) 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 
정확히 작동하는지 검증하는 절차다. 

, 모든 함수와 메소드에 대한 테스트 케이스(Test case) 작성하는 절차를 말한다. 
이를 통해서 언제라도 코드 변경으로 인해 문제가 발생할 경우, 
단시간 내에 이를 파악하고 바로 잡을  있도록 해준다. 이상적으로, 
 테스트 케이스는 서로 분리되어야 한다. 이를 위해 가짜 객체(Mock object) 생성하는 것도 좋은 방법이다. 
유닛 테스트는 (일반적인 테스트와 달리) 개발자(developer) 뿐만 아니라 보다 
 심도있는 테스트를 위해 테스터(tester) 의해 수행되기도 한다.

허허 이것참… 제가 단위 테스트에 대해 말하려고 한 내용을 전부 말해주고 있네요😅

저는 그럼 단위 테스트의 추가할 내용을 말하고, 위키 피디아의 내용을 하나 하나씩 뜯어보도록 하겠습니다.

단위 테스트의 장점

💡다른 블로그 글의 내용과 중복되는 내용도 있겠지만, 되도록 제 경험을 바탕으로 장점을 나열해보겠습니다.

1. 테스트의 시간이 단축된다.

“음? 이게 무슨 말이야??”

“테스트 코드 작성하는 만큼 시간이 걸리는거 아닌가요??”

라고 생각하시는 분들이 계실겁니다.

보통 개발자가 어떤 기능을 개발하고 테스트를 할 때면 Application을 구동하고 해당 기능을 실행해봅니다.

Spring MVC Web 프로젝트를 예로 들면 특정 엔드 포인트 Rest API를 하나 뚫어놓고, 그 API가 호출될 때 실행되는 기능을

Controller > Service > RepositoryLayer를 따라 로직을 개발합니다.

그리고 나서 마지막에 Postman이나 Swagger, Curl등 다양한 툴을 사용하여 API를 호출하죠.

API를 호출했더니~ 기능이 정상적으로 동작합니다~👏

그럼 이제 다음 작업(배포, 또는 추가 기능개발)을 들어가야할까요?

❗절대 아닙니다.

해당 기능에는 여러가지 케이스가 분명 존재할 것입니다.

예를 들어 특정 파라미터의 값에 따라 IllegalArgumentException이 발생할 수도 있고,

if 문이나 switch 문이 있다면 다양한 케이스가 발생할 수 있습니다.

(특히 위와 같은 인수 테스트에선 Exception 발생에 대한 테스트가 누락되는 경우가 많습니다.)

또, 모든 케이스를 테스트한다고 해도, 이런 테스트 케이스를 확인할 문서도 필요할 것입니다.

이런 모든 케이스를 값을 바꿔가면 API를 호출하여 테스트를 검증하고 완료했다고 가정해봅시다.

그런데, 다음날 갑자기 로직을 바꿔야 한답니다.

위에서 예를 든 IllegalArgumentException이 아니라 CustomException을 추가하여 CustomException을 발생시켜야 한다네요.

그럼 다시 Application을 구동하고 Postman을 열어 API를 호출해야 할까요?

이때 드는 시간은 어떻게 될까요?

위와 같은 경우에 시간을 단축하기 위해 단위 테스트가 필요합니다.

만약 IllegalArgumentException이 발생하는 케이스의 단위 테스트 코드가 짜여있다면,

해당 부분만 CustomException하고 테스트 코드를 돌려서 해당 기능을 점검할 수 있습니다.

단 몇 초만에 말이죠.

물론 테스트 코드 작성 시간이 추가되긴 합니다만

제 경험상 테스트 코드를 작성하고 테스트 하는데 드는 시간은 일반적인 인수 테스트보다는 적었습니다.

또한 코드는 끊임 없이 변경될 것이고, 끊임 없이 테스트를 해야 하기 때문에 시간이 지날 수록

단위 테스트는 시간을 절약해줄 것입니다.

2. 깔끔한 코드를 지향할 수 있는 근거가 된다.

단위 테스트 코드를 짜다 보면 이런 경우가 있습니다.

하나의 클래스의 테스트 코드를 작성하기 위해 여러 의존성이 있는 클래스를 주입합니다.

보통 테스트 코드의 생김새는 다음과 같습니다.

@ExtendWith(MockitoExtension.class)
public class OrderServiceTest {

    @Mock
    private StockService stockService;

    @Mock
    private PaymentService paymentService;

    @InjectMock
    private OrderService orderService;

    @Test
    void orderTest(){}

}

위의 예시에서는 StockService, PaymentService 2개만을 의존하고 있지만 다른 서비스 뿐만 아니라

수많은 Repository 등 여러 의존성을 갖고 있는 경우를 심심치 않게 볼 수 있습니다.

그렇다면 슬슬 냄새가 납니다. OrderService에 대한 책임이 너무 많은 것 같다. 뭔가 책임 분리가 필요하다”라고 말이죠.

또, 하나의 메소드에서 수 많은 테스트 케이스가 발생한다.

하나의 메소드는 하나의 일만 하는 것이 베스트이고, 그럼 많아봐야 몇개 안되는 테스트 케이스가 나와야 하는데

이상하게 10개 이상의 테스트 케이스가 나온다.

이것 역시 하나의 메소드가 엄청나게 많은 일을 하고 있을 가능성이 있습니다.

이렇듯 단위 테스트는 코드의 여러 안좋은 냄새를 맡을 수 있는 촉매가 되기도 합니다.

실제로 저는 단위 테스트 코드를 작성하면서 여러 안좋은 코드를 많이 만나보고 이를 클린 코드를 지향하도록 리팩토링 했습니다.

3. 리팩토링의 기반이 된다.

당연한 얘기겠지만 테스트 코드는 모두 기본 목적은 기능의 안정화입니다.

많은 단위 테스트 코드를 작성해서 기능의 안정성을 보장했고, 위에서 말한 안좋은 코드의 냄새를 맡았다면 이제는 리팩토링을 진행해야 겠죠?

하지만 만약 단위 테스트 코드가 없는 상태라면 어떻게 될까요?

리팩토링 이후 처음에 말했던 인수 테스트를 진행하면서 기능 점검을 해야할 것입니다.

생각만 해도 끔찍하죠?? 😱

예전에는 괜히 “잘 돌아가는 코드는 건드리지마라”라는 말이 있었던게 아니었습니다.

그러나 단위 테스트 코드를 작성했다면 기능의 안정성을 보장받았으니

언제든지 코드를 깔끔하고, 유연하면서 확장성있게 리팩토링 할 수 있습니다.

4. 기능을 문서화 할 수있다.

이건 부수적인 장점이라고 생각합니다만 어떤 클래스의 기능 목록을 볼때 테스트 코드의 결과를 통해 한번에 확인할 수 있습니다.

IntelliJ 기준으로 테스트 코드를 구동시켜보면 하단에 다음과 같이 테스트 결과가 출력되게 됩니다.

unit-test-result

이걸 보면 OrderTest에서 어떤 로직들(재고 체크를 하고, 결제 실패 체크를 하는 등의)이 돌아가는지 코드를 뜯어보지 않아도 알 수 있습니다.

마무리


이번 포스팅에선 단위 테스트의 장점에 대해 작성해봤습니다.

이미 테스트 코드를 작성하고 있지만, 어떤 장점이 있는지 궁금하셨던 분들이나

회사에서 테스트 코드를 작성하고 있지 않아 장점을 어필하여 도입하시고 싶으신 분들

이 글을 읽으시는 많은 분들에게 내용이 잘 전달되어서 도움이 되었으면 좋겠습니다.

오늘도 긴 제 글을 읽어주셔서 감사합니다.😄

💡단위 테스트를 좋아하다보니 할 말이 많아 글이 길어지네요. 다음 포스팅에선 단위 테스트 코드 작성법에 대해 포스팅을 해보도록 하겠습니다.

댓글남기기