어디까지를 유닛 테스트로 검증하고 어디부터를 결합 테스트로 검증해야 하는가 - 경계를 긋는 방법과 실무 판단표

· · 테스트, 유닛 테스트, 결합 테스트, 테스트 설계, Windows 개발, C# / .NET

테스트 설계 이야기에서 매번 수수하게 어려운 것이 어디까지를 유닛 테스트에 밀어 넣고 어디부터를 결합 테스트로 올릴 것인가입니다.

여기서 위험한 것은,

  • 빠르게 돌리고 싶으니까 뭐든지 유닛 테스트로 한다
  • 실물에 가까우니까 뭐든지 결합 테스트로 한다

라는 양극단입니다.

전자는 mock투성이가 되어 실전에서 망가지는 포인트를 놓치기 쉽고, 후자는 느리고 망가지기 쉬운 테스트군이 되기 쉽습니다.
실무에서 봐야 할 축은 조금 더 분명합니다.

  • 확인하고 싶은 것은 자기들의 로직인가, 밖과의 연결인가
  • in-memory의 fake로 바꿔도 의미가 떨어지지 않는가
  • DB / 파일 / HTTP / DI / 설정 / 프레임워크 / OS의 동작이 본론인가
  • 대량의 입력 패턴을 고속으로 돌리고 싶은가

이 4가지가 보이면 유닛 테스트와 결합 테스트의 경계는 꽤 긋기 쉬워집니다.

이 글에서는 2026년 3월 시점에 참조할 수 있는 Microsoft Learn과 Martin Fowler의 공개 정보를 전제로, 유닛 테스트와 결합 테스트의 경계를 실무 쪽으로 정리합니다.123

1. 먼저 결론

꽤 거칠게, 하지만 실무에서 쓰기 쉽게 말하면 이렇습니다.

  1. 순수 로직은 유닛 테스트
  2. 접속·배선·변환·환경 차이는 결합 테스트
  3. 어느 쪽이든 검증할 수 있다면 우선 유닛 테스트
  4. 결합 테스트는 넓고 무겁게 하기보다 경계를 좁게 한정

한마디로 하면 유닛 테스트는 「판단의 테스트」, 결합 테스트는 「접속의 테스트」입니다.

금액 계산, 상태 전이, 입력 검증, 승인 조건, 예외의 분류처럼 외부 자원 없이 의미가 완결되는 것은 유닛 테스트로 치우치는 편이 빠르고 망가지기 어렵고 입력 패턴도 두껍게 돌릴 수 있습니다.
한편으로 SQL 실행, JSON / CSV의 직렬화, 라우팅, 모델 바인딩, DI 등록, 파일 락, 권한, COM 등록, 32bit / 64bit, STA / MTA 같은 「연결된 순간에 배신하는 것」은 결합 테스트 쪽에 두는 편이 안전합니다.

Microsoft Learn의 Integration tests in ASP.NET Core에서도 통합 테스트는 중요한 인프라 시나리오로 좁히고, 유닛 테스트로 끝낼 수 있다면 그쪽을 고르도록 정리되어 있습니다.

2. 이 글에서 말하는 유닛 테스트와 결합 테스트

여기서는 다음과 같이 정리합니다.

레벨 무엇을 확인하는가 전형적인 구성
유닛 테스트 분리된 1개 책임의 올바름 fake / mock / stub을 쓰고 외부 자원을 끊는다
결합 테스트 복수 컴포넌트의 접속과 인프라나 프레임워크를 포함한 동작 실 DB, 실 파일, 실 serializer, 실 host, 실 pipeline 등
E2E / 기능 테스트 앱 전체의 사용자 플로 배포된 앱, 복수 서비스, 실 브라우저나 실 프로세스

.NET의 유닛 테스트 정리에서는 좋은 유닛 테스트는 fast / isolated / repeatable이며, 파일 시스템이나 DB 같은 외부 요인에 의존하지 않는 것으로 설명되어 있습니다. 자세한 것은 Unit testing best practices for .NET이 이해하기 쉽습니다.

또한 결합 테스트는 「별도 프로세스나 별도 서버를 반드시 쓰는 무거운 테스트」만 가리키는 것은 아닙니다.
같은 프로세스 내에서도 복수의 실 컴포넌트를 연결하고 프레임워크나 인프라의 실제 동작을 확인한다면 그것은 결합 테스트 쪽입니다.

예를 들어 ASP.NET Core의 controller action을 유닛 테스트할 때 대상은 action 본체의 판단으로 좁히고, routing, model binding, filters 같은 프레임워크 쪽의 상호 작용은 결합 테스트에서 다룬다는 구분이 공식에서도 제시되어 있습니다. 자세한 것은 Unit test controller logic in ASP.NET Core를 보면 정리하기 쉽습니다.

3. 한 장으로 보는 판단표

우선 가장 실무에서 쓰기 쉬운 표를 둡니다.

확인하고 싶은 것 주력으로 할 테스트 보충
금액 계산, 할인, 상태 전이, 입력 검증 유닛 테스트 입력 패턴을 두껍게 돌리고 싶다
예외의 분류, 에러 메시지 선택, 리트라이할지의 판단 유닛 테스트 실 I/O 없이 의미가 완결된다
Repository의 SQL / ORM 변환, transaction 결합 테스트 실 DB나 실 provider의 동작이 본론
JSON / XML / CSV의 serialize / deserialize 결합 테스트 wire format의 어긋남은 fake로는 찾기 어렵다
라우팅, 모델 바인딩, 필터, middleware 결합 테스트 프레임워크와의 접속 확인
WPF / WinForms의 ViewModel이나 Presenter의 상태 전이 유닛 테스트 UI를 올리지 않아도 의미가 있다
실제의 Binding, Dispatcher, control lifecycle, message loop 결합 테스트 or UI 테스트 프레임워크와 스레드의 동작이 주제
파일 패스, 권한, 락, 공유 폴더, 개행 코드, 문자 코드 결합 테스트 OS와 파일 시스템의 실 동작이 필요
COM 등록, 32bit / 64bit, STA / MTA, DLL 로드 결합 테스트 환경 차이와 프로세스 경계가 주제
앱 전체의 기동, 주요 유스케이스의 통과 확인 E2E / 스모크 개수는 적어도 된다

보는 방식의 요령은 어느 테스트가 「운영에서 망가지는 이유」에 가장 가까운가입니다.
코드의 둘 장소가 아니라 줄이고 싶은 불확실성으로 정하는 편이 흔들리지 않습니다.

4. 유닛 테스트에 가져야 할 것

유닛 테스트에 맞는 것은 외계를 제거해도 의미가 남는 책임입니다.

예를 들어 다음과 같은 것입니다.

  • 업무 규칙
  • 분기
  • 상태 전이
  • 입력 검증
  • 에러 분류
  • 리트라이 방침의 결정
  • ViewModel / Presenter의 상태 변화
  • 변환 로직 그 자체

특히 조합이 많은 것일수록 유닛 테스트로 치우치는 가치가 높습니다.

예를 들어,

  • 쿠폰 있음 / 없음
  • 재고 있음 / 없음
  • 초회 주문 / 재주문
  • 관리자 / 일반 사용자
  • 정상값 / 경계값 / 부정값

처럼 분기 조건이 늘수록 결합 테스트로 전부 돌리는 것은 무거워집니다.
여기는 유닛 테스트로 세세하게 쪼개는 편이 합리적입니다.

또한 유닛 테스트에서는 외부 요인을 제어 가능하게 해 두는 것이 중요합니다.

  • 현재 시각은 주입한다
  • GUID나 난수는 바꿀 수 있게 한다
  • sleep으로 기다리지 않는다
  • 실 DB나 실 파일을 건드리지 않는다
  • 실 네트워크로 나가지 않는다

이쯤이 지켜지면 테스트는 꽤 안정됩니다.

4.1. 유닛 테스트에서 mock이 너무 늘어날 때

유닛 테스트를 쓰려고 했더니,

  • mock이 7개 필요
  • setup이 길다
  • arrange가 본체보다 길다
  • 무엇을 확인하고 싶은지 안 보인다

가 된다면 대체로 다음 중 하나입니다.

  1. 그 클래스가 책임 과다
  2. 정말은 결합 테스트에서 확인해야 할 배선을 유닛 테스트에 밀어 넣고 있다

mock은 외계를 끊기 위한 도구이지, 실물과의 접속이 올바르다는 것을 증명하는 도구가 아닙니다.
여기를 잘못 잡으면 「전부 green인데 실전에서 떨어진다」가 일어나기 쉬워집니다.

5. 결합 테스트로 올릴 4가지 경계

결합 테스트로 올려야 할 장소는 대체로 포맷, 배선, 환경, 시간의 4가지로 정리할 수 있습니다.

5.1. 포맷의 경계

여기서 말하는 포맷은 다음 같은 것입니다.

  • JSON / XML / CSV
  • DB의 schema와 mapping
  • nullable / precision / timezone
  • enum이나 날짜의 시리얼라이즈
  • 문자 코드나 BOM
  • 개행 코드

Martin Fowler도 serialize / deserialize가 들어가는 경계는 결합 테스트 후보로 들고 있습니다. 자세한 것은 The Practical Test Pyramid가 참고가 됩니다.

예를 들어,

  • DTO를 JSON으로 했더니 필드 이름이 달랐다
  • CSV의 인용 부호나 개행이 망가졌다
  • decimal이 반올림되었다
  • DB에서 DateTimeOffset의 취급이 어긋났다
  • null과 빈 문자열의 취급이 상정과 달랐다

같은 불량은 유닛 테스트만으로는 빠지기 쉽습니다.

5.2. 배선의 경계

배선의 경계는 예를 들어 다음입니다.

  • DI 등록
  • 설정의 bind
  • 라우팅
  • 모델 바인딩
  • 필터
  • middleware
  • host의 기동
  • 이벤트 배선
  • WPF의 Binding이나 command 접속

여기는 「자기 함수가 올바른가」가 아니라 복수의 실 부품이 올바르게 연결되어 있는가가 본론입니다.

ASP.NET Core에서는 controller action의 유닛 테스트는 action의 판단으로 좁히고, routing이나 model binding, filters는 결합 테스트 쪽에서 보는 정리가 공식에 있습니다.
Web이 아니어도 사고방식은 같고, 데스크톱 앱에서도 ViewModel의 상태 전이는 유닛 테스트, 실제의 XAML Binding이나 Dispatcher를 포함하는 동작은 결합 테스트 쪽입니다.

5.3. 환경의 경계

Windows 개발에서는 여기가 꽤 중요합니다.

  • 파일 권한
  • 공유 폴더
  • 파일 락
  • 임시 파일에서의 rename
  • 관리자 권한
  • 서비스 기동 권한
  • COM 등록
  • 32bit / 64bit
  • STA / MTA
  • DLL의 로드 위치

이쯤은 OS나 실행 환경의 조건 그 자체가 주역입니다.
in-memory fake로는 의미가 꽤 떨어지므로 결합 테스트로 잡는 편이 안전합니다.

특히 기존 Windows 소프트웨어나 COM / ActiveX를 포함한 구성에서는 로직보다 먼저 등록, bitness, 스레드 모델, 권한에서 구르는 일이 평범하게 있습니다.
이런 실패는 유닛 테스트가 아니라 환경을 포함한 결합 테스트가 줍는 영역입니다.

5.4. 시간의 경계

또 하나 놓치기 쉬운 것이 시간과 병행성입니다.

  • timeout
  • cancellation
  • retry의 실 동작
  • timer 구동
  • 백그라운드 처리의 정지
  • race condition
  • shutdown 시의 종료 순서

여기서 중요한 것은 판단과 실 동작을 나누는 것입니다.

예를 들어,

  • 몇 번까지 retry할 것인가
  • 어느 예외를 retry 대상으로 할 것인가

는 유닛 테스트로 충분합니다.
한편으로,

  • 실제로 timeout이 듣는가
  • cancellation이 전파되는가
  • timer와 비동기 처리가 충돌했을 때 망가지지 않는가
  • 종료 시에 핸들이나 태스크가 깔끔하게 닫히는가

는 결합 테스트 쪽입니다.

6. 흔한 판단 실수

6.1. Repository를 mock해서 만족해 버린다

Repository 주변을 전부 mock으로 통과시켜도,

  • SQL이 올바른가
  • transaction이 듣는가
  • schema와 일치하고 있는가
  • mapping이 어긋나지 않는가
  • 문자 코드나 precision이 망가지지 않는가

는 모릅니다.

Repository는 로직의 테스트 대상이라기보다 경계의 접속점인 경우가 많습니다.
그 경우는 유닛 테스트보다 결합 테스트의 비중을 올리는 편이 실태에 맞습니다.

6.2. Controller / Endpoint의 유닛 테스트에서 프레임워크까지 보려고 한다

controller action의 유닛 테스트에서 보고 싶은 것은,

  • 조건 분기
  • 반환값의 선택
  • 의존 서비스의 호출 구분

정도입니다.

한편으로,

  • route가 맞는가
  • model binding이 통과하는가
  • filter가 듣는가
  • middleware를 통과한 결과 어떻게 보이는가

는 결합 테스트 쪽입니다.
여기를 섞으면 무엇이 망가졌는지 알기 어려워집니다.

6.3. 결합 테스트에서 입력 패턴을 총당한다

결합 테스트는 실물에 가까운 만큼 아무래도 느려집니다.
그래서 분기의 총당은 유닛 테스트, 경계의 대표 케이스는 결합 테스트로 나누는 편이 이득입니다.

Microsoft Learn의 통합 테스트 해설에서도 DB나 파일 시스템에 대해서는 전 패턴을 결합 테스트로 돌리는 것이 아니라 read / write / update / delete 같은 대표적인 시나리오로 좁히는 방향이 권장됩니다.

6.4. CI에서 외부 서비스의 운영계를 그대로 두드린다

이것은 피하는 편이 안전합니다.

결합 테스트는 「실물다움」이 중요하지만 그렇다고 매번 운영 SaaS나 운영 API를 두드릴 필요는 없습니다.
Fowler도 외부 서비스는 로컬에 세우고, fake를 두거나 전용의 test instance를 쓰는 방향을 권장합니다.

실무에서는,

  • 로컬 DB
  • 임시 디렉토리
  • test host
  • 전용의 test environment
  • 계약을 고정한 fake service

의 조합이 다루기 쉽습니다.

7. 실무에서의 추천 구성

비율에 절대의 정답은 없습니다.
다만 꽤 범용적으로 쓸 수 있는 것은 다음 3층입니다.

주력 무엇을 둘까
코어 층 유닛 테스트를 두껍게 업무 규칙, 상태 전이, 입력 검증, 에러 분류
경계 층 좁은 결합 테스트를 둔다 DB, 파일, HTTP, serializer, DI, 설정, COM, 권한
전체 층 소수의 스모크 / E2E 기동 확인, 주요 플로, 중대 장애의 재발 방지

감각적으로는 수로 두꺼워지는 것은 유닛 테스트, 경계의 짙음으로 두꺼워지는 것은 결합 테스트입니다.

추천하는 진행 방식은 이렇습니다.

  1. 우선 앱의 경계를 열거한다
  2. 로직을 외계에서 끊을 수 있는 형태로 치우친다
  3. 경계마다 「최소 1개의 happy path」와 「대표적인 failure path」를 둔다
  4. 전체의 통과는 개수를 좁힌다
  5. 버그가 나오면 그 버그를 최소 비용으로 재현할 수 있는 층에 테스트를 추가한다

마지막 5가 중요합니다.

  • 규칙의 오류라면 유닛 테스트를 추가한다
  • SQL / binding / 설정 / 권한 / 등록의 오류라면 결합 테스트를 추가한다
  • 기동이나 배포를 포함하는 장애라면 스모크나 E2E를 추가한다

이 늘리는 방식을 하면 테스트의 책임이 흔들리기 어려워집니다.

8. 헷갈릴 때 마지막에 보는 5가지 질문

마지막으로 헷갈릴 때의 체크용으로 5가지 질문으로 정리합니다.

  1. in-memory의 fake로 바꿔도 확인하고 싶은 의미가 남는가
    • 남는다면 유닛 테스트 쪽입니다.
  2. 망가졌을 때 의심하는 것은 로직이 아니라 접속이나 설정이 아닌가
    • 그렇다면 결합 테스트 쪽입니다.
  3. DB / 파일 / serializer / DI / route / model binding / OS / 권한 / bitness / thread가 주제가 아닌가
    • 그렇다면 결합 테스트 쪽입니다.
  4. 대량의 입력 패턴을 고속으로 돌리고 싶은가
    • 그렇다면 유닛 테스트 쪽입니다.
  5. 그 테스트가 떨어졌을 때 무엇을 고치면 되는지 바로 알 수 있는가
    • 알 수 없다면 테스트의 층이 섞여 있습니다.

이 5가지 질문으로 정리하면 「어쩐지 실물에 가까우니까 결합 테스트」, 「어쩐지 빠르니까 유닛 테스트」라는 거친 결정 방식을 피하기 쉬워집니다.

9. 정리

유닛 테스트와 결합 테스트의 경계는 코드의 둘 장소가 아니라 어떤 불확실성을 줄이고 싶은가로 정하는 것이 가장 실무적입니다.

요점을 정리하면 다음과 같이 됩니다.

  • 유닛 테스트는 판단의 테스트
  • 결합 테스트는 접속의 테스트
  • 분기의 총당은 유닛 테스트
  • 포맷, 배선, 환경, 시간은 결합 테스트
  • 전체의 통과 확인은 소수의 스모크 / E2E로 잡는다

가장 피하고 싶은 것은,

  • mock으로 실물과의 접속까지 증명한 기분이 된다
  • 결합 테스트에서 전 분기를 돌리려 한다
  • 유닛 테스트와 결합 테스트의 책임을 섞는다

3가지입니다.

헷갈린다면 그 불량은 「판단」이 망가지는 것인가, 「접속」이 망가지는 것인가를 먼저 보세요.
이 1가지 질문으로 꽤 많은 케이스는 정리할 수 있습니다.

10. 관련 기사

11. 참고 자료

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기