알게된것,복습한것,헷갈렸던것 정리

테스트코드, 단위테스트

ysrec328 2025. 7. 17. 03:49

단위 테스트란?

단위 테스트에 대한 정의를 구분하기 위해서는 고전파와 런던파 관점에서 알아볼 수 있음

 

고전파 ->

모든 사람이 단위 테스트와 테스트 주도 개발에 원론적으로 접근

 

런던파

-> 런던의 프로그래밍 커뮤니티에서 시작

 

단위 테스트란?

단위테스트는

- 작은 코드 조각 (단위)을 검증하고,

- 빠르게 수행하고,

- 격리된 방식으로 처리하는 자동화된 테스트

마지막 격리 문제에 대한 접근법이 런던파와 고전파가 다름

 

 

테스트 코드 작성 예시

비즈니스 로직

-> 고객은 제품을 구매할 수 있다.

-> 재고가 충분하면 구매는 성공하며, 제품의 재고가 1개 줄어든다.

-> 재고가 부족하면 구매는 실패하며, 아무런 액션이 발생하지 않는다.

 

고전파 테스트 코드

public void purchase_success_when_enough_inventory() {
	// given
    Store store = new Store(); // 상점 생성
    store.addInventory("MacBook Pro", 10); // 상점에 맥북 10개 추가
    Customer customer = new Customer();
    
    // when
    boolean result = customer.purchase(store, "MacBook Pro", 3); // 상점에서 맥북 3개 구매
    
    // then
    assertTrue(result); // 구매 성공
    assertEquals(7, store,getInventory("MackBook Pro")); // 남은 재고 7개

 

고전파 테스트 코드

public void purchase_fails_when_not_enough_inventory() {
	// given
    Store store = new Store(); // 상점 생성
    store.addInventory("MacBook Pro", 10); // 상점에 맥북 10개 추가
    Customer customer = new Customer();
    
    // when
    boolean result = customer.purchase(store, "MacBook Pro", 15); // 상점에서 맥북 15개 구매
    
    // then
    assertFalse(result); // 구매 실패 (재고 부족)
    assertEquals(10, store.getInventory("MakBook Pro")); // 남은 재고 그대로 10개

 

고전파 테스트 코드의 경우

Customer과 Store 를 둘 다 별도의 테스트 베드로 교체하지 않고, 그대로 사용함

이 테스트로 인해 Customer 와 Store 모두 테스트가 가능해짐

 

하지만 Customer와 Store 내 버그가 발생하면 해당 단위 테스트는 실패할 수 있음

-> 두 클래스는 서로 격리되어 있지 않음

 

 

위 코드의 주석과 해설

public void purchase_success_when_enough_inventory() {
	// given(준비) : 테스트를 위한 초기 상태 설정
    Store store = new Store();  // Store 클래스의 새 인스턴스를 생성합니다. 
  								// 이 인스턴스가 테스트 대상이 됩니다 
    store.addInventory("MacBook Pro", 10); 
    // 'store' 객체에 "MacBook Pro" 상품 10개를 재고로 추가합니다
    Customer customer = new Customer();
    // 'Customer' 클래스의 새 인스턴스를 생성합니다. 이 객체가 상호작용을 시작합니다
    
    // when (실행) : 테스트 하고자 하는 특정 행동을 실행
    boolean result = customer.purchase(store, "MacBook Pro", 3);
    // 'custoemr' 객체가 'store'에서 "MacBook Pro" 3개를 구매하도록 요청합니다
    // 이 메서드의 반환값 (구매성공/ 실패여부)이 'result' 변수에 저장됩니다.
    
    // then (검증) : 실행 결과가 예상과 일치하는지 확인
    assertTrue(result); 
    // 'result' 변수가 'true'인지 확인합니다. 이는 구매 작업이 성공했음을 의미합니다
    assertEquals(7, store.getInventory("MacBook Pro"));
    // 'store'의 "MacBook Pro" 재고가 7개로 줄었는지 확인합니다
    // 초기 재고 10개에서 3개 구매 후 남은 재고가 7개여야 정상입니다

 

 

 

 

런던파 테스트 코드

Mocking 을 활용

@Mock Store store;
// 1. 'Store' 객체를 모의 Mock 객체로 선언합니다.
// 실제 Store 객체 대신 테스트를 위해 가짜 객체를 만듭니다

public void purchase_success_when_enough_inventory() { 
// 2. 테스트 메서드입니다. 재고가 충분할 때 구매가 성공하는 시나리오를 테스트합니다

	// given (준비 단계)
    given(store.hasEnoughInventory("MacBook Pro")).willReturn(true);
    // 3. 'store' 모의 객체의 'hasEnoughInventory("MacBook Pro") 메서드가 호출되면
    // 'true' 를 반환하도록 설정합니다
    // 이는 'MacBook Pro' 재고가 충분하다고 가정하는 것입니다
    
    Customer customer = new Customer();
    // 4. 'Customer' 객체를 새로 생성합니다
    
    // when (실행 단계)
    boolean result = customer.purchase(store, "MacBook Pro", 3);
    // 5. 'customer' 가 'store'에서 "MackBook Pro" 3개를 구매하는 메서드를 호출하고,
    // 그 결과를 'result' 변수에 저장합니다
    
    // then (검증 단계)
    assertTrue(result)
    // 6. 'result' 변수가 'true'인지 검증합니다. 즉, 구매가 성공했음을 확인합니다
    
    verify(store.removeInventory("MacBook Pro", 3), times(1));
    // 7. 'store' 모의 객체의 'removeInventory("MacBook Pro", 3)' 메서드가 정확히 1번 호출되었는지 검증합니다
    // 이는 구매 성공 시 재고가 올바르게 감소했는지 확인하는 것입니다

 

런던파 테스트 코드

Store 를 Mocking 하여 사용함

테스트 하고자 하는 비즈니스 로직에만 집중하여 테스트 코드를 작성

 

런던파의 접근

런던파에서는 테스트 대상 시스템을 협력자에게서 격리하는 것을 의미함

하나의 클래스가 다른 클래스 또는 여러 클래스에 의존하면 이 모든 의존성을 테스트 대역(test double)로 대체해야 함

외부 영향과 분리해서 테스트 대상 클래스에만 집중할 수 있도록 함

 

 

단위 테스트 현황

대부분의 프로그래머는 단위 테스트를 실천하고 그 중요성을 알고 있기에, 꾸준히 관리가 필요한 프로젝트라면

단위 테스트 적용은 "필수"에 가까움

 

제품 코드와 테스트 코드의 비율은 1:1에서 1:3 정도가 됨

 

논쟁 포인트

"단위 테스트를 작성해야하는가?" -> "좋은 단위 테스트를 작성하는 것은 어떤 의미인가?"

 

단위 테스트 목표

소프트웨어의 지속 가능한 성장을 가능하게 하기 위해 단위 테스트를 작성함

단위 테스르를 작성하다보면 더 나은 코드 설계로 이어질 수 있음

 

좋은 테스트와 좋지 않은 테스트

모든 테스트를 작성할 필요는 없으며, 잘못된 테스트는 오류를 알아내는 데 도움이 되지 않고,

유지 보수가 어려워지며 리소스만 투자하게 되는 경우가 발생할 수 있음

 

 

성공 적인 테스트를 위해서는?

개발주기에 통합되어 있음

-> 코드가 변경될 때마다 테스트 코드를 실행하여 끊임없는 테스트를 하는 것이 좋음

 

코드 베이스에서 가장 중요한 부분만을 대상으로 함

-> 도메인 모델, 비즈니스 로직을 위주로 테스트 코드를 작성

 

최소 유지비로 최대 가치를 끌어냄(가성비)

-> 가치있는 테스트를 식별하기 + 가치있는 테스트를 작성하기

 

 

단위 테스트의 그ㅜ조

Given - When - Then 패턴

 

Given : 준비

When : 실행

Then : 검증

 

 

아래 코드를 검증해보자

- 전달받은 두 파라미터를 더해 해당 값을 반환

 

public class CalculatorTests {
// 1. 'Calculator' 클래스를 테스트하는 클래스임을 나타냅니다.
// 보통 '테스트 대상 클래스명 + Tests' 형식으로 이름을 짓습니다
	public void sum_of_two_numbers() {
    // 2.테스트 메서드입니다. 두 숫자의 합을 계산하는 시나리오를 테스트합니다.
    // 메서드명은 어떤 상황을 테스트하는지 명확하게 표현하는 것이 좋습니다
    
        // given (준비 단계)
        int a = 3; // 3. 첫 번째 입력 값 'a'를 3으로 초기화합니다.
        int b = 5; // 4. 두 번째 입력 값 'b'를 5로 초기화합니다
        Calculator calculator = new Calculator();
        // 5. 테스트할 'Calculator' 객체를 생성합니다. 
        // 이 객체가 테스트의 대상(System Under Test, SUT)이 됩니다

        // when (실행 단계)
        int actual = calculator.sum(a, b);
        // 6. 'calculator' 객체의 'sum' 메서드를 'a'와 'b'를 인자로 호출합니다.
        // 이 메서드가 반환하는 실제 결과값(actual value)을 'actual' 변수에 저장합니다
        // 이 값은 아직 예상하는 값과 동일한지 모릅니다

        // then (검증 단계)
        int expected = 8;
        // 7. 'sum' 메서드 호출에 대한 예상 결과값(expected value)을 8로 정의합니다
        assertEquals(expected, actual);
        // 8. 'assertEquals' 메서드를 사용하여 'expected'(예상값)와 'actual(실제값)이
        // 동일한지 검증합니다.
        // 만약 두 값이 다르면 테스트는 실패하고, 같다면 테스트는 성공합니다
    }
}