397. 단위 테스트 (Unit Test) - 최소 단위 기능 검증
⚠️ 이 문서는 소프트웨어를 구성하는 가장 작은 부품인 개별 모듈이나 함수(메서드)가 설계된 대로 정확히 동작하는지를 독립적으로 분리하여 검증하는, 개발자 주도의 테스팅 첫 번째 관문인 **'단위 테스트(Unit Testing)'**를 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 단위 테스트는 자동차를 조립하기 전에, 나사 하나, 톱니바퀴 하나의 강도와 크기가 정확한지 먼저 개별적으로 확인하는 과정이다. 소프트웨어에서는 가장 작은 단위인 **'함수(Function)나 클래스(Class)'**가 테스트의 단위가 된다.
- 가치: 버그는 소프트웨어가 거대해질수록 찾기 기하급수적으로 어려워진다. 단위 테스트는 가장 작은 단위에서 버그를 조기에 압살(Shift-Left)함으로써, 나중에 전체 시스템을 다 조립해 놓고 원인을 찾아 헤매는 디버깅 지옥을 막아준다.
- 기술 체계: 코드를 짠 개발자가 직접 수행하며, 코드의 내부 구조와 제어 흐름을 훤히 들여다보고 테스트하는 화이트박스 테스트(White-box Testing) 기법이 주로 사용된다.
Ⅰ. 개요: 작은 부품의 완벽함 (Context & Necessity)
수십만 줄의 코드로 이루어진 웹 서버에서 회원가입 버튼을 눌렀는데 서버가 죽어버렸다. 원인이 뭘까? 네트워크 문제인지, 데이터베이스 연결 문제인지, 아니면 비밀번호를 암호화하는 10줄짜리 작은 함수 하나가 에러를 뿜었는지 알 길이 없다. 이것이 거대한 시스템을 한꺼번에 테스트할 때 겪는 '버그의 은닉(Bug Hiding)' 현상이다.
이를 막기 위해 소프트웨어 공학은 상향식(Bottom-Up) 품질 보증을 채택했다. "전체를 엮어서 돌려보기 전에, 회원가입 함수, 비밀번호 암호화 함수, DB 접속 함수를 전부 다 쪼개서 하나하나 따로따로 100% 통과시켜 놓고 조립하자!" 이 가장 밑바닥, 가장 좁은 범위를 커버하는 치밀한 테스트가 바로 **단위 테스트(Unit Test)**이다.
📢 섹션 요약 비유: 우주선을 쏠 때, 다 조립해 놓고 발사 버튼을 눌러서 터지는지 안 터지는지 실험하는 건 미친 짓입니다. 단위 테스트는 우주선에 들어갈 수만 개의 나사와 엔진 밸브를 공장에서 하나하나 현미경으로 검사하여 불량품을 미리 걸러내는 필수 작업입니다.
Ⅱ. 단위 테스트의 3대 핵심 특징
단위 테스트가 다른 시스템/인수 테스트와 구분되는 가장 결정적인 특징들은 다음과 같다.
1. 격리 (Isolation)와 독립성
로그인()함수를 테스트하려고 하는데, 이 함수 안에서 실제DB 서버를 호출하게 코드를 짜면 안 된다. DB 서버가 다운되어 있어서 테스트가 실패하면, 로그인 로직이 틀린 건지 망이 끊긴 건지 알 수 없기 때문이다.- 따라서 진짜 DB 대신, 가짜 DB 객체인 **목(Mock) 객체나 스텁(Stub)**을 꽂아 넣어서, 오직 내 로그인 로직 10줄만 완벽히 격리된 환경에서 테스트해야 한다.
2. 화이트박스 테스트 (White-box Testing) 위주
- 테스터는 함수의 소스 코드를 보면서 설계한다.
if (age > 18)이라는 분기문이 보이면, 테스터는 의도적으로age=19일 때와age=18일 때의 두 가지 단위 테스트 케이스를 만들어 모든 코드 흐름(Branch Coverage)이 다 실행되도록 검증한다.- 개발자 본인이 자기가 짠 코드를 대상으로 직접 작성하는 것이 가장 일반적이다.
3. 자동화 (Automation)의 꽃
- 단위 테스트 코드는 한 번 짜놓으면, 빌드 서버(CI/CD, 예: Jenkins)에서 하루에도 수백 번씩 자동으로 실행된다.
- 이를 통해 개발자가 어제 짠 코드를 오늘 수정하다가 무언가 깨뜨렸을 때, 즉시 알람이 울리는 회귀 테스트(Regression Test) 방어막 역할을 완벽하게 수행한다.
┌──────────────────────────────────────────────────────────────────────────────────┐
│ 단위 테스트(Unit Test)의 '격리(Isolation)' 메커니즘 시각화 │
├──────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ [ 나쁜 예: 얽혀있는 테스트 (Integration Test에 가까움) ] │
│ [ 내 함수: 회원가입() ] ──▶ (실제 네트워크) ──▶ [ 진짜 외부 DB 서버 ] │
│ (DB 서버가 죽어있으면 내 함수 코드 로직이 맞아도 테스트가 실패함! 불확실성↑) │
│ │
│ 🛡️ [ 완벽한 예: 단위 테스트 (Unit Test) ] │
│ [ 내 함수: 회원가입() ] ──▶ [ 🤡 가짜 DB 객체 (Mock Object) ] │
│ (항상 "가입 성공!" 이라고만 대답하게 설정) │
│ │
│ * 결과: 외부 요인 100% 차단. 오직 '회원가입' 함수 안의 순수한 계산 로직, │
│ if/else 문이 제대로 돌아가는지만 초정밀하게 검증 성공! │
└──────────────────────────────────────────────────────────────────────────────────┘
Ⅲ. 실무에서의 단위 테스트 (TDD와 프레임워크)
단위 테스트를 수작업으로 짤 수는 없다. 전 세계적으로 표준화된 단위 테스트 프레임워크를 쓴다.
- Java: JUnit, Mockito
- Python: pytest, unittest
- JavaScript/TS: Jest, Mocha
[TDD (Test-Driven Development)의 부상] 과거에는 코드를 다 짠 뒤에 단위 테스트를 만들었다면, 최근에는 **"테스트 코드부터 먼저 짜고, 그 테스트를 통과하기 위한 진짜 코드를 짜자"**라는 극단적인 단위 테스트 중심 개발 방법론인 TDD가 실무의 강력한 트렌드로 자리 잡았다. 이는 단위 테스트가 그 자체로 '살아있는 설계 명세서'가 됨을 증명한다.
Ⅳ. 결론
"디버깅의 공포를 극복하는 가장 작고 강력한 무기." 개발자에게 단위 테스트를 작성하는 일은 본 코드 작성보다 2배의 시간이 더 드는 귀찮고 고통스러운 작업일 수 있다. 그러나 이 작고 파편화된 테스트 뭉치들은 소프트웨어의 덩치가 산만하게 커지고 수십 명의 개발자가 한 코드를 건드리는 순간, 폭풍우 속의 닻처럼 시스템의 견고함을 지탱하는 구원자가 된다. 단위 테스트가 없는 시스템 리팩토링은 눈을 가리고 지뢰밭을 걷는 것과 같으며, 단위 테스트의 높은 커버리지(Coverage)는 곧 현대 소프트웨어 기업의 기술적 자부심(Engineering Excellence) 그 자체다.
📌 관련 개념 맵
- 테스트 레벨 확장: 단위 테스트(Unit) $\rightarrow$ 통합 테스트(Integration) $\rightarrow$ 시스템 테스트(System) $\rightarrow$ 인수 테스트(UAT)
- 주요 기법: 화이트박스 테스트 (구문/분기 커버리지), 격리 테스트 (Isolation)
- 보조 기술 도구: 목 객체 (Mock), 스텁 (Stub), 더미 (Dummy), 스파이 (Spy)
- 개발 방법론: TDD (Test-Driven Development, 테스트 주도 개발)
👶 어린이를 위한 3줄 비유 설명
- 거대한 레고 성을 만들려고 해요. 레고 성을 다 만들고 나서 "어라? 튼튼한가?" 하고 밟아보는 건 너무 위험하죠?
- 그래서 조립을 시작하기 전에, 2칸짜리 레고 블록, 4칸짜리 레고 블록 하나하나를 손으로 꽉 눌러보면서 "이 블록은 불량이 아니네!"라고 검사하는 거예요.
- 이렇게 소프트웨어를 이루는 아주 작은 블록(함수) 하나하나가 잘 돌아가는지 먼저 검사하는 것을 단위 테스트라고 부른답니다!