Skip to content

iq-dev-lab/unit-testing

Repository files navigation

🧪 Unit Testing: Principles to Practice

"테스트를 작성하는 것과, 올바른 테스트를 작성하는 것은 다르다"


"테스트가 없는 코드는 레거시다. 하지만 잘못된 테스트가 있는 코드는 더 위험한 레거시다"

안티패턴 Before부터 올바른 패턴 After까지
왜 이렇게 작성해야 하는가 라는 질문으로 단위 테스트의 원리를 끝까지 파헤칩니다


GitHub Java JUnit5 Docs License


🎯 이 레포에 대하여

단위 테스트에 관한 자료는 많습니다. 하지만 대부분은 "어떻게 작성하는가" 에서 멈춥니다.

일반 자료 이 레포
"테스트는 AAA 패턴으로 작성하세요" AAA가 깨지는 순간, 어떤 냄새가 나는가
"Mock을 사용하세요" Stub, Spy, Fake 중 이 상황에서 무엇을 써야 하는가
"커버리지를 높이세요" 커버리지 90%인데 왜 버그가 생기는가
"테스트하기 좋은 설계를 하세요" 테스트가 어렵다는 신호가 설계 문제를 어떻게 가리키는가
이론 나열 Before 코드 + After 코드 + 왜 달라야 하는지 이유 실측

🚀 빠른 시작

각 챕터의 첫 문서부터 바로 학습을 시작하세요!

Testing Fundamentals Good Tests Mocking Testable Design Integration Anti-patterns Advanced


📚 전체 학습 지도

💡 각 섹션을 클릭하면 상세 문서 목록이 펼쳐집니다


🔹 테스팅 기초 (Testing Fundamentals)

핵심 질문: "좋은 테스트"와 "있는 테스트"는 무엇이 다른가?

단위 테스트가 존재하는 이유와 올바른 첫걸음 (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 — 각 원칙이 깨졌을 때의 증상

🔹 좋은 테스트의 해부 (Anatomy of Good Tests)

핵심 질문: 테스트 코드도 코드다. 어떻게 읽기 쉽고 유지보수하기 쉽게 만드는가?

가독성 높고 신뢰할 수 있는 테스트를 만드는 구체적 기법 (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 활용

🔹 목킹 전략 (Mocking Strategies)

핵심 질문: 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의 장단점

🔹 테스트 가능한 설계 (Testable Design)

핵심 질문: 테스트가 어렵다는 느낌은 설계가 나쁘다는 신호인가?

테스트 용이성을 높이는 설계 원칙과 리팩터링 패턴 (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에서 테스트 경계를 어떻게 그리는가

🔹 통합 테스팅 (Integration Testing)

핵심 질문: 단위 테스트가 모두 통과했는데 왜 시스템이 망가지는가?

경계를 가로지르는 테스트 — 데이터베이스, 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로 서비스 간 계약을 테스트하는 방법

🔹 테스트 안티패턴 (Test Anti-patterns)

핵심 질문: 테스트가 있는데도 코드 변경이 두렵다면 — 무엇이 잘못된 것인가?

신뢰를 갉아먹는 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 활용

🔹 고급 주제 (Advanced Topics)

핵심 질문: 테스트를 "잘 쓴다"는 것을 넘어, 테스트가 설계를 이끌 수 있는가?

뮤테이션 테스팅, 속성 기반 테스팅, 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
🤔 트레이드오프 이 패턴의 한계와 적용하지 말아야 할 상황
📌 핵심 정리 한 화면 요약

🙏 Reference


⭐️ 도움이 되셨다면 Star를 눌러주세요!

Made with ❤️ by Dev Book Lab


"테스트를 작성하는 것과, 올바른 테스트를 작성하는 것은 다르다"

About

테스트를 작성하는 것과, 올바른 테스트를 작성하는 것은 다르다

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors