C# 아키텍처 마스터리 — 클린 아키텍처에서의 테스트 전략 (.NET) (Part 7)
Source: Dev.to
What You Test
전통적인 시스템은 외부에서 내부로 테스트합니다.
클린 아키텍처는 내부에서 외부로 테스트합니다.
Priority order
- 도메인 로직
- 애플리케이션 유스케이스
- 인프라스트럭처 통합
- 웹 / API 레이어
이 순서는 의도된 것입니다.
Classic testing pyramid
- 유닛 테스트 (많음)
- 통합 테스트 (일부)
- 엔드‑투‑엔드 테스트 (드물게)
Clean Architecture sharpens it
| Layer | Coverage |
|---|---|
| Domain Tests | ███████████ |
| Application Tests | ████████ |
| Integration Tests | ████ |
| API / E2E Tests | ██ |
비즈니스 규칙이 가장 높은 신뢰를 받아야 합니다.
Business rules
- 불변 조건
- 계산 로직
- 상태 전이
Domain unit test
// ✅ Domain unit test
[Fact]
public void Order_Cannot_Be_Created_With_Negative_Total()
{
var act = () => new Order(-10);
act.Should().Throw();
}
도메인 테스트에 목(mock)이 필요하다면 뭔가 잘못된 것입니다.
Application test with mocks
// ✅ Application test with mocks
[Fact]
public async Task CreateOrder_Saves_Order_And_Sends_Notification()
{
var repo = Substitute.For<IOrderRepository>();
var notifier = Substitute.For<INotifier>();
var useCase = new CreateOrderUseCase(repo, notifier);
await useCase.Execute(new CreateOrderCommand(100));
await repo.Received(1).SaveAsync(Arg.Any<Order>());
await notifier.Received(1).NotifyAsync(Arg.Any<Notification>());
}
목은 경계에만 존재해야 합니다.
Infrastructure tests
인프라스트럭처 테스트는 “이것이 실제로 동작하는가?” 라는 질문에 답합니다.
- EF Core 매핑
- SQL 쿼리
- 외부 API
- 파일 시스템
- 메시지 브로커
EF Core integration test
// ✅ EF Core integration test
using var db = CreateRealDbContext();
var repo = new OrderRepository(db);
await repo.SaveAsync(order);
var saved = await db.Orders.FindAsync(order.Id);
saved.Should().NotBeNull();
이 테스트들은 느리지만, 적게, 그리고 필수적입니다.
API tests
API 테스트는 다음을 검증해야 합니다:
- 라우팅
- HTTP 계약
- 직렬화
- 인증 / 인가
비즈니스 규칙을 다시 테스트해서는 안 됩니다.
// ✅ API test
var response = await client.PostAsJsonAsync("/orders", request);
response.StatusCode.Should().Be(HttpStatusCode.OK);
API 테스트가 복잡하다면, 컨트롤러가 너무 무거운 것입니다.
Do not test
- 프레임워크 내부 구현
- EF Core 자체
- ASP.NET Core 라우팅 로직
- Microsoft 라이브러리
당신의 코드를 테스트하고, 그들의 코드는 테스트하지 마세요.
Warning signs
DbContext를 곳곳에서 목킹- 통합 테스트가 유닛 테스트를 대체
- HTTP 모델에 결합된 테스트
- 리팩터링 시 깨지는 테스트
- 방대한 테스트 설정 코드
대부분 경계 위반을 나타냅니다.
Testing is not about coverage
테스트는 다음을 위한 것입니다:
- 신뢰
- 변경 안전성
- 설계 피드백
좋은 테스트는 리팩터링을 지루하게 만듭니다.
Before shipping, ask
- 인프라 없이 비즈니스 규칙을 테스트했는가?
- 유스케이스에 집중된 유닛 테스트가 있는가?
- 실제 시스템을 대상으로 통합 테스트를 했는가?
- API 테스트가 얇은가?
- 테스트가 설계 결정을 이끄는가?
예라면, 당신의 아키텍처는 제대로 작동하고 있는 것입니다.
If testing feels painful
- 경계를 다시 검토
- 누수를 제거
- 책임을 단순화
훌륭한 테스트는 훌륭한 아키텍처의 부수 효과입니다.