단위 테스트란?
단위 테스트에 대한 정의를 구분하기 위해서는 고전파와 런던파 관점에서 알아볼 수 있음
고전파 ->
모든 사람이 단위 테스트와 테스트 주도 개발에 원론적으로 접근
런던파
-> 런던의 프로그래밍 커뮤니티에서 시작
단위 테스트란?
단위테스트는
- 작은 코드 조각 (단위)을 검증하고,
- 빠르게 수행하고,
- 격리된 방식으로 처리하는 자동화된 테스트
마지막 격리 문제에 대한 접근법이 런던파와 고전파가 다름
테스트 코드 작성 예시
비즈니스 로직
-> 고객은 제품을 구매할 수 있다.
-> 재고가 충분하면 구매는 성공하며, 제품의 재고가 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(실제값)이
// 동일한지 검증합니다.
// 만약 두 값이 다르면 테스트는 실패하고, 같다면 테스트는 성공합니다
}
}
'알게된것,복습한것,헷갈렸던것 정리' 카테고리의 다른 글
| LIKE와 =(등호) 차이 (0) | 2025.10.03 |
|---|---|
| 오버로딩(Overloading) 이란? (0) | 2025.09.27 |
| @NoArgsConstructor, @AllArgsConsturctor, @RequiredArgsConstructor 정리 (0) | 2025.09.11 |
| 파라미터와 기본생성자 (4) | 2025.08.01 |
| JPA 정의와 설정 (1) | 2025.07.15 |