6주차 Unit 1.5 — 픽스처(Fixture)와 @BeforeEach

Psj·2026년 5월 28일

F-lab

목록 보기
186/240

Unit 1.5 — 픽스처(Fixture)와 @BeforeEach

F-LAB JAVA · 6주차 · Phase 1 · JUnit 테스트
🏆 Phase 1 완주 — 일관된 테스트의 완성


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • 픽스처(Fixture) 의 정의는?
  • @BeforeEach 로 픽스처 생성 하는 방식은?
  • 픽스처를 인스턴스 변수로 두는 이유는?
  • 테스트 결과의 일관성 이란?
  • DB 잔여 데이터로 인한 실패 는?
  • 시작 상태 보장 (deleteAll) 은?
  • 통합 테스트 DB 격리 (@Transactional, @Sql) 는?
  • "테스트는 외부 환경에 의존하면 안 된다" 원칙은?
  • Phase 1 전체 의 종합은?

🎯 핵심 한 문장

픽스처(Fixture) 는 여러 테스트가 공통으로 쓰는 준비물(정보·오브젝트)로 @BeforeEach 에서 매 테스트 전에 새로 생성하며, 테스트는 코드 변경이 없으면 항상 같은 결과를 내야 하므로 DB 잔여 데이터로 인한 실패를 막기 위해 시작 상태를 보장해야 한다.
픽스처 는 테스트 수행에 필요한 정보·오브젝트로, 여러 테스트가 공통으로 쓰는 준비물이다.
픽스처는 인스턴스 변수로 선언하고 @BeforeEach 에서 매 테스트 전에 새로 생성하여, 중복을 제거하고 각 테스트가 깨끗한 픽스처로 시작하게 한다.
좋은 테스트는 코드 변경이 없으면 항상 같은 결과 를 내야 하는데, DB 에 이전 테스트의 잔여 데이터가 남아 실패한다면 그것은 잘못된 테스트다.
따라서 dao.deleteAll() 같은 코드로 시작 상태를 보장 하거나, 통합 테스트에서는 @Transactional (테스트 후 롤백) · @Sql (초기 데이터) 로 DB 를 격리하여 "테스트는 외부 환경에 의존하면 안 된다" 는 원칙을 지킨다.

비유 — 실험실 표준 세팅

픽스처 = 실험 표준 세팅:

픽스처 (준비물):
  - 비커, 시약, 측정기
  - 여러 실험 공통
  - 실험 전 준비

@BeforeEach (매 실험 전):
  - 비커 세척
  - 시약 새로 준비
  - 깨끗한 시작

일관성:
  - 같은 조건 = 같은 결과
  - 비커에 이전 잔여물 → 오염 (실패)

시작 상태 보장:
  - 비커 세척 (deleteAll)
  - 깨끗한 시작

통합 테스트 (실제 DB):
  - @Transactional: 실험 후 원상복구 (롤백)
  - @Sql: 초기 시약 세팅

외부 의존 X:
  - 옆 실험실 상태 무관
  - 독립 실험

→ 픽스처 = 공통 준비물 (@BeforeEach 생성), 시작 상태 보장 (일관성, 외부 의존 X).


🧭 9개 섹션 로드맵

1. 픽스처의 정의
2. @BeforeEach로 생성
3. 인스턴스 변수로
4. 테스트 결과의 일관성
5. DB 잔여 데이터 실패
6. 시작 상태 보장
7. 통합 테스트 격리
8. Phase 1 완주 정리
9. 면접 + 자기 점검

1️⃣ 픽스처의 정의

1.1 픽스처

픽스처 (Fixture):

  테스트 수행에 필요한
  정보·오브젝트.

  - 공통 준비물
  - 여러 테스트가 사용

1.2 예시

픽스처 예시:

  - 테스트 대상 객체 (dao)
  - 테스트 데이터 (sample)
  - Mock 객체
  - 설정 값

1.3 공통 준비물

공통 준비물:

  여러 테스트:
    - 같은 dao 필요
    - 같은 샘플 필요

  → 픽스처로 공유 (생성은 매번)

1.4 ILIC 의 맥락

class ShipmentDaoTest {
    // 픽스처 (공통 준비물)
    private ShipmentDao dao;             // 대상 객체
    private Shipment sampleShipment;     // 테스트 데이터
    
    @BeforeEach
    void setUp() {
        dao = new ShipmentDao();
        sampleShipment = new Shipment(1L, "BL001", BigDecimal.TEN);
    }
    
    @Test
    void test_add() {
        dao.add(sampleShipment);   // 픽스처 사용
    }
    
    @Test
    void test_get() {
        dao.add(sampleShipment);   // 픽스처 사용 (새것)
        assertThat(dao.get(1L), is(notNullValue()));
    }
    // dao, sampleShipment = 픽스처
}
class ShipmentDao {
    void add(Shipment s) {}
    Shipment get(Long id) { return null; }
}
record Shipment(Long id, String blNo, java.math.BigDecimal weight) {}

1.5 자기 점검 답변

픽스처(Fixture)의 정의는?

:
1. 픽스처:

  • 테스트 준비물
  • 정보·오브젝트
  1. 예시:

    • dao, 샘플, Mock
  2. 공통:

    • 여러 테스트 사용
  3. 생성:

    • 매번 (@BeforeEach)

2️⃣ @BeforeEach로 생성

2.1 매 테스트 전 생성

// @BeforeEach 로 픽스처 생성
class DaoTest {
    private ShipmentDao dao;   // 픽스처
    
    @BeforeEach
    void setUp() {
        dao = new ShipmentDao();   // 매 테스트 전 새로
    }
}

2.2 중복 제거

중복 제거:

  픽스처 생성:
    - @BeforeEach 한 곳
    - 모든 테스트 공통

  → 각 테스트 중복 X

2.3 깨끗한 픽스처

깨끗한 픽스처:

  매 테스트 전:
    - 새 픽스처
    - 이전 영향 X

→ 깨끗한 시작

2.4 ILIC 의 맥락

class FreightServiceTest {
    // 픽스처
    private FreightService service;
    private ShipmentDao mockDao;
    private FreightCalculator mockCalc;
    
    @BeforeEach
    void setUp() {
        // 매 테스트 전 픽스처 생성 (중복 제거)
        mockDao = mock(ShipmentDao.class);
        mockCalc = mock(FreightCalculator.class);
        service = new FreightService(mockDao, mockCalc);
    }
    
    @Test
    void test_one() {
        // 픽스처 사용 (깨끗)
        service.process(new Shipment(1L));
    }
    
    @Test
    void test_two() {
        // 새 픽스처 (test_one 영향 X)
        service.process(new Shipment(2L));
    }
    // setUp 한 곳, 모든 테스트 공통
}
class FreightService {
    FreightService(ShipmentDao d, FreightCalculator c) {}
    void process(Shipment s) {}
}
class ShipmentDao {}
class FreightCalculator {}
record Shipment(Long id) {}

2.5 자기 점검 답변

@BeforeEach로 픽스처 생성하는 방식은?

:
1. 매 전 생성:

  • @BeforeEach
  1. 중복 제거:

    • 한 곳
  2. 깨끗한 픽스처:

    • 매번 새로
  3. 공통:

    • 모든 테스트

3️⃣ 인스턴스 변수로

3.1 인스턴스 변수

픽스처 = 인스턴스 변수:

  private 필드로:
    - @BeforeEach 에서 초기화
    - @Test 에서 사용

→ 테스트 간 공유 (선언)

3.2 왜 인스턴스 변수

왜 인스턴스 변수:

  - @BeforeEach 에서 할당
  - @Test 에서 접근
  - 여러 메서드 공유 (선언)

→ 지역 변수면 공유 불가

3.3 부담 없음 (Unit 1.4)

부담 없음:

  인스턴스 변수 픽스처:
    - 매번 새 인스턴스
    - 초기화 보장

→ Unit 1.4 연결

3.4 ILIC 의 맥락

class ShipmentServiceTest {
    // 픽스처 = 인스턴스 변수
    private ShipmentService service;     // 인스턴스 변수
    private ShipmentDao dao;             // 인스턴스 변수
    
    @BeforeEach
    void setUp() {
        dao = mock(ShipmentDao.class);   // 할당
        service = new ShipmentService(dao);
    }
    
    @Test
    void test1() {
        service.process(...);   // 접근
    }
    
    @Test
    void test2() {
        verify(dao).add(...);   // 접근 (같은 픽스처 선언)
    }
    // service, dao 를 여러 테스트에서 접근
    // → 인스턴스 변수 (지역이면 불가)
}
class ShipmentService {
    ShipmentService(ShipmentDao d) {}
    void process(Object o) {}
}
class ShipmentDao { void add(Object o) {} }

3.5 자기 점검 답변

픽스처를 인스턴스 변수로 두는 이유는?

:
1. 인스턴스 변수:

  • private 필드
  1. :

    • @BeforeEach 할당
    • @Test 접근
  2. 공유:

    • 여러 메서드
  3. 부담 없음:

    • 매번 초기화

4️⃣ 테스트 결과의 일관성

4.1 일관성

테스트 결과 일관성:

  코드 변경 없으면:
    - 항상 같은 결과
    - 통과는 항상 통과
    - 실패는 항상 실패

→ 신뢰

4.2 왜 중요

왜 중요:

  일관성 없으면:
    - 가끔 통과/실패 (flaky)
    - 신뢰 X
    - 무시하게 됨

→ 테스트 무의미

4.3 일관성 깨는 요인

일관성 깨는 요인:

  - DB 잔여 데이터
  - 외부 시스템 의존
  - 시간/날짜 의존
  - 랜덤 값
  - 테스트 순서

4.4 ILIC 의 맥락

// 일관성 있는 테스트
class ConsistentTest {
    private ShipmentDao dao;
    
    @BeforeEach
    void setUp() {
        dao = new ShipmentDao();
        dao.deleteAll();   // 일관성 (깨끗한 시작)
    }
    
    @Test
    void add_one_shipment() {
        dao.add(new Shipment(1L));
        assertThat(dao.count(), is(1));   // 항상 1
        // 몇 번 돌려도 항상 통과
    }
}

// ❌ 일관성 없는 테스트
class FlakyTest {
    @Test
    void uses_current_time() {
        // 시간 의존 (일관성 X)
        LocalTime now = LocalTime.now();
        assertThat(now.getHour(), is(lessThan(12)));
        // 오후엔 실패 (flaky)
    }
}
class ShipmentDao {
    void add(Shipment s) {}
    void deleteAll() {}
    int count() { return 0; }
}
record Shipment(Long id) {}

4.5 자기 점검 답변

테스트 결과의 일관성이란?

:
1. 일관성:

  • 변경 없으면 같은 결과
  1. 왜 중요:

    • flaky 면 신뢰 X
  2. 깨는 요인:

    • DB 잔여, 시간, 랜덤
  3. 신뢰:

    • 항상 같은 결과

5️⃣ DB 잔여 데이터 실패

5.1 잔여 데이터 문제

DB 잔여 데이터:

  이전 테스트 데이터:
    - DB 에 남음
    - 다음 테스트 영향
    - 잘못된 실패

5.2 예시

// 잔여 데이터 문제
@Test
void test_count() {
    dao.add(new Shipment(1L));
    assertThat(dao.count(), is(1));
    // 첫 실행: 통과
    // 재실행: count = 2 (이전 데이터) → 실패!
}
// 잔여 데이터로 실패

5.3 잘못된 테스트

잘못된 테스트:

  잔여 데이터로 실패:
    - 코드는 정상
    - 테스트가 잘못
    - 시작 상태 미보장

→ 테스트 설계 문제

5.4 근본 원인

근본 원인:

  외부 상태 (DB):
    - 인스턴스처럼 자동 격리 X
    - 영속적
    - 명시적 초기화 필요

5.5 ILIC 의 맥락

// 잔여 데이터 문제와 해결
class ShipmentDaoTest {
    private ShipmentDao dao;
    
    // ❌ 초기화 없으면
    @Test
    void without_cleanup() {
        dao.add(new Shipment(1L));
        assertThat(dao.count(), is(1));
        // 재실행 시 누적 → 실패
    }
    
    // ✓ 초기화 있으면 (다음 섹션)
    @BeforeEach
    void setUp() {
        dao = new ShipmentDao();
        dao.deleteAll();   // 잔여 제거
    }
    
    @Test
    void with_cleanup() {
        dao.add(new Shipment(1L));
        assertThat(dao.count(), is(1));   // 항상 1
    }
}
class ShipmentDao {
    void add(Shipment s) {}
    void deleteAll() {}
    int count() { return 0; }
}
record Shipment(Long id) {}

5.6 자기 점검 답변

DB 잔여 데이터로 인한 실패는?

:
1. 잔여 데이터:

  • 이전 테스트 데이터
  1. 예시:

    • count 누적 → 실패
  2. 잘못된 테스트:

    • 코드 정상, 테스트 문제
  3. 원인:

    • 외부 상태 미초기화

6️⃣ 시작 상태 보장

6.1 시작 상태 보장

시작 상태 보장:

  테스트 시작 시:
    - 알려진 상태
    - 깨끗한 시작

  dao.deleteAll() 등

6.2 deleteAll

// 시작 상태 보장 (deleteAll)
@BeforeEach
void setUp() {
    dao = new ShipmentDao();
    dao.deleteAll();   // 모든 데이터 삭제 (깨끗)
}
// 매 테스트가 빈 DB 에서 시작

6.3 알려진 상태

알려진 상태:

  방법:
    1. deleteAll (빈 상태)
    2. 초기 데이터 삽입
    3. 알려진 시작점

→ 예측 가능

6.4 일관성 확보

일관성 확보:

  시작 상태 보장:
    - 매번 같은 시작
    - 같은 결과
    - 일관성

→ 신뢰 회복

6.5 ILIC 의 맥락

class ShipmentDaoStateTest {
    private ShipmentDao dao;
    
    @BeforeEach
    void setUp() {
        dao = new ShipmentDao();
        // 시작 상태 보장
        dao.deleteAll();              // 1. 깨끗하게
        dao.add(new Shipment(1L));    // 2. 알려진 초기 데이터
        dao.add(new Shipment(2L));
        // → 항상 2개로 시작 (알려진 상태)
    }
    
    @Test
    void initial_count_is_two() {
        assertThat(dao.count(), is(2));   // 항상 2
    }
    
    @Test
    void add_makes_three() {
        dao.add(new Shipment(3L));
        assertThat(dao.count(), is(3));   // 2 + 1
        // 알려진 시작 (2) 기반
    }
    // 매번 같은 시작 → 일관성
}
class ShipmentDao {
    void add(Shipment s) {}
    void deleteAll() {}
    int count() { return 0; }
}
record Shipment(Long id) {}

6.6 자기 점검 답변

시작 상태 보장 (deleteAll) 은?

:
1. 시작 상태 보장:

  • 알려진 상태
  1. deleteAll:

    • 깨끗한 시작
  2. 알려진 상태:

    • 예측 가능
  3. 일관성:

    • 매번 같은 시작

7️⃣ 통합 테스트 격리

7.1 통합 테스트 문제

통합 테스트 문제:

  실제 DB 사용:
    - 데이터 영속
    - 테스트 간 영향
    - 격리 어려움

7.2 @Transactional

// @Transactional (테스트 후 롤백)
@SpringBootTest
@Transactional   // 각 테스트 후 자동 롤백
class ShipmentDaoIntegrationTest {
    @Autowired
    private ShipmentDao dao;
    
    @Test
    void test_add() {
        dao.add(new Shipment(1L));
        // 테스트 후 롤백 → DB 원상복구
    }
}
// 변경이 커밋 안 됨 (격리)

7.3 @Sql

// @Sql (초기 데이터)
@Sql("/test-data.sql")   // 테스트 전 실행
@Test
void test_with_data() {
    // test-data.sql 의 초기 데이터로 시작
    assertThat(dao.count(), is(greaterThan(0)));
}
// 알려진 초기 상태

7.4 격리 방법 비교

격리 방법:

@Transactional:
  - 테스트 후 롤백
  - 빠름, 편함
  - 커밋 동작 테스트 X

@Sql:
  - 초기 데이터 세팅
  - 명시적

deleteAll (@BeforeEach):
  - 수동 초기화
  - 단순

7.5 ILIC 의 맥락

// 통합 테스트 격리 (ILIC)
@SpringBootTest
@Transactional   // 각 테스트 후 롤백 (격리)
class ShipmentDaoIntegrationTest {
    @Autowired
    private ShipmentDao dao;
    
    @Test
    @Sql("/init-shipments.sql")   // 초기 데이터
    void find_by_status() {
        List<Shipment> booked = dao.findByStatus("BOOKED");
        assertThat(booked, hasSize(2));   // 알려진 초기 데이터
        // 테스트 후 자동 롤백 (다른 테스트 영향 X)
    }
    
    @Test
    void add_and_verify() {
        dao.add(new Shipment(99L, "BL099", "BOOKED"));
        assertThat(dao.get(99L), is(notNullValue()));
        // 테스트 후 롤백 → DB 깨끗
    }
    // @Transactional 로 격리 + @Sql 로 초기 상태
}
class ShipmentDao {
    void add(Shipment s) {}
    Shipment get(Long id) { return null; }
    java.util.List<Shipment> findByStatus(String s) { return null; }
}
record Shipment(Long id, String blNo, String status) {}

7.6 자기 점검 답변

통합 테스트 DB 격리 (@Transactional, @Sql) 는?

:
1. @Transactional:

  • 테스트 후 롤백
  1. @Sql:

    • 초기 데이터
  2. 비교:

    • 롤백/세팅/수동
  3. 목적:

    • 외부 의존 X

8️⃣ Phase 1 완주 정리

8.1 Phase 1 학습 종합

Phase 1 — JUnit 테스트

Unit 1.1 — 단위 테스트 필요성
  - 코드가 코드 검증
  - FIRST

Unit 1.2 — assertThat과 매처
  - Hamcrest, is()

Unit 1.3 — JUnit 실행 방식
  - 7단계, 새 인스턴스

Unit 1.4 — 매번 새 오브젝트
  - 독립 실행, @BeforeAll

Unit 1.5 — 픽스처와 @BeforeEach
  - 공통 준비물, 일관성

8.2 핵심 메시지

Phase 1 핵심 메시지:

  "테스트는 코드가 코드를 자동 검증하며,
   각 테스트는 독립적·일관적이어야 한다.
   픽스처는 @BeforeEach 로 매번 준비하고,
   외부 상태는 명시적으로 격리한다."

8.3 "외부 환경 의존 X" 원칙

"테스트는 외부 환경에 의존하면 안 된다":

  - 다른 테스트 (격리)
  - DB 잔여 데이터 (초기화)
  - 시간/랜덤 (제어)
  - 외부 시스템 (Mock)

→ 일관성·독립성

8.4 다음 Phase 예고

Phase 1 → Phase 2:
  - 테스트 → 웹 인프라

Phase 2 — 웹 인프라 기초:
  - 웹서버 vs WAS
  - 서블릿/JSP
  - SSR vs CSR
  - JAR vs WAR

8.5 자기 점검 답변

Phase 1의 종합은?

:
1. 5 Unit:

  • 필요성/매처/실행/새인스턴스/픽스처
  1. 메시지:

    • 독립·일관 테스트
  2. 원칙:

    • 외부 의존 X
  3. 다음:

    • 웹 인프라

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
픽스처?공통 준비물
@BeforeEach 생성?매 전, 중복 제거
인스턴스 변수?여러 메서드 공유
일관성?같은 결과
잔여 데이터?잘못된 실패
시작 상태?deleteAll
@Transactional?롤백
@Sql?초기 데이터
외부 의존?격리
flaky?일관성 깨짐

9.2 자기 점검 체크리스트

픽스처

  • 정의

@BeforeEach

  • 생성

인스턴스 변수

  • 공유

일관성

  • 같은 결과

잔여 데이터

  • 실패

시작 상태

  • deleteAll

통합 테스트

  • @Transactional/@Sql

9.3 추가 심화 질문

Q1: @Transactional 테스트 주의점?

답:

  • 롤백되어 실제 커밋 안 봄
  • 커밋 동작 테스트 시 부적합
  • @Commit 으로 강제 가능
  • 프록시 동작 차이

Q2: 픽스처 빌더 패턴?

답:

  • 테스트 데이터 생성 빌더
  • ShipmentFixture.builder()...
  • 가독성, 재사용
  • Object Mother 패턴

Q3: @DirtiesContext?

답:

  • 컨텍스트 오염 표시
  • 다음 테스트 재생성
  • 비싸지만 격리
  • 신중히

Q4: Testcontainers 격리?

답:

  • 실제 DB 컨테이너
  • 테스트별/클래스별
  • 현실적 + 격리
  • 느림 (트레이드오프)

Q5: given-when-then?

답:

  • 테스트 구조화
  • given (준비/픽스처)
  • when (실행)
  • then (검증)
  • 가독성

🎯 핵심 요약 — 3줄 정리

1. 픽스처와 @BeforeEach

  • 픽스처 = 여러 테스트 공통 준비물 (정보·오브젝트)
  • 인스턴스 변수로 두고 @BeforeEach 에서 매번 생성

2. 테스트 일관성

  • 코드 변경 없으면 항상 같은 결과
  • DB 잔여 데이터로 실패하면 잘못된 테스트 (시작 상태 보장)

3. 외부 환경 격리

  • deleteAll (시작 상태), @Transactional (롤백), @Sql (초기 데이터)
  • "테스트는 외부 환경에 의존하면 안 된다"

🏆 Phase 1 완주 — JUnit 테스트

🧪 Phase 1 — JUnit 테스트
  ✅ Unit 1.1 단위 테스트의 필요성
  ✅ Unit 1.2 assertThat과 매처
  ✅ Unit 1.3 JUnit 실행 방식
  ✅ Unit 1.4 매번 새 오브젝트
  ✅ Unit 1.5 픽스처와 @BeforeEach ← 여기, Phase 1 완주

→ 테스트 필요성 (FIRST)
→ 단언 (assertThat/매처)
→ 실행 방식 (새 인스턴스)
→ 독립 실행
→ 픽스처 (일관성)

📚 다음으로...

Phase 2 — 웹 인프라 기초

📚 Phase 2 — 웹 인프라 기초
  Unit 2.1 — 웹서버 vs WAS
  Unit 2.2 — 서블릿과 JSP
  Unit 2.3 — SSR vs CSR
  Unit 2.4 — JAR vs WAR

6주차 누적 진행

🧪 Part A — 학습 도구와 환경
  ✅ Phase 1 — JUnit 테스트 (5 Unit) ← 완주
  ⏭ Phase 2 — 웹 인프라 기초 (4 Unit)

총: 5/28 Unit

🏆 Phase 1 완주 — JUnit 테스트

profile
Software Developer

0개의 댓글