본문 바로가기
Computer Science/Method

Testable Code란?

by 코딩맛 2024. 11. 13.

Testable Code란

쉽게 테스트할 수 있는 코드를 의미한다. 

이는 코드가 독립적이고 예측 가능하게 동작하여 단위 테스트, 통합 테스트 등을 쉽게 수행할 수 있도록 작성된 코드를 가리킨다.

Testable Code는 소프트웨어의 품질을 높이고 유지 보수성을 향상시키기 때문에 소프트웨어 개발에서 매우 중요하다.

이 내용을 이해하기 위해서는 먼저 테스트하기 어려운 코드가 무엇인지 알아보자.

Index
1. 테스트하기 어려운 코드
    1-1. 제어할 수 없는 값에 의존하는 경우
    1-2. 외부에 영향을 주는 경우

2. 테스트하기 좋은 코드로 개선 
    2-1. 제어할 수 없는 값에 의존하는 경우
    2-2. 외부에 영향을 주는 경우

3. 개인 소감

1. 테스트하기 어려운 코드

테스트 작성이 어려운 이유는 구현부 코드가 테스트에 적합하지 않게 설계되었기 때문이다.

테스트에 용이하게 설계된 경우, Mock 없이도 쉽게 테스트할 수 있다.

테스트가 어렵다고 느끼는 경우는 주로 멱등성이 보장되지 않는 순수 함수가 아닐 때 발생한다.

즉, 코드를 여러 번 실행해도 항상 같은 결과가 나오지 않으면 테스트하기 어렵게 느껴진다.

구체적인 예로 두 가지 경우가 있다.

 

1-1. 제어할 수 없는 값에 의존하는 경우

  • 실행 시 마다 반환값이 다른 경우 (ex. Math.random(), new Date())
  • 사용자 입력에 의존하는 경우(ex. readLine)
  • 외부 SDK에 의존하는 경우 (ex. PG사 라이브러리)
class Car {
  ...
  go() {
    if (랜덤값뽑기() > 3) {
      앞으로전진();
    }
  }
}

 

예시 코드에서는 랜덤값을 반환하는 함수 때문에 테스트가 어렵다.

제어할 수 없는 값으로 인해 매번 동일한 결과를 보장하지 않기 때문에 멱등성이 보장되는 순수 함수가 아니다.

 

1-2. 외부에 영향을 주는 경우

  • 출력(ex. console.log)
  • 외부 메시지 발송(ex. 이메일 발송, 메세지 큐)
  • 데이터베이스에 의존하는 경우
  • 외부 API/cookie/localStorage를 사용하는 경우

예시

class Order {
  ...
  async cancel() {
    const cancelOrder = new Order();
    cancelOrder.amount = this.amount;
    cancelOrder.description = this.description;
    
    await DB에저장(cancelOrder);
  }
}

 

예시 코드에서 데이터베이스에 의존하는 함수는 테스트를 어렵게 만든다. 이유는 다음과 같다:

  • DB 스키마가 존재해야 하고 테스트 환경 설정이 필요함
  • 테스트 이후 DB를 초기화해야 함
  • 테스트 속도가 느려짐

이 중 외부 환경 구축 속도 저하가 가장 큰 문제다.

 

2. 테스트하기 좋은 코드로 개선 

테스트하기 좋은 코드로 개선하려면 멱등성이 보장되는 순수 함수로 만들어야 한다.

이를 위해 제어 불가능한 값이나 외부 의존성을 없애거나, 불가능할 경우 적절한 위치로 이동시켜야 한다.

controller - service - repository- domain

 

내부에 테스트하기 어려운 코드가 포함되면 전체적으로 테스트가 어려워진다.

테스트하기 어려운 코드는 바깥쪽의 controller 단에 위치시키는 것이 최적이다.

이를 통해 내부 로직들이 멱등성을 보장할 수 있도록 하고, 테스트 용이성을 높일 수 있다.

 

2-1. 제어할 수 없는 값에 의존하는 경우

// 테스트하기 어려운 코드
class Car {
  /* ... */
  go() {
    if (랜덤값뽑기() > 3) {
      앞으로전진();
    }
  }
}
// 테스트하기 좋은 코드
class Car {
  /* ... */
  go(randomNumber) {
    if (randomNumber > 3) {
      앞으로전진();
    }
  }
}

 

랜덤 값을 생성하는 로직을 go 메서드 내부가 아닌 인자로 전달받는 형태로 수정하면,

랜덤 생성 로직을 클래스 밖으로 분리할 수 있다.

// 이와 같은 형태로 사용할 수 있다.
const car = new Car();
car.go(랜덤값뽑기());

// 이와 같은 형태로 테스트할 수 있다.
car.go(4이상의수)
expect(/* car의 주행거리 */).toBe(/* 1보 전진됨 */)

 

이렇게 수정하면 테스트 시 랜덤 숫자 대신 지정한 숫자를 전달하여 쉽게 테스트할 수 있다.

 

 

2-2. 외부에 영향을 주는 경우

// 테스트하기 어려운 코드
class Order {
  /* ... */
  async cancel() {
    const cancelOrder = new Order();
    cancelOrder.amount = this.amount;
    cancelOrder.description = this.description;
    
    await DB에저장(cancelOrder);
  }
}
// 테스트하기 좋은 코드
class Order {
  /* ... */
  cancel() {
    const cancelOrder = new Order();
    cancelOrder.amount = this.amount;
    cancelOrder.description = this.description;
    
    return cancelOrder;
  }
}

 

DB 의존 로직을 제거하고 객체만 반환하도록 수정하면,

외부 의존(DB) 로직은 도메인 계층이 아닌 서비스 계층에 위치하게 된다.

// 수정 이전의 Service 로직
class OrderService {
  /* ... */
  async cancel(orderId:number) {
    const order = await orderRepository.findById(orderId);
    await order.cancel(); // ⚠️ Order와 OrderService 모두 데이터베이스에 의존
  }
}

// 수정 이후의 Service 로직
class OrderService {
  /* ... */
  async cancel(orderId:number) {
    const order = await orderRepository.findById(orderId);
    const cancelOrder = order.cancel(); // 👍🏻 Order는 데이터베이스에 의존하지 않음
    await DB에저장(cancelOrder);
  }
}

 

수정 전에는 도메인 계층과 Service 계층 모두 데이터베이스에 의존했으나,

수정 후에는 Service 계층에서만 데이터베이스에 의존하게 되어 테스트가 어려운 범위가 축소되었다.

 

async, await가 포함된 로직은 외부에 의존하는 로직이다.

위 로직이 있다면 최대한 바깥쪽으로 빼낼 수 있는지 고민해야 한다.

 

3. 개인 소감

테스트 코드에 대해서도 클린한 코드를 만드는 것에 대한 필요성을 알게 되었다.

테스트하기 용이하게 만들려면 최대한 모듈화시켜서 기능마다 의존하지 않고 독립적으로 실행되도록 하는 것이 중요한 것 같다.

이미 구현부부터 복잡하게 만들어진 코드에 대한 테스트 코드를 작성하기는 쉽지 않겠지만 개발 초기 단계에 기능 위주로 작게 쪼개어 하나씩 테스트 해보면 좋을 것 같다.

그리고 테스트하기 쉽게 만들어진 코드는 모듈화되어 있어 하나의 기능 수정이 다른 기능에 영향을 미치지 않기에

유지 보수에 있어서도 도움이 될 것 같다.

 

 

 

https://devhanyoung.tistory.com/7

 

Testable Code

유닛 테스트를 작성하다 보면, 내 코드가 테스트 불가능한(혹은 어려운) 경우가 있다. 이럴 때면 당혹감과 함께 여러가지 의문이 밀려든다. '왜 내 코드는 테스트하기 어려울까?' '어떻게 수정해

devhanyoung.tistory.com

'Computer Science > Method' 카테고리의 다른 글

레이어드 아키텍처  (0) 2024.12.02
클린 아키텍처  (2) 2024.11.27
TDD (Test-Driven Development)란?  (1) 2024.11.08