[워밍업 클럽] BE 클린코드&테스트 3주차 발자국

강의 요약

테스트란?

  • 테스트를 하는 이유, 필요한 이유

    • 빠른 피드백

    • 자동화

    • 안정감

  • 테스트 코드가 없다면?

    • 변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.

    • 변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야한다.

    • 빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.

  • 테스트 코드가 병목이 된다면

    • 프로덕션 코드의 안정성을 제공하기 힘들다.

    • 테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.

    • 잘못된 검증이 이루어질 가능성이 생긴다.

  • 올바른 테스트 코드란?

    • 자동화 테스트

      • 빠른 시간 안에 버그를 발견

      • 비용을 절약

    • 소프트웨어의 빠른 변화를 지원

    • 팀 차원의 이익

    • 단기적으론 느리지만, 장기적으로는 빠르다.

단위 테스트

  • 작은 코드 단위를 독립적으로 검증하는 테스트

  • 빠름

  • 안정적

JUnit 5

AssertJ / Fluent assertions for java

AssertJ

  • 테스트 코드 작성을 원활하게 돕는 테스트 라이브러리

  • 풍부한 API, 메서드 체이닝 지원

테스트 케이스 세분화하기

  • 테스트 코드 작성 전에 암묵적이거나 아직 드러나지 않은 요구사항이 있는가? 생각해보자

    • 금액이 0 이하인가?

    • 수량이 0 이하인가?

  • 해피 케이스와 예외 케이스

    • 경계값 테스트가 중요하다.

테스트하기 어려운 영역을 분리하기

  • 테스트 할 때마다 변경되는 값이 있다면, 테스트를 정상적으로 할 수 없다.

  • 테스트 하고자 하는 영역을 잘 구분하자

  • 외부로 분리할수록 테스트 가능한 코드는 많아진다.

  • 테스트하기 어려운 영역이란?

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

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

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

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

// BAD
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);
}

// GOOD
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);
}

TDD

  • 테스트 주도 개발 Test Driven Development

  • 프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 개발 방법론

  • RED → GREEN → REFACTOR

    • 실패하는 테스트 작성

    • 테스트가 통과하도록 최소한의 코딩

    • 구현 코드 개선, 테스트 통과 유지하면서 리팩토링

선 기능 구현, 후 테스트 작성 시

  • 테스트 자체의 누락 가능성

  • 특정 테스트 케이스만 검증할 가능성

    • 해피 케이스

  • 잘못된 구현을 다소 늦게 발견할 가능성

선 테스트 작성, 후 기능 구현 시

  • 복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다.

    • 유연하면서 유지보수가 쉬움

  • 쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.

  • 구현에 대한 빠른 피드백을 받을 수 있다.

  • 과감한 리팩토링이 가능해진다.

테스트 코드는 문서

  • 프로덕션 기능을 설명하는 테스트 코드 문서

  • 다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완

  • 어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시킴

    → 모두의 자산으로 공유할 수 있다.

문서의 기능을 하기 위해서…

  • @DisplayName 을 섬세하게

    • 테스트 행위에 대한 결과까지 기술하기

      • BAD : 음료 1개 추가 테스트

      • GOOD : 음료를 1개 추가할 수 있다.

      • BEST : 음료를 1개 추가하면 주문 목록에 담긴다!

    • 도메인 용어를 사용하여 한층 추상화된 내용을 담기

      • BAD : 특정 시간 이전에 주문을 생성하면 실패한다.

      • GOOD : 영업 시작 시간 이전에는 주문을 생성할 수 없다.

    • 테스트의 현상을 중점으로 기술하지 말 것

BDD

  • Behavior Driven Development

  • TDD에서 파생된 개발 방법

  • 함수 단위의 테스트에 집중하기보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중

  • 개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준을 권장

     

Given / When / Then

Given : 시나리오 진행에 필요한 모든 준비 과정 (객체, 값, 상태 등)

When : 시나리오 행동 진행

Then : 시나리오 진행에 대한 결과 명시, 검증

어떤 환경에서 (Given)

어떤 행동을 진행했을 때 (When)

어떤 상태 변화가 일어난다 (Then)

→ DisplayName에 명확하게 작성할 수 있다.

 

미션 회고

읽기 쉬운 코드에서 작성하였던 지뢰찾기를 기반으로 테스트 코드를 작성하였다.

어떤 코드를 테스트 해야할 지, 어떻게 테스트 코드를 짜야할 지 깊이 고민하게 되었다.

 

느낀점

테스트코드에 대해 어느 정도 감이 생기고 있는 것 같다. 하지만 여전히 실무에 적용하기엔 너무 어렵다.

비지니스 로직이 변경되더라도 테스트 코드가 어느정도 내구성을 가졌으면 하는데, 이렇게 작성하는게 매우 어렵다.

또한 Given 절에서 객체를 사전준비하는 과정이 너무 번거롭다.

이를 해결할 수 있는 좋은 방법이 있을 것 같은데..

여전히 테스트 코드는 귀찮고 어렵다.

댓글을 작성해보세요.

채널톡 아이콘