개발 일지

테스트 코드란 (테스트 코드를 작성해야 하는 이유) 본문

카테고리 없음

테스트 코드란 (테스트 코드를 작성해야 하는 이유)

junjun_ 2024. 5. 11. 17:11

이번 글에서는 테스트에 대한 인프런 강의를 바탕으로 테스트 코드란 무엇이고 테스트 코드가 왜 중요한지, 더 좋은 테스트 코드를 작성하기 위한 방법은 뭔지 등을 정리해 보겠습니다. 

 

Practical Testing: 실용적인 테스트 가이드 | 박우빈 - 인프런

박우빈 | 이 강의를 통해 실무에서 개발하는 방식 그대로, 깔끔하고 명료한 테스트 코드를 작성할 수 있게 됩니다. 테스트 코드가 왜 필요한지, 좋은 테스트 코드란 무엇인지 궁금하신 모든 분을

www.inflearn.com

 

 

테스트 코드 강의를 듣게 된 배경

  • 친구와 프런트엔드-백엔드 역할을 나눠 간단한 토이 프로젝트를 진행하면서 ec2에 배포를 먼저 해두고 개발을 했었습니다. ( 전에는 먼저 개발 후 나중에 배포를 했었습니다)
  • CI/CD를 구현했음에도 불구하고, 한번 코드를 수정하고 재배포하는 데 시간이 걸려 재 배포 전 자체적으로 확실하게 검증할 필요를 느꼈습니다.
  • 검증 목적으로 선택한 것은 이미 어느 정도 작성할 수 있고 빠른 피드백을 받을 수 있는 "테스트 코드" 
  • 하지만 그동안 작성했던 테스트 코드는 따로 공부한 것이 아닌 그때그때 필요한 부분을 찾아가며 작성한 것이어서,
    • 테스트 코드에 대한 개념이 제대로 잡혀있지 않았고, 특히 Mock에 대한 이해가 부족하다는 것을 깨달았습니다.
  • 이번 기회에 테스트 코드에 대해 기초부터 확실히 공부하고자 인프런에서 강의를 듣게 되었습니다.

 

테스트에 대한 글뿐만 아니라 라이브 코딩을 진행하면서 개발에 도움이 될만한 이야기들도 함께 해주시기 때문에 강의가 알차고 좋았습니다.

 


테스트 코드란

테스트 코드는 소프트웨어 개발에서 작성한 프로그램이 의도한 대로 동작하는지 검증하기 위해 작성되는 코드입니다. 이는 특정 기능이나 모듈이 기대하는 대로 작동하는지 확인하는 데 사용됩니다. 테스트 코드는 개발자가 작성한 프로덕션 코드와 별도로 작성되며, 다양한 시나리오를 테스트하여 코드의 품질과 신뢰성을 높이는 역할을 합니다.

 

 

테스트 코드가 중요한 이유

그렇다면 이러한 테스트 코드가 과연 "왜" 필요할까요? 우리는 왜 테스트 코드에 대하여 공부하고 테스트 코드를 사용해야 할까요. 먼저 테스트 코드를 작성하지 않으면 수동으로 테스트를 진행해야 하는데 수동으로 테스트를 진행하면 다음과 같은 어려움이 있습니다.

  1. 개발자는 사람이고, 사람은 항상 실수를 유발할 수 있는 가능성을 가지고 있습니다.
  2. 하나하나 수동으로 테스트를 하다 보면 소프트웨어가 커지는 속도를 따라갈 수가 없게 되고 결국 테스트로 커버할 수 없는 영역이 발생하게 됩니다.
  3. 피드백이 늦어지게 되고 그러면 개발 사이클이 느리게 돌아가게 됩니다.
  4. 유지보수가 어려워지고 결과적으로 소프트웨어의 신뢰성이 떨어지게 되며 신뢰성이 없는 소프트웨어는 사용자가 사용할 수 없습니다.

테스트 코드를 활용하면 이런 어려움들을 해결할 수 있습니다.


우리가 테스트 코드로 얻고자 하는 것

빠른 피드백:

  • 테스트 코드는 개발자에게 코드 변경 사항이 의도한 대로 작동하는지 즉시 피드백을 제공합니다. 이를 통해 버그를 빠르게 발견하고 수정할 수 있습니다.

자동화:

  • 테스트 코드를 통해 반복적인 검증 작업을 자동화할 수 있습니다. 이는 수작업 검증에 비해 시간과 노력을 절약하고, 인간의 실수를 줄입니다.

안정성:

  • 테스트 코드는 소프트웨어의 안정성을 높이는 데 기여합니다. 다양한 테스트(단위 테스트, 통합 테스트, 시스템 테스트 등)를 통해 코드의 다양한 측면을 검증함으로써, 예상치 못한 버그를 줄이고, 제품의 신뢰성을 높일 수 있습니다.


이러한 이유로 테스트를 작성하는 것은 귀찮지만 꼭 필요합니다.

"가까이 보면 느리지만 멀리 보면 가장 빠릅니다."

 

내가 지금 시간을 30분, 1시간 더 투자해서 테스트를 작성하는 게 나중에 미래의 수많은 시간을 아낄 수 있을 것입니다.

 

좋은 테스트 코드를 작성하는 방법

테스트란 문서다

  •  프로적션 기능을 설명하는 테스트 코드 문서
  •  다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완
  •  어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격 시켜서 문서와 가능 
"우리는 항상 팀으로 일한다" 

 

그렇다면 어떻게 더 좋은 테스트 코드, 더 좋은 문서를 남길 수 있을까요.

 

1. 테스트케이스 세분화 하기 

해피케이스와 예외케이스에 대한 경곗값 테스트는 필수입니다.

  • 경곗값: 범위(이상, 이하, 초과, 미만), 구간, 날짜 등
  • 해피케이스: 요구사항을 만족시키는 경우
  • 예외케이스: 범위를 벗어나는 조건에 대해 올바르게 에러를 처리하거나, 예외를 발생시키는지 확인하는 테스
요구사항: 음료는  1잔 이상 주문할 수 있다.
  • 해피케이스: 요구사항을 만족시키는 테스트 작성 -> 음료를 1~ 2잔 주문하는 테스트 작성
  • 예외 케이스: 0잔은 주문할 수 없음 ->  0잔 주문 할 때 예외가 잘 발생하는지에 대한 테스트 작성

또한 테스트 케이스를 세분화하는 과정에서, 우리는 항상 "암묵적이거나 아직 드러나지 않은 요구 사항이 있는가"에 대한 질문을 던지고 고민해야 합니다. 위 요구사항에의 암묵적인 요구사항은 "주문은 가게 운영시간에만 할 수 있다"일 것입니다. 

 

암묵적인 요구사항: 가게 운영 시간(10:00~22:00) 외에는 주문을 생성할 수 없다

  • 해피케이스: 요구사항을 만족시키는 테스트 작성
  • 예외 케이스: 

이 요구사항에 대한 테스트를 작성하기 위해서는 생각할 점이 하나 있습니다. 아래는 주문객체를 생성하는 코드입니다.

    public Order createOrder() {
        LocalDateTime currentDateTime = LocalDateTime.now();
        LocalTime currentTime = currentDateTime.toLocalTime();
        if(currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
            throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요");
        }
        return new Order(currentDateTime, beverages);
    }
  • 위 코드는 메서드 내부에서 `LocalDateTime.now()`를 사용하여 현재 시간을 가져와서  Order 객체를 생성합니다.
  • 현재 시간을 가져오기 때문에 요구사항인 "가게 운영 시간(10:00~22:00) 외에는 주문을 생성할 수 없다"는 조건을 테스트하려면 실제 영업시간에만 테스트를 진행해야 하는 제약이 발생합니다.
  • 테스트의 제약을 없애려면 어떻게 해야 할까요?

 

2. 테스트하기 어려운 영역을 구분하고 분리하기

위와 같은 문제점을 해결하기 위해서는 다음과 같이  테스트를 진행하기 어려운 영역들을 분리해야 합니다.

테스트를 작성할 때는 완벽하게 제어할 수 있는 것이 중요합니다. 제어할 수 없는 것은 상위레이어로 분리하는 것

제어할 수 없는 값들은 

관측할 때마다 다른 값에 의존하는 코드

  • 현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등

외부 세계에 영향을 주는 코드

  • 표준 출력, 메시지 발송, 데이터베이스에 기록하기 등

현재 시간은 관측할 때마다 변하는 값이므로 제어할 수 없는 부분입니다. 따라서 상위 레이어에서 시간을 받아오도록 리팩토링해 보겠습니다.

    public Order createOrder(LocalDateTime currentDateTime) {
        LocalTime currentTime = currentDateTime.toLocalTime();
        if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) {
            throw new IllegalArgumentException("주문 시간이 아닙니다. 관리자에게 문의하세요.");
        }

        return new Order(currentDateTime, beverages);
    }
  • 이렇게 currentDateTime을 외부에서 받아오도록 리펙토링을 하면 테스트 환경에서는 예측 가능한 고정된 값을 사용하여 테스트를 수행할 수 있고, 이는 테스트의 신뢰성과 일관성을 높입니다.
  // 해피케이스
  @Test
    void createOrderWithCurrentDateTime() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        cafeKiosk.add(americano);

        LocalDateTime currentDateTime = LocalDateTime.of(2024, 3, 24, 10, 0);
        Order order = cafeKiosk.createOrder(currentDateTime);

        assertThat(order.getBeverageList()).hasSize(1);
        assertThat(order.getBeverageList().get(0).getName()).isEqualTo("아메리카노");
    }

// 예외케이스
    @Test
    void createOrderOutsideOpenTime() {
        CafeKiosk cafeKiosk = new CafeKiosk();
        Americano americano = new Americano();
        cafeKiosk.add(americano);

        assertThatThrownBy(() -> cafeKiosk.createOrder(LocalDateTime.of(2024, 3, 24, 9, 59)))
                .isInstanceOf(IllegalArgumentException.class)
                .hasMessage("주문 시간이 아닙니다. 관리자에게 문의하세요.");
    }
  • 고정적인 값으로 해피케이스와 예외케이스에 대한 테스트 코드를 작성할 수 있습니다.

 


3. DisplayName을 섬세하게 ( @DisplayName: Juite5부터 추가된 어노테이션)

 

ex) 음료를 1개 추가 테스트 ->  음료를 1개 추가할 수 있다. -> 음료를 1개 추가하면 주문목록에 담긴다.

  • 명사의 나열보다 문장으로 작성하기
  • ~테스트 지양하기
  • 테스트 행위에 대한 결과까지 기술하기

ex) 특정 시간 이전에 주문을 생성하면 실패한다 -> 영업 시작 시간 이전에는 주문을 생성할 수 없다.

  • 도메인 용어를 사용하여 한층 추상화된 내용을 담기 
  • 테스트의 현상을 중점으로 기술하지 말 것( ~실패한다, ~성공한다)

 

 

정리

테스트 코드란 

  •  소프트웨어 개발에서 작성한 프로그램이 의도한 대로 동작하는지 검증하기 위해 작성되는 코드

테스트 코드를 작성해야 하는 이유 (테스트 코드의 장점)

  • 빠른 피드백
  • 자동화
  • 안정성
  • 문서화

좋은 테스트 코드를 작성하기 위해서는

  • 테스트 케이스를 세분화시키기
  • 항상 "암묵적이거나 아직 드러나지 않은 요구 사항이 있는가"에 대한 질문을 던지고 고민하기
  • 완벽하게 제어하기 위해 테스트하기 어려운 부분을 분리하기
  • DisplayName을 섬세하게 작성하기

 

여기까지 강의 들으면서 좋았던 내용들을 전반적으로 정리를 해보았습니다.

여기 정리한 부분 말고도 좋았던 내용이나 정리해 두고 싶은 내용들은 이후에 따로 정리해 보려고 합니다

 

더 공부해 볼 것들

  • 핵사고날
  • @Transaction