"테스트를 작성하는 것과, 올바른 테스트를 작성하는 것은 다르다"
"테스트가 없는 코드는 레거시다. 하지만 잘못된 테스트가 있는 코드는 더 위험한 레거시다"
안티패턴 Before부터 올바른 패턴 After까지
왜 이렇게 작성해야 하는가 라는 질문으로 단위 테스트의 원리를 끝까지 파헤칩니다
단위 테스트에 관한 자료는 많습니다. 하지만 대부분은 "어떻게 작성하는가" 에서 멈춥니다.
| 일반 자료 | 이 레포 |
|---|---|
| "테스트는 AAA 패턴으로 작성하세요" | AAA가 깨지는 순간, 어떤 냄새가 나는가 |
| "Mock을 사용하세요" | Stub, Spy, Fake 중 이 상황에서 무엇을 써야 하는가 |
| "커버리지를 높이세요" | 커버리지 90%인데 왜 버그가 생기는가 |
| "테스트하기 좋은 설계를 하세요" | 테스트가 어렵다는 신호가 설계 문제를 어떻게 가리키는가 |
| 이론 나열 | Before 코드 + After 코드 + 왜 달라야 하는지 이유 실측 |
각 챕터의 첫 문서부터 바로 학습을 시작하세요!
💡 각 섹션을 클릭하면 상세 문서 목록이 펼쳐집니다
핵심 질문: "좋은 테스트"와 "있는 테스트"는 무엇이 다른가?
단위 테스트가 존재하는 이유와 올바른 첫걸음 (6개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. What Is a Unit? | "단위"의 정의가 팀마다 다른 이유, 고전파 vs 런던파 관점 차이 |
| 02. The Test Pyramid | Unit / Integration / E2E 비율의 근거, 피라미드가 뒤집히면 생기는 일 |
| 03. AAA Pattern | Arrange-Act-Assert 구조가 깨지는 신호, Given-When-Then과의 차이 |
| 04. Test Naming Conventions | testSave() vs save_whenDuplicate_throwsException() — 이름이 문서가 되는 방법 |
| 05. Coverage Myths | 커버리지 100%인데 버그가 생기는 이유, 커버리지가 측정하지 못하는 것 |
| 06. FIRST Principles | Fast / Isolated / Repeatable / Self-validating / Timely — 각 원칙이 깨졌을 때의 증상 |
핵심 질문: 테스트 코드도 코드다. 어떻게 읽기 쉽고 유지보수하기 쉽게 만드는가?
가독성 높고 신뢰할 수 있는 테스트를 만드는 구체적 기법 (6개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. Single Assert Principle | 단일 단언 원칙의 의미, 여러 단언이 필요할 때 어떻게 분리하는가 |
| 02. Test Data Builders | 복잡한 픽스처를 Builder 패턴으로 정리하는 방법, 가독성 변화 비교 |
| 03. Boundary Testing | 경계값 분석, Off-by-One 에러를 테스트로 잡는 방법 |
| 04. Parameterized Tests | @ParameterizedTest, @CsvSource, @MethodSource — 중복 없이 케이스 확장 |
| 05. Fixtures & SetUp | @BeforeEach의 올바른 범위, 공유 픽스처가 만드는 숨겨진 결합 |
| 06. Meaningful Assertions | 실패 메시지를 읽으면 원인을 알 수 있는 단언 작성법, AssertJ 활용 |
핵심 질문: Stub, Mock, Fake — 이 세 가지를 구분하지 못하면 테스트가 거짓말을 한다
테스트 대역의 올바른 선택과 Mockito 실전 활용 (7개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. Test Doubles Taxonomy | Dummy / Stub / Spy / Mock / Fake 5가지 분류, 언제 무엇을 선택하는가 |
| 02. Stub vs Mock | 상태 검증 vs 행동 검증의 트레이드오프, 잘못된 Mock 사용이 만드는 취약한 테스트 |
| 03. Mockito Best Practices | @Mock / @InjectMocks / @Captor 올바른 사용, when().thenReturn() vs doReturn() |
| 04. Partial Mocking with Spy | @Spy가 필요한 상황과 함정, Spy가 필요하다면 설계를 의심하라 |
| 05. Argument Matchers | any() 남용이 만드는 거짓 통과, ArgumentCaptor로 정확하게 검증하기 |
| 06. Verify Wisely | verify()를 언제 써야 하고 언제 쓰면 안 되는가, 과도한 검증의 문제 |
| 07. Fakes over Mocks | In-memory Repository가 Mockito Repository보다 나은 경우, Fake의 장단점 |
핵심 질문: 테스트가 어렵다는 느낌은 설계가 나쁘다는 신호인가?
테스트 용이성을 높이는 설계 원칙과 리팩터링 패턴 (6개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. DI for Testability | 의존성 주입이 테스트에 미치는 영향, 생성자 주입 vs 필드 주입의 테스트 비용 차이 |
| 02. Avoiding Static Methods | 정적 메서드가 테스트를 망치는 이유, PowerMock 없이 해결하는 방법 |
| 03. Interface Segregation | 작은 인터페이스가 Stub을 쉽게 만드는 이유, ISP와 테스트 용이성의 관계 |
| 04. Humble Object Pattern | UI / 외부 시스템과 로직을 분리해 테스트 가능한 영역을 최대화하는 방법 |
| 05. Pure Functions First | 부수효과 없는 로직의 테스트 용이성, 도메인 모델을 순수하게 유지하는 전략 |
| 06. Ports and Adapters | Hexagonal Architecture에서 테스트 경계를 어떻게 그리는가 |
핵심 질문: 단위 테스트가 모두 통과했는데 왜 시스템이 망가지는가?
경계를 가로지르는 테스트 — 데이터베이스, API, 스프링 컨텍스트 (6개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. Integration Test Scope | 무엇을 통합 테스트로 검증해야 하는가, 단위 테스트와의 역할 분리 |
| 02. Database Testing with Testcontainers | H2 대신 실제 DB로 테스트하기, Testcontainers 설정과 비용 |
| 03. REST API Testing | MockMvc vs WebTestClient vs RestAssured 선택 기준과 비교 |
| 04. Spring Context Slicing | @WebMvcTest, @DataJpaTest, @SpringBootTest의 범위와 올바른 선택 |
| 05. Test Transaction Management | @Transactional 테스트의 롤백 함정, 실제 커밋을 검증해야 할 때 |
| 06. Contract Testing | Consumer-Driven Contracts, Pact로 서비스 간 계약을 테스트하는 방법 |
핵심 질문: 테스트가 있는데도 코드 변경이 두렵다면 — 무엇이 잘못된 것인가?
신뢰를 갉아먹는 7가지 안티패턴과 리팩터링 방법 (7개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. Test Logic in Production | if (isTest) 분기, 테스트 전용 생성자 — 테스트가 프로덕션을 오염시키는 패턴 |
| 02. Sleepy Tests | Thread.sleep()으로 비동기를 검증하는 문제, Awaitility로 안전하게 대체하기 |
| 03. Flickering Tests | 불규칙하게 실패하는 테스트의 4가지 원인과 각각의 해결 전략 |
| 04. Overspecified Tests | 구현 세부사항을 검증하는 테스트, 리팩터링할 때마다 테스트가 깨지는 이유 |
| 05. Test Code Duplication | 반복되는 setUp, 반복되는 단언 — DRY가 테스트에서 의미하는 것 |
| 06. Hidden Test Dependencies | 테스트 실행 순서에 의존하는 테스트, 공유 상태가 만드는 시한폭탄 |
| 07. Assertion Roulette | 실패 시 어떤 단언이 틀렸는지 알 수 없는 테스트, SoftAssertions 활용 |
핵심 질문: 테스트를 "잘 쓴다"는 것을 넘어, 테스트가 설계를 이끌 수 있는가?
뮤테이션 테스팅, 속성 기반 테스팅, TDD — 테스트의 다음 단계 (6개 문서)
| 문서 | 다루는 내용 |
|---|---|
| 01. Mutation Testing | PIT(PITest)로 테스트 품질 측정하기, 커버리지가 잡지 못하는 결함 찾기 |
| 02. Property-Based Testing | jqwik으로 경계를 자동 탐색, 예제 기반 테스트와 속성 기반 테스트의 차이 |
| 03. Architecture Testing | ArchUnit으로 레이어 의존성 규칙을 테스트로 강제하는 방법 |
| 04. TDD Workflow | Red → Green → Refactor 사이클의 실전 리듬, 언제 테스트를 먼저 쓰고 언제 나중에 쓰는가 |
| 05. Test-Driven Design | 테스트를 먼저 쓰면 설계가 어떻게 달라지는가, "테스트 가능성 = 설계 품질"의 의미 |
| 06. Testing Legacy Code | 테스트 없는 코드에 테스트를 추가하는 순서, Seam 개념과 Characterization Test |
🟢 단위 테스트를 처음 배우는 개발자 / 테스트 습관을 만들고 싶은 분 (2~3주)
Week 1 — 테스트의 본질 이해
what-is-a-unit
the-test-pyramid
aaa-pattern
test-naming-conventions
Week 2 — 올바른 테스트 작성
single-assert-principle
meaningful-assertions
test-doubles-taxonomy
stub-vs-mock
Week 3 — 안티패턴 제거
overspecified-tests
flickering-tests
hidden-test-dependencies
test-code-duplication
🔵 테스트를 쓰고 있지만 신뢰가 가지 않는 개발자 (4~6주)
anatomy-of-good-tests 전체
→ mocking-strategies 전체 (Stub, Mock, Fake 완전히 구분)
→ test-anti-patterns 전체 (내 테스트에서 냄새 찾기)
→ testable-design 전체 (설계부터 바꾸기)
🔴 테스트가 설계를 이끄는 수준을 목표로 하는 개발자 (2~3개월)
전체 순서대로 + 예제 코드 직접 리팩터링
→ advanced-topics 챕터로 마무리
→ 실제 프로젝트에 mutation testing 적용
→ legacy code에 characterization test 추가하며 체화
모든 문서는 동일한 구조로 작성됩니다.
| 섹션 | 설명 |
|---|---|
| 🎯 핵심 질문 | 이 문서를 읽고 나면 답할 수 있는 질문 |
| 🔍 왜 이 패턴이 중요한가 | 이 패턴이 없을 때 생기는 실제 문제 |
| 😱 안티패턴 (Before) | 흔히 쓰는 잘못된 코드 + 무엇이 문제인지 |
| ✨ 올바른 패턴 (After) | 개선된 코드 + 왜 이것이 더 나은지 |
| 💻 실전 적용 | 복사해서 바로 쓸 수 있는 예제, JUnit5 + AssertJ + Mockito |
| 🤔 트레이드오프 | 이 패턴의 한계와 적용하지 말아야 할 상황 |
| 📌 핵심 정리 | 한 화면 요약 |
- Unit Testing: Principles, Practices, and Patterns — Vladimir Khorikov
- Growing Object-Oriented Software, Guided by Tests — Freeman, Pryce
- Test-Driven Development: By Example — Kent Beck
- Working Effectively with Legacy Code — Michael Feathers
- JUnit 5 User Guide