“테스트 를 학습 한다”처음 듣는 신입 에게 이 말JUnit 의 어노테이션외우는 일 처럼 들린다. @Test, assertEquals, Mockito.when().thenReturn()문법 학습.

그러나 *9 년 을 지나고 보면 이 단어 의 *진짜 의미전혀 다른 영역 에 있다. *테스트 를 배운다 는 것은 내 코드 의 *형태 자체바꾸는 학습. 내 객체 가 *어떻게 *남과 대화 하는지, 어디서 *경계 가 깨지는지, 무엇이 *내 책임 이고 무엇이 *남의 책임 인지 를 눈에 보이는 형태드러내는 학습*.

즉 *테스트 의 학습객체 지향 의 학습다른 이름.

이 글은 테스트 를 *어떻게 쓰는지 가 아니라 테스트 를 *학습 하는 것내 머릿 속 에서 *무엇을 바꾸는가 * 에 대한 고찰. 9 년 의 실전 에서 내가 *오해 했다가 *교정 한 7 가지 의미밀도 있게 풀어 낸다.

함께 보면 좋은 자매편 :


TL;DR — 한 줄 결론

“테스트 를 학습 한다”진짜 의미JUnit 의 문법 학습 도, 커버리지 의 수치 학습 도 아니다. 내 객체 가 *외부 의존 으로 부터 얼마나 분리 되어 있는지, *내 객체 의 *책임 이 *얼마나 좁고 분명 한지, *내 객체 의 *공개 API 가 *얼마나 정직 한지 — 이 세 가지 의 *피드백 을 *실시간 으로 받는 *훈련. 테스트 가 *쓰기 어렵다 는 것은 기술 의 부족 이 아니라 설계 의 신호. 그 신호 를 *듣는 귀 를 *기르는 일테스트 학습 의 본질.


1. *오해 1 — *“테스트 = JUnit 의 문법”**

처음 3 년차 까지 의 내 *오해. JUnit / Mockito / AssertJ문법 을 *능숙 해지면 테스트 를 잘 한다믿었다.

1.1 *문법 의 *얕은 학습**

@Test
void test() {
    // given
    Order order = new Order(...);
    when(repo.findById(1L)).thenReturn(Optional.of(order));

    // when
    OrderDTO result = service.getOrder(1L);

    // then
    assertEquals(order.getId(), result.getId());
}

이런 테스트 를 *수백 개 작성 했다. 모든 메서드 마다 *대응 테스트 1 개. 커버리지 88 %. PR 통과. 완료.

1.2 *문법 학습 의 *세 가지 함정**

  • Mock 의 의미모르고 *Mockito 만 안다. 왜 mock 하는지 가 아니라 어떻게 *when().thenReturn() 을 쓰는지* 만 안다.
  • given/when/then형식 은 따르되, 그 안에 들어 갈 *내용 의 *수준고민 없음. 복사 붙여 넣기 의 의례.
  • 테스트 가 *깨지면 *수정 한다. 왜 깨졌나통찰 의 기회 인데 그저 *맞는 답 을 *재 주입.

이 단계 에서 나 의 코드 는 *변하지 않는다. 테스트 가 *나 의 *코드 의 형태바꾸지 못한다.

1.3 벗어 나는 첫 신호

“이 테스트 가 *너무 *쓰기 힘들다”반복 되는 순간. Mock 이 5 개 이상 필요 한 단위 테스트. given 블록 이 *30 줄. 어디 부터 손 댈지 막막함.

예전 의 나“내 Mockito 가 *서툴러서 그렇다”. *교재 를 다시 본다.

지금 의 나“내 *클래스 가 *너무 많은 일 을 한다”. *내 *경계 가 *흐릿하다. 테스트 가 *내게 *설계 의 빚경고 하고 있다.

이 *번역 의 차이학습 의 *진정한 시작.


2. *오해 2 — *“테스트 = *안전망”**

4 ~ 5 년차내 *진화 한 오해. 테스트 는 *리팩토링 의 안전망 이라는 부분 적 으로 옳은 인식.

2.1 *안전망 의 *효용**

  • 큰 리팩토링 시 기능 깨짐 *즉시 탐지.
  • 수정 의 *심리 적 안전감.
  • 수년 후 *후임 자 의 *암묵 적 문서.

이 시야 만 으로 도 코드 의 안전성극적으로 개선. 이게 *대부분 의 회사 의 *공식 입장. “테스트 잘 짭시다, 안전 하게 갑시다”.

2.2 *그러나 *안전망 의 *한계**

테스트 를 *안전망 으로 만 보는 시야* 는 코드 가 작성 된 후 의 *수동 적 검증 에 머문다. 테스트 가 *코드 의 *형태 를 결정 하지 못한다*.

  • 코드 작성테스트 작성 (사후)
  • 테스트 가 *어렵 으면 *테스트 를 *우회 (private 노출, reflection, helper). 코드 는 그대로.
  • 결과 — *테스트 가 *코드 의 *모양 의 *증명 이 아니라 코드 의 *현재 모양 의 *기념 비.

2.3 *이 시점 의 *조용한 진실**

안전망 시각 에서 의 *테스트코드 보다 *늘 *2 단계 늦은 추격자. 시니어 개발자 의 *진짜 실력테스트 학습 의 *다음 단계 부터 시작 된다.


3. *오해 3 — *“테스트 = 커버리지 의 수치”**

조직 차원흔한 오해. PR 의 통과 조건 으로 Jacoco 80 %.

3.1 *수치 의 *유혹**

  • 측정 가능, 자동화 가능, 보고 가능.
  • 관리자 의 *주간 보고깔끔 한 숫자.
  • 통과 / 불통과 의 *명확한 기준.

3.2 *수치 의 *조용한 거짓말**

@Test
void test() {
    service.doSomething();   // 호출 만 함
    // assertion 없음
}

커버리지 — 100 %. 실제 검증 — 0 %.

또는 :

@Test
void test() {
    when(externalApi.call()).thenReturn(MOCK_RESPONSE);
    Result result = service.process();
    assertEquals(MOCK_RESPONSE, result);   // 사실상 mock 의 무한 반사
}

무엇 도 검증 안 된 *tautology. 그러나 *커버리지 + 통과.

3.3 *수치 의 *함정 의 *진짜 위험**

개발자 가 *커버리지 를 *맞추는 작업 으로 학습 의 방향왜곡 됨. 진짜 테스트 의 가치수치 의 *형식 적 충족 으로 환원.

9 년 의 경험커버리지 80 % *수치코드 품질 의 *명확한 지표아니라는 사실반복 적 확인. 수치 의 의미그 안에 든 *테스트 의 *내용 으로 만 결정 됨*.

학습 의 진화“몇 % 인가” 가 아니라 “이 테스트 들 이 *진짜 무엇 을 검증 하는가” 의 질문 으로 전환*.


4. *전환점 — *“테스트 가 *설계 의 *피드백 이다”**

6 ~ 7 년차결정 적 발견. GOOS 책 (Growing Object-Oriented Software, Guided by Tests, Freeman/Pryce) 을 통과한 후.

4.1 *설계 피드백 의 *정의**

테스트 가 *쓰기 *어렵다 면 *기술 의 부족 이 아니라 *설계 의 *건강 의 약점**.

테스트 의 증상 설계 의 진단
Mock 이 5 개 이상 필요 클래스 가 *너무 많은 의존성단일 책임 위반
given 의 *셋업 30 줄 생성자 가 *너무 많은 일Builder 또는 *분리 필요*
private 메서드 를 *직접 테스트 하고 싶다 그 *내부 로직 이 *별도 클래스 의 *공개 책임클래스 추출 필요
static 메서드 / new 키워드 가 테스트 막는다 의존성 *제어 불가생성자 주입 누락
테스트 가 *내부 구현 알아야 통과 공개 API 가 *내부 노출 함캡슐화 약화
동일 테스트 가 *여러 곳 에 *복사 됨 공통 추상 의 누락 — 추상화 의 *실패

이 표 의 모든 진단 의 *치료테스트 가 아니라 *프로덕션 코드 의 *재 설계.

4.2 *Listen to your tests**

GOOS 의 유명 한 구호. 테스트 의 *통증 은 *진단 *. *진단 을 *듣고 *처방 은 *프로덕션 코드 의 *재 구성.

한 줄 의 *철학테스트 의 의미영구 적으로 바꾼다.

4.3 내 *settlement 프로젝트 의 *경험적 증거**

settlement 의 *Outbox Publisher첫 버전테스트 시 *Kafka mock + DB mock + Clock mock + Transaction mock + Metrics mock = 5 개 mock. 테스트 30 줄.

기능 동작. 그러나 *읽기 어렵. 수정 어렵.

GOOS 적 진단“5 개 mock = 5 개 의 협력자 = 단일 책임 위반”.

재 설계Publisher 를 *3 개 클래스 로 분리 :

  • OutboxLoader (DB 만)
  • KafkaSender (Kafka 만)
  • OutboxOrchestrator (둘 의 조정 + Metrics + Clock)

각 클래스 의 테스트 — *mock 1 ~ 2 개. 명확한 책임. 주석 거의 없어도 *읽힘.

프로덕션 코드 의 *품질 향상 의 *직접 적 인 *원동력 이 *테스트 의 통증.


5. *깊이 — *“테스트 = *설계 의 *시간 적 압축”** (TDD)

테스트 가 *설계 피드백 이면 그 피드백 을 *코드 *작성 전 에 받자TDD 의 본질.

5.1 *Kent Beck 의 *Red-Green-Refactor**

  1. Red — *실패 하는 테스트 작성 (원하는 동작 의 선언).
  2. Green — *최소한 의 코드 로 통과 시킴.
  3. Refactor — *동작 보존 + 설계 개선.

표면 적으로 *순서 의 문제. 진짜 의미 는 *순서 의 문제 가 아니다.

5.2 *TDD 가 *진짜 학습 시키는 것**

  • 원하는 *공개 API코드 쓰기 전에 *상상 한다. 호출 자 의 시점 으로 내 객체 를 보는 *훈련.
  • 최소 단위 의 변화한 번에 *한 책임 만 추가. 작은 단위 의 *결정 의 *축적.
  • Refactor 를 *별도 단계제도화설계 의 *지속 적 개선부차 적 활동 이 아닌 *중심 단계.

5.3 *Ian Cooper 의 *경고**

“TDD, where did it all go wrong?” 강의 의 결정 적 통찰 :

Kent Beck 의 *Test행위 의 단위 (unit of behavior) 였다. 우리 가 *오해 하고 클래스 / 메서드 단위테스트 하기 시작 했다. *결과 — *과도한 Mock + 내부 구조 결합 + 리팩토링 의 *불가능.

올바른 *TDD 의 단위시스템 의 *경계 (port) 또는 기능 의 *행위. 예 :

  • Order 의 *결제 처리 행위 (입력 → 출력 + 부수 효과).
  • 클래스 A 의 *메서드 m 이 아니라.

한 줄 의 의미 의 차이테스트 의 *모양 과 *유지 보수성 을 *전혀 다른 차원 으로 바꾼다*.

5.4 *Mockist vs Classicist 의 *고전 적 분기**

Mockist (London 학파) Classicist (Chicago 학파)
모든 협력자 를 *mock 실제 객체 사용, 외부 경계 만 mock
상호 작용 (호출 의 순서 / 횟수) 검증 최종 상태 (output / state) 검증
완전한 격리 작은 통합 (sociable test)
Mockito / EasyMock 적극 POJO 의 *new + assertion 중심

양 학파 의 *오랜 논쟁. 대답 — *둘 의 *조합. 외부 경계 (DB, Kafka, 외부 API) 는 *mock. 내부 도메인 객체 는 *실제 사용.

이 균형내 *settlement / sparta 의 *테스트 패턴. 내 *9 년 의 *경험 적 수렴.


6. *경계 — *“테스트 = *시스템 의 *경계 의 *공식 문서”**

8 ~ 9 년차 에서 깨닫는 다음 층.

6.1 *Port / Adapter 의 *경계 가 *테스트 의 단위**

Hexagonal Architecture 의 시각 :

  • 도메인 모델외부 와 격리. 순수 한 Java 객체. 테스트 가 *극도로 간단. Mock 0 개.
  • Application Service (포트)도메인 의 조정. 외부 의존 은 *인터페이스 로 추상화. 테스트 시 *내부 도메인 은 실제 + 외부 포트 만 mock.
  • Adapter (DB, Kafka, REST)각각 별도 통합 테스트 (Testcontainers 등 활용).

결과테스트 가 *시스템 의 *경계 의 *공식 적 인 *문서. 어디 가 *내 도메인 책임 이고 어디 가 *외부 책임 인지 코드 보다 *명료.

6.2 *settlement 의 *ArchUnit — *경계 의 *컴파일 시 강제**

나의 settlement 에선 *ArchUnit 으로 3 가지 규칙컴파일 차원 에서 강제:

  1. Domain 패키지 가 *Spring 의 어떤 클래스 도 *의존 못 함.
  2. Application 의 *비즈니스 로직 이 *JPA 직접 사용 못 함.
  3. Adapter 간 *교차 의존 금지.

이게 테스트 의 *최고 형태 중 하나. 런타임 의 *행위 검증 이 아니라 구조 의 *불변식 검증. 팀 의 *수십 명 의 *우연한 위반 을 *컴파일 시 차단.

6.3 *Specification 으로 서 의 *테스트**

“테스트 가 코드 의 *동작 을 *증명 한다”* 보다 “테스트 가 *동작 의 *정의” 다 의 시각.

  • 테스트 = *기대 의 *공식 적 *언어.
  • 프로덕션 코드 = *그 기대 의 *실행 가능 한 *구현.
  • 그 둘 의 *동시 진화 가 *시스템 의 본질.

BDD (Behavior-Driven Development)given-when-then진짜 의미 가 이 시각. 비즈니스 의 *언어테스트 의 *형식 으로 직접 표현*.


7. *철학 — *“테스트 = *자기 의 *과거 + 미래 의 자아 와 의 대화”**

가장 깊은 층. 9 년 의 사고 추적비로소 알게 한 시점.

7.1 *과거 의 자아 의 *의도 의 보존**

몇 달 후 의 내가 *이 코드 를 본다. 왜 *이렇게 분기 했는지 기억 못 함. git blame 봐도 *그 당시 의 *전체 맥락재현 불가.

그러나 *테스트 가 있으면 :

  • 입력 의 *특정 패턴 에서 출력 의 *특정 패턴의도 적 임 을 밝힘.
  • 각 분기 의 *존재 이유 가 *별도 테스트 로 *기록.
  • 변경 시 *내가 *모르고 깨면 *과거 의 *내가 즉시 경고.

테스트 가 *시간 을 *가로지르는 *내 *의도 의 *서명.

7.2 *미래 의 협력자 의 *학습 곡선 단축**

내 동료 가 *내 코드 를 *읽는다. 프로덕션 코드 만 보면 *what 은 알아도 *why 는 모름.

테스트 가 :

  • 공개 API 의 *대표 적 사용 패턴코드 형태로 *전시.
  • 예외 적 케이스 의 *명시 적 처리각자 의 테스트 로 *분리 되어 *읽기 쉬움.
  • 변경 의 *영향 범위 가 *깨지는 테스트 의 *분포자동 추적.

테스트 가 *동료 에 대한 *시간 적 친절.

7.3 *나 자신 의 *집중 의 *외주화**

Kent Beck덜 알려진 통찰 :

“테스트 의 *진짜 가치 는 *내가 *코드 작성 중기억 해야 할 것외주. 내가 *지금 이 한 줄 에 집중 할 수 있는 전제 조건.”*

테스트 가 *내 *전체 시스템 의 *불변식 을 *지키는 동안, 나는 *지금 *이 한 줄부분 적 문제 에 *완전 집중 가능. 인지 부하 의 분산.

이게 *9 년 이 *알려준 *가장 깊은 의미. 테스트 의 학습 = *집중 의 *경제학 의 학습.


8. *학습 의 *단계 의 *정리**

이 글 의 7 가지 의 의미학습 의 단계 로 다시 정리.

단계 의미 기간
1 JUnit 의 문법 0 ~ 6 개월
2 테스트 = 안전망 6 개월 ~ 3 년
3 커버리지 의 *수치 조직 따라 *수년 정체 가능
4 테스트 = *설계 피드백 (GOOS) 4 ~ 6 년 (책 / 멘토 / 사고 가 촉발)
5 TDD — *설계 의 *시간 적 압축 5 ~ 8 년 (Ian Cooper 의 교정 포함)
6 경계 의 *공식 문서 (Hexagonal / ArchUnit) 7 ~ 9 년
7 시간 적 자아 와 의 *대화 지속 적 *내면 화

기간 은 *대략 적. 조직 / 멘토 / 본인 의 *호기심 의 *3 차 함수.

중요 한 한 가지위 의 단계 가 *순차 적 *덮어 쓰기 가 아니라 *누적 적 *층 위. 8 년차 도 *문법 사용. *수치 의 *함정 도 *반복 적 으로 *재 발견. 학습 은 *나선 형.


9. *추천 *학습 자원**

9.1

  1. “Growing Object-Oriented Software, Guided by Tests” (Freeman/Pryce, 2009) — OOP + 테스트 의 *가장 깊은 합본. 내 *6 년차 의 *전환 점.
  2. “Test-Driven Development by Example” (Kent Beck, 2002) — 원본. 짧지만 *밀도 ↑.
  3. “Working Effectively with Legacy Code” (Michael Feathers, 2004) — 테스트 없는 코드 를 *어떻게 *테스트 가능 한 *상태로 끌어 올리는지. 현실 의 *대부분 의 *상황.
  4. “xUnit Test Patterns” (Gerard Meszaros, 2007) — 테스트 코드 의 *디자인 패턴 의 *백과 사전.
  5. “Refactoring” (Martin Fowler, 2nd ed., 2018) — 테스트 와 *불가분 의 *짝.

9.2 강의 / 영상

  1. Ian Cooper — “TDD, where did it all go wrong?” (2017 NDC London). 25 분 의 *교정.
  2. Sandi Metz — “The Magic Tricks of Testing” (RailsConf 2013). 언어 가 Ruby 지만 *원리 는 보편.
  3. Kent Beck — “Test Desiderata” 시리즈. 짧은 12 편 의 *깊은 명상.

9.3 *훈련 의 *습관**

  • 매주 *한 개 *kata (CodingDojo.org).
  • 기존 코드 의 *“이 테스트 *왜 *쓰기 어렵나?” 5 분 *자문.
  • 팀 코드 리뷰 시 *“이 테스트 가 *몇 개 의 *Mock 을 요구 하는가?”* 를 공식 적 *질문 으로 추가.

10. *결론 — *7 가지 의미 의 *동시 적 *진실**

“객체 지향 개발 에서 *테스트 를 *학습 한다는 것* * 의 의미 :

  1. JUnit 의 *문법 을 *익히는 것맞다, 그러나 *시작 일 뿐.
  2. 코드 의 *안전망 을 *만드는 것맞다, 그러나 *수동 적 임.
  3. 커버리지 의 *수치 를 채우는 것대부분 *함정.
  4. 설계 의 *피드백 을 *듣는 것진짜 의 시작.
  5. 설계 를 *시간 적 으로 *압축 하는 것 (TDD) — 훈련 가능 한 *기량.
  6. 시스템 의 *경계 의 *공식 문서 화조직 차원 의 *효과.
  7. 시간 적 자아 + 미래 동료 와 의 *대화 의 *제도화가장 깊은 의미.

이 7 가지 가 *동시 적 *진실. 어느 하나 가 *다른 것 을 *대체 하지 않는다. 각자 의 깊이 의 층 위내 *경험 의 *9 년 동안 *서로 *자랐다.

테스트 를 *학습 한다 는 것은 결국 *내 *객체 가 *어떻게 *살아 있는지나 자신 에게 *반복 적으로 *질문 하는 *훈련. 그 질문 의 *형식 이 *JUnit 의 *어노테이션 이라는 사소한 사실너무 일찍 만족 하지 말기.


다음 으로 *권 하는 읽기**

  • Sandi Metz — “Practical Object-Oriented Design in Ruby” (POODR). OOP 의 *깊이 자체. 언어 와 무관 한 *통찰.
  • Robert C. Martin — “Clean Code”테스트 챕터. F.I.R.S.T 원칙 (Fast, Independent, Repeatable, Self-validating, Timely).
  • Hexagonal Architecture 의 원작Alistair Cockburn 의 *2005 글. 경계 의 *철학.
  • 자매편 — 내 *5/29 글 TDD / Mockito / 가치 / Spring 의 역사역사 적 시점 의 보충.

다음 글이 7 가지 의 의미 가 *내 *settlement / sparta-msa / lemuel-xr실제 PR 들 에 *어떻게 적용 되는지3 부 케이스 시리즈 — 곧.