본문 바로가기
가치관 쌓기/개발 돌아보기

왜 테스트 코드를 작성하는 걸까?

by simplify-len 2022. 10. 29.

 트레바리에서 혼자 3주라는 아주 짫은 시간에 구독 서비스을 만들었다. 낮밤, 주중/주말을 가리지 않고 정해진 기한 내에 개발을 마치고 싶었다. 여유 없이 기한내에 개발을 하다보니, 스스로 끊임없이 일정을 핑계삼아 올바른 길보다는 빠른 길을 찾으려는 타협점을 끊임없이 찾으려 했다.

트레바리의 개발 문화는 테스트 주도 개발 이라는 (사실 거창한건 아니지만) 문화을 CTO님이 최우선을 둔다.  나 또한 CTO 님의 영향 덕택에 테스트 코드을 마치 습관처럼 작성한다. 테스트 코드의 장점은 CTO 님 덕택에 직접 몸소 느끼고 있다. (장점에 대해서 중심으로 이야기하지는 않을 예정이다.)

 구독 서비스을 만들면서 빠른 길을 찾기 위해서 습관처럼 작성하던 테스트 코드을 중간부터는 스킵하고 넘어가기 일쑤였다. 그러다 보니 문득 테스트 코드을 왜 작성해야 하는 거지? 라는 비평적인 물음이 던져졌다. 당연히, 테스트 코드을 왜 작성해야 되는지 머리로는 이해하고 있지만 표현해본 적은 없지 않았나 싶다.

구독 서비스을 만들면서, 일부는 단위 테스트을 먼저 작성하고 프로덕트 코드을 작성하는 식의 테스트 주도 설계 처럼 코딩하기도 하고 일부는 프로덕트 코드을 먼저 작성하고 난 뒤에 단위 테스트 코드을 만들기도 했다.

이 과정에서 느꼈던 생각이나 감정에 대해서 이야기해보려 한다.

 테스트 코드을 작성하는 이유는 여러가지가 있다고 생각한다. 

생각의 저변을 넓히는 전략으로서

프로덕트 코드 먼저 작성한다.

 'src/main/java' 에 속하는 프로덕트 코드을 작성하고 난 뒤에 작성한 나의 코드가 내가 예상한 대로 동작되는지 체크하기 위해 테스트 코드을 작성한다. 이 때 내가 테스트 코드을 통해 알 수 있는 것은 프로덕트 코드가 예상한 대로 동작되는지 확인할 수 있다.

코드 커버리지을 확인하는 것이 아니라면, 대부분 성공을 예상하는 테스트 코드을 작성하게 된다. 프로덕트을 먼저 작성함으로써, 이미 결과물은 나와있다. 나는 테스트 코드을 프로덕트 코드을 보면서 맞쳐서 개발한다.

"1 + 1 = 2" 라는 프로덕트 코드가 있다면, 나는 1+1= 2라는 결과물이 맞는지에 대한 테스트 코드을 작성한다.

테스트 코드을 먼저 작성한다.

 'src/test/java' 에 속하는 테스트 코드을 먼저 작성하고 난 뒤에 내가 예상한 결과값을 나오길 바라면서 프로덕트 코드을 작성한다. 이것은 코드 커러버리와 상관없이 내가 작성한 테스트 코드가 통과하길 기대하며 코드을 작성한다.

하나의 단위 테스트가 통과하고 난 뒤에 발생할 수 있는 경우의 수을 고민하면서 다음 테스트 코드을 작성한다.

"? + ? = 2" 이다 라는 요구사항에 맞는 테스트 코드을 0 + 2 = 2, 1 + 1 = 2 , 2 + 0 = 2 이런 식의 여러 테스트 코드을 작성해본다.

프로덕트 코드을 먼저하든, 테스트 코드을 먼저하든 원하는 결과물을 얻을 수는 있다. 그러나, 테스트 코드을 먼저 작성함으로서 나는 이미 도출된 결과물을 보고 이야기하는 것이 아닌, 입력과 출력을 두고 어떻게 결과물을 만들어 낼 수 있을지를 고민하게 된다. 이것은 테스트 코드가 주는 생각의 저변을 넓히는 전략이라고 생각한다.

관심과 집착의 용도로서 

테스트 코드을 만드는 이유 중에 하나는 내가 작성한 프로덕트 코드에 이번보다 더한 관심과 집착을 부여하기 위한 용도로 만든다.

    /**
     * |----------|-------------|-----------|-------------*|-------------------|-------------|*
     * |-------정기 결제--------라이브세션-----다음 정기 결제일-----라이브세션-------다음다음정기결제일|*
     * |-------(12.15)--------(1.12)------(1.19)-----------(2.9)---------------(2.16)--------|*
     * 2, 4 20:00
     * 3, 4 10:00* *
     */
    @Test
    void xxx7() {
        LocalDateTime appliedAt = LocalDateTime.of(2023, 2, 9, 18, 59);
        PlanedBillingAt planedBillingAt = PlanedBillingAt.create(appliedAt,
                liveSchedule,
                regularBillingSchedule);
        assertThat(planedBillingAt).isEqualTo(
                PlanedBillingAt.of(LocalDateTime.of(2023,1,19,10,0,0,0)));
    }

    /**
     * |----------|-------------|-----------|--------------|*------------------|-------------|*
     * |-------정기 결제--------라이브세션-----다음 정기 결제일-----라이브세션-------다음다음정기결제일|*
     * |-------(12.15)--------(1.12)------(1.19)-----------(2.9)---------------(2.16)--------|*
     * 2, 4 20:00
     * 3, 4 10:00* *
     */
    @Test
    void xxx8() {
        LocalDateTime appliedAt = LocalDateTime.of(2023, 2, 9, 19, 1);
        PlanedBillingAt planedBillingAt = PlanedBillingAt.create(appliedAt,
                liveSchedule,
                regularBillingSchedule);
        assertThat(planedBillingAt).isEqualTo(
                PlanedBillingAt.of(LocalDateTime.of(2023,2,16,10,0,0,0)));
    }

구독 서비스을 만들면서, 결제가 되는 시점은 굉장히 중요하다. 잘못된 결제일에 결제가 된다면 유저는 얼마나 화가 날까? 내가 작성한 프로덕트 도메인 코드을 더 명시적으로 표현하고 싶었다.

 위 테스트 코드는 나의 관심과 집착을 한 층 더 높여줄 도구로 사용되었다. 주석에 표현된 내용은 일정마다 유저가 구독을 신청할 경우에 어떤 결과물이 나와야 하는지 나열하고, 이 상황에 내가 원하는 결과물이 나올 수 있도록 프로덕트 코드을 수정했다.

나의 불안을 감소시켜주기 위해서

 테스트 코드을 작성하지 않고, 프로덕트 코드을 작성하고 있었다.

@Service
@RequiredArgsConstructor
public class SubscriptionOrderTerminator {

	...
    @Transactional
    public OrderJdbcEntity terminate(String orderId, String subscriptionId, String userId, String reason) {

        OrderJdbcEntity entity = queryOrdersPersistencePort.getByUserIdAndSubscriptionIdAndBillingOrderId(userId, subscriptionId, orderId);


        if (!isTodayPaied(entity.getCreatedAt())) // Look At this
            return OrderJdbcEntity.EMPTY;

        refundOrderWebRequestPort.refundByOrderId(orderId, entity.getPrice());

        OrderJdbcEntity refunded = entity.refunded(reason);

        return commandOrderPersistencePort.save(refunded);
    }
}

위 코드는 당일 결제가 아니라면 환불을 하지 않도록 해야 하는 코드이다. 먼저 마음에 여유가 없어서 내가 말하는 도메인과 인프라을 분리해야 하는 포트&어탭터 아키텍쳐에서 지름길을 선택했다. 이로써 내가 지켜야하는 원칙이 무너졌다.

이미 이시점에서 나는 불안감을 느끼고 있었다. 그리고 위 코드을 테스트 하기 위해서 프로젝트 전체을 빌드하고 메뉴얼하게 직접 API 을 요청하면서 결과물을 확인했다. 여기서 2번째 불안감을 느꼈다. 만약 유저가 이렇게 요청하면 어떻게하지? 저렇게 요청하면 어떻게하지?

3주간의 시간동안 개발 후 주어진 시간에 나는 이 불안함 마음을 지우기 위해서 테스트 코드을 작성했다. 그리고 마음의 안도을 얻었다.

내가 테스트 코드을 작성하지 못한 이유는

 테스트 코드을 작성하지 않는 이유는 단 하나이다. 마음에 여유가 없어서, 나 자신과 끊임없이 타협을 했기 때문이라고 생각한다. 그 선택이 올바르지 않았다면 나는 어떤 선택을 했어야 했을까? '다음에 만들기 카드'을 쓸 게 아니라면 어떤 선택을 했어야 했을까? 사실 아직도 잘 모르겠다. 테스트 코드을 작성하는 건 그만큼의 시간과 노력을 들여야 한다. 이건 명백한 팩트다.

테스트 코드을 작성하는 일을 우회할 만큼, 그러니까 테스트 코드을 작성함으로써 얻을 수 있는 이득에 준하는 다른 행위가 무엇이 있을까?

 

댓글