C# 아키텍처 마스터리 — 클린 아키텍처에서의 테스트 전략 (.NET) (Part 7)

발행: (2025년 12월 24일 오전 05:57 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

What You Test

전통적인 시스템은 외부에서 내부로 테스트합니다.
클린 아키텍처는 내부에서 외부로 테스트합니다.

Priority order

  1. 도메인 로직
  2. 애플리케이션 유스케이스
  3. 인프라스트럭처 통합
  4. 웹 / API 레이어

이 순서는 의도된 것입니다.

Classic testing pyramid

  • 유닛 테스트 (많음)
  • 통합 테스트 (일부)
  • 엔드‑투‑엔드 테스트 (드물게)

Clean Architecture sharpens it

LayerCoverage
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

  • 경계를 다시 검토
  • 누수를 제거
  • 책임을 단순화

훌륭한 테스트는 훌륭한 아키텍처의 부수 효과입니다.

Back to Blog

관련 글

더 보기 »