도메인 경계와 영향 범위 CI: 작은 변경을 작게 검증하기
업무 단위로 도메인을 나누고, 바뀐 곳과 그에 의존하는 부분만 골라 테스트하며, 경로 필터로 건너뛴 테스트가 병합을 막는 함정을 피하는 검증 방법을 살펴본다.
개정 2판 · 최근 고침: 독자에게 보이는 문장과 용어를 다듬고 기존 근거 연결을 유지했다. · 고침 기록 보기
도메인 경계와 영향 범위를 계산하는 원칙을 설명한다. 도구와 기준 커밋은 팀의 CI 구성에 맞춰 정해야 한다. 아래 상점 사례는 실행하지 않은 가상 예시이며, 작업 시간이 몇 분 줄었는지와 같은 측정값은 제시하지 않는다.
변경 규모에 맞게 테스트를 줄이려면
저장소가 커지면 모든 테스트를 매번 돌리기가 버거워진다. 다시 테스트하고, 다시 빌드하고, 린트를 다시 실행하는 일이 쌓여 병합 대기 시간이 길어진다. 기다림이 길어지면 작은 수정도 미루게 되고, 미룬 수정이 쌓여 병합은 더 무거워진다.
변경된 부분만 테스트하도록 범위를 나눌 수 있다. 폴더마다 테스트를 연결하고 바뀌지 않은 곳은 건너뛰는 방식이다. 다만 설정이 허술하면 건너뛴 검사가 병합을 막을 수 있다. 로컬 테스트를 통과해도 CI와 보호 브랜치의 병합 조건은 충족하지 못하는 경우다. 테스트 범위를 나누는 기준과 함께 이런 설정 문제도 살펴봐야 한다.
업무와 도메인 모델을 기준으로 경계 정하기
마이크로서비스 안내는 비즈니스 기능을 중심으로 설계하라고 설명한다. 데이터 접근이나 메시징 같은 기술 계층보다 장바구니·결제·배송처럼 업무가 수행하는 기능을 기준으로 나눈다. 각 서비스의 결합도는 낮추고 내부 응집도는 높인다. 한 서비스를 수정할 때 다른 서비스를 함께 수정할 필요가 적을수록 결합도가 낮다.
도메인 주도 설계에서는 하나의 도메인 모델이 적용되는 범위를 바운디드 컨텍스트라고 부른다. 같은 용어도 컨텍스트마다 뜻이 달라질 수 있으므로, 각 범위에서 유비쿼터스 언어(공유 언어)를 정하고 컨텍스트 사이의 관계를 컨텍스트 맵에 기록한다. 폴더를 나누는 것만으로 이런 경계가 정해지지는 않는다. 각 폴더가 어떤 업무와 모델을 담당하는지 먼저 합의해야 한다.
이 절의 근거 S27

프로젝트 그래프와 커밋으로 영향 범위 계산하기
영향 범위를 계산하려면 변경된 파일을 확인할 수 있는 버전 기록과 프로젝트 그래프가 필요하다. 그래프에는 파일이 속한 프로젝트와 프로젝트 사이의 의존 관계가 담긴다. 변경된 프로젝트와 이에 의존하는 프로젝트를 찾아 빌드·테스트·린트 대상을 정한다. 대상이 적으면 계산량을 줄일 수 있지만, 거의 모든 프로젝트가 영향을 받는다면 전체 테스트를 실행하는 편이 낫다.
Nx의 affected 명령은 비교할 기준 커밋(base)과 대상 커밋(head)을 받는다. 로컬의 기본 base는 main 브랜치다. CI에서는 두 값을 직접 정해야 하며, 문서는 main 브랜치에서 CI가 마지막으로 성공한 커밋을 base로 권한다. 그 이후의 변경을 빠짐없이 포함하기 위해서다. 기준 커밋을 찾을 수 있도록 전체 Git 이력을 받아야 한다. GitHub에서는 마지막 성공 실행을 찾는 도구를 사용할 수 있고, 다른 CI에서도 해당 커밋을 변수로 전달할 수 있다.
이 절의 근거 S28
그림을 글로 읽기
금액 계산 변경에서 결제 도메인과 검색 도메인으로 향하는 화살표는 변경의 영향 방향이다. 이 경우 테스트 대상 프로젝트에는 결제와 검색이 모두 포함된다. 별도 사례인 화면 문구 변경에서는 결제 도메인으로만 영향이 전달되므로 결제만 테스트 대상이 된다. affected 명령은 base·head 커밋을 비교해 대상을 계산한다. 가상 상점 예시다.
경로 필터로 건너뛴 검사가 병합을 막는 경우
GitHub Actions의 경로 필터는 테스트 나누기의 또 다른 수단이다. push와 pull_request 이벤트에 paths나 paths-ignore를 걸어 바뀐 경로에 따라 워크플로의 실행 여부를 결정한다. 브랜치 필터와 함께 쓰면 두 조건을 모두 만족할 때만 워크플로가 실행된다.
브랜치 필터나 경로 필터, 커밋 메시지 때문에 워크플로를 건너뛰면 해당 상태 검사가 Pending으로 남을 수 있다고 GitHub 문서는 설명한다. 그 검사가 보호 브랜치의 필수 조건이면 결과가 보고되지 않아 병합이 막힌다. 테스트 범위를 줄이려던 설정이 병합을 중단시킬 수 있는 것이다. 이를 어떻게 피할지는 팀의 CI 구성에 맞춰 결정해야 한다.
이 절의 근거 S29
| 방법 | 선택 기준 | 확인할 사항 |
|---|---|---|
| 경로 필터 | 변경된 경로와 브랜치가 설정한 조건을 모두 충족하는지 | 건너뛴 검사가 Pending으로 남아 병합을 막는지 |
| 영향 범위 계산 | 프로젝트 그래프와 base·head 커밋 | 대부분의 프로젝트가 포함되면 전체 테스트를 검토 |
| 건너뛴 검사 | 필수 검사 결과가 보고되지 않으면 병합 불가 | 경로 필터와 필수 검사 설정을 함께 점검 |
표를 글로 읽기
경로 필터는 변경 경로와 브랜치 조건을 사용하고, 영향 범위 계산은 프로젝트 그래프와 base·head 커밋을 사용한다. 세 번째 행은 건너뛴 필수 검사가 Pending 상태로 남아 병합을 막는지 확인하도록 설명한다.
의존성 캐시와 빌드 산출물
GitHub가 제공하는 러너(GitHub-hosted runners)는 매번 초기화된 환경에서 시작하므로 의존성을 매번 다시 받는다. 의존성 캐시는 자주 쓰는 파일을 GitHub에 저장해 다시 만드는 시간을 아끼는 수단이다. 산출물과는 쓰임이 다르다. 캐시는 실행 사이에 다시 쓰는 재료용이고, 산출물은 실행이 끝난 뒤에 보거나 다음 일에 넘기는 결과물용이다.
복원한 캐시도 신뢰할 수 없는 입력으로 다뤄야 한다. 캐시는 브랜치나 태그 단위로 공유되며, 신뢰할 수 없는 이벤트로 시작한 워크플로에서도 민감한 캐시에 접근할 수 있기 때문이다. 시크릿은 캐시에 넣지 않는다. 캐시가 실행 시간을 줄여주더라도 테스트를 대신하지는 못한다.
이 절의 근거 S30
TDD와 통합 테스트의 역할
테스트 주도 개발은 다음에 구현할 기능의 테스트를 먼저 작성하고(레드), 테스트를 통과할 때까지 코드를 구현한 뒤(그린), 새 코드와 기존 코드를 다듬는 과정(리팩터)을 반복한다. 테스트를 먼저 쓰면 코드를 호출하는 쪽의 인터페이스부터 생각하게 되므로 인터페이스와 구현을 분리하는 데 도움이 된다. 단위 테스트는 개별 모듈에 빠른 피드백을 주지만, 도메인 사이에 주고받는 데이터와 호출 방식이 약속대로 동작하는지는 별도로 확인해야 한다.
단위 테스트는 개별 모듈의 동작을 확인하고, 통합 테스트와 계약 테스트는 모듈이나 도메인이 함께 동작할 때의 계약을 확인한다. 컨텍스트 맵은 컨텍스트 사이의 관계를 기록하며, 영향 범위 계산은 변경이 어떤 프로젝트에 영향을 주는지 판단한다. 이는 여러 출처를 종합한 편집자의 구분이다. 로컬 단위 테스트는 수정할 때마다, 영향 범위 테스트는 풀 리퀘스트마다, 전체 통합 테스트는 정해진 주기로 실행하는 구성을 생각할 수 있다. 배치 방법은 팀이 정하되 각 검증 역할을 생략하는 것은 권하지 않는다. 특히 공유 모듈을 수정했다면 로컬 테스트 통과만으로 병합을 결정해서는 안 된다.
| 검증 종류 | 실행 시점 | 확인할 내용 |
|---|---|---|
| 로컬 단위 테스트 | 코드를 수정할 때마다 | 모듈이 의도대로 동작하는가 |
| 영향 범위 테스트 | 풀 리퀘스트마다 | 변경이 영향을 주는 프로젝트가 동작하는가 |
| 전체 통합 테스트 | 정해진 주기에 | 모듈을 결합해도 동작하는가 |
| 병합 상태 점검 | 주기적으로 | 건너뛴 필수 검사가 병합을 막는가 |
표를 글로 읽기
로컬 단위 테스트는 코드를 수정할 때마다 개별 모듈의 동작을 확인한다. 영향 범위 테스트는 풀 리퀘스트마다 변경이 영향을 주는 프로젝트를 검사한다. 전체 통합 테스트는 정해진 주기에 모듈을 결합한 상태를 확인하고, 병합 상태 점검은 Pending으로 남은 필수 검사를 확인한다.
가상 예시: 상점 저장소의 변경 범위
아래는 실제로 실행하지 않은 가상 예시다. 상점 저장소에 결제와 검색 도메인이 있고, 둘이 금액 계산 모듈을 함께 사용한다고 가정하자. 금액 계산을 수정하면 결제와 검색 모두 테스트 대상에 포함된다. 결제 화면의 문구만 수정하면 결제만 포함된다. 이 범위는 폴더 이름이 아니라 프로젝트 그래프의 의존 관계를 기준으로 계산한다.
CI에서는 최근 성공 커밋을 base로, 이번 풀 리퀘스트(pull request, PR)의 최신 커밋을 head로 정해 affected 명령에 전달한다. 아래 셸 코드는 발췌본이며 두 변수는 팀의 CI에서 설정해야 한다. 전체 워크플로가 아니므로 이 코드만 붙여 넣어서는 실행되지 않는다. origin/main 같은 브랜치 이름으로 비교를 시작할 수도 있지만, 권장 기준은 main에서 CI가 마지막으로 성공한 커밋이다. 경로 필터를 사용한다면 건너뛴 검사가 보호 브랜치의 필수 조건인지도 확인한다. 이 예시는 시간 단축 측정값을 제시하지 않는다. 실제 효과는 같은 풀 리퀘스트 집합으로 도입 전후의 대기 시간과 계산량을 비교해 확인할 수 있다.
# 팀 CI가 두 변수를 준다. 전체 흐름이 아니라 발췌이다.
: "${NX_BASE:?set NX_BASE to last successful main commit SHA}"
: "${NX_HEAD:?set NX_HEAD to PR head SHA}"
nx affected -t test --base="$NX_BASE" --head="$NX_HEAD"실패 양상과 한계
프로젝트 그래프에 없는 의존 관계는 영향 범위 계산에서 빠진다. 그래프에 기록되지 않은 동적 import, 수동 배포 스크립트, 문서로만 남은 계약은 변경돼도 테스트 대상으로 선택되지 않을 수 있다. 반대로 공유 모듈을 수정하면 테스트 대상이 많아져 실행 시간을 줄이기 어려울 수 있다. 이때는 전체 테스트를 실행하는 편이 낫다.
기준 커밋을 잘못 정하면 테스트 대상도 달라진다. 필요보다 오래된 커밋을 기준으로 삼으면 이미 확인한 변경까지 포함하고, 너무 최근의 커밋을 선택하면 확인해야 할 변경을 빠뜨린다. 얕은 클론 때문에 기준 커밋을 찾지 못하는 경우도 있다. 로컬 테스트를 통과했더라도 CI의 경로 필터와 보호 브랜치 조건 때문에 병합이 중단될 수 있으므로, 두 환경의 차이를 확인해야 한다.
- 대상이 너무 많으면 기준 시점 확인
기준 커밋이 너무 오래됐다면 불필요한 변경까지 포함된다. main에서 CI가 마지막으로 성공한 커밋을 기준으로 삼는다.
- 변경이 빠지면 비교 범위 확인
기준 커밋이 너무 최근이면 필요한 변경이 빠진다. 마지막 성공 실행 이후의 변경을 모두 포함하도록 기준을 정한다.
- 기준 커밋을 찾지 못하면 이력 확인
얕은 클론에 기준 커밋이 없을 수 있다. 전체 Git 이력을 받고, GitHub에서는 마지막 성공 실행을 찾는 도구도 활용한다.
단계를 글로 읽기
기준 커밋을 점검하는 세 단계. 대상이 너무 많으면 기준이 오래됐는지, 변경이 빠지면 기준이 너무 최근인지 확인한다. 기준 커밋을 찾지 못하면 얕은 클론 여부를 확인하고 전체 Git 이력을 받는다.
대기 시간과 누락된 오류 확인하기
병합 대기 시간, 실행당 계산량, 건너뛴 검사의 보고 여부를 측정한다. 영향 범위에서 빠진 프로젝트에 오류가 생긴 횟수도 따로 기록한다. 이런 오류가 늘면 프로젝트 그래프에 누락된 의존 관계가 있는지 확인하고 도메인 경계도 다시 살핀다.
필수 검사가 Pending 상태로 남아 병합이 중단되는지도 주기적으로 확인한다. 같은 문제가 반복되면 경로 필터와 보호 브랜치의 필수 검사 설정을 함께 점검한다. 테스트 범위를 줄인 뒤에도 필요한 검사 결과가 보고되는지 확인해야 한다.
테스트 범위를 줄인 뒤에도 확인할 것
업무와 도메인 모델을 기준으로 경계를 정한 뒤, 프로젝트 그래프와 base·head 커밋으로 변경의 영향 범위를 계산한다. 로컬 단위 테스트로 빠르게 피드백을 받되, CI에서 검사를 건너뛸 때는 보호 브랜치의 필수 조건도 함께 확인해야 한다.
도구와 기준 커밋은 팀의 CI 구성에 맞춰 정한다. 병합 조건을 설정하는 방법은 「검토 게이트와 병합」에서, 평가 결과를 해석하는 기준은 「AI 평가」에서 이어서 살펴볼 수 있다.
근거 출처 5건
- [1] 비즈니스 기능을 기준으로 나누는 설계, 결합도와 응집도, 바운디드 컨텍스트·유비쿼터스 언어·컨텍스트 맵
Use Domain Analysis to Model Microservices (외부)
learn.microsoft.com (Azure Architecture Center) - [2] 프로젝트 그래프로 테스트 대상을 선택하는 방법, base·head 설정, CI의 최근 성공 커밋과 전체 Git 이력의 사용
Nx: Run Only Tasks Affected by a PR (외부)
nx.dev - [3] 경로 필터로 건너뛴 검사가 Pending 상태로 남아 병합을 막는 경우
GitHub Actions: Workflow syntax for GitHub Actions (외부)
docs.github.com - [4] 캐시와 빌드 산출물의 차이, 복원한 캐시를 사용할 때의 주의점
GitHub Actions: Dependency caching (외부)
docs.github.com - [5] 레드-그린-리팩터 과정과 테스트를 먼저 작성하면서 인터페이스를 설계하는 방법
Test Driven Development (외부)
martinfowler.com · 2023-12-11
함께 읽기
- AI 평가: 무엇을 어떻게 측정할 것인가 (개념 뼈대 · 함께 읽기: AI 평가)
- AI 평가의 발전: 벤치마크에서 실제 개발 과제까지 (발전 과정 · AI 평가의 발전 과정)
- SWE-bench 공개 (2023) (사건 기록 · SWE-bench 연구 소개)
- 검토 게이트와 병합: 기록에서 머지 큐까지 (개념 뼈대 · 함께 읽기: 검토 게이트와 병합)
- 검토 게이트와 병합: 기록에서 머지 큐까지 (개념 뼈대 · 이 글을 가리킴)