본문으로 건너뛰기

테스트 라이브러리 사양

Functorium 프레임워크가 제공하는 테스트 유틸리티 라이브러리의 공개 API 사양입니다. 설계 원칙과 사용 패턴은 Functorium.Testing 라이브러리 가이드를 참조하십시오.

타입네임스페이스설명
FinTFactoryArrangements.EffectsFinT<IO, T> Mock 반환값 생성 헬퍼
HostTestFixture<TProgram>Arrangements.Hosting호스트 통합 테스트 Fixture
QuartzTestFixture<TProgram>Arrangements.ScheduledJobsQuartz Job 통합 테스트 Fixture
LogTestContextArrangements.LoggingSerilog 기반 인메모리 로그 캡처 컨텍스트
StructuredTestLogger<T>Arrangements.Logging구조화된 로깅 지원 테스트 로거
SourceGeneratorTestRunnerActions.SourceGeneratorsIIncrementalGenerator 테스트 실행기
DomainErrorAssertionsAssertions.Errors도메인 에러 검증 확장 메서드
ApplicationErrorAssertionsAssertions.Errors애플리케이션 에러 검증 확장 메서드
AdapterErrorAssertionsAssertions.Errors어댑터 에러 검증 확장 메서드
ErrorCodeAssertionsAssertions.Errors범용 에러 코드 검증 확장 메서드
ErrorCodeExceptionalAssertionsAssertions.Errors예외 기반 에러 검증 확장 메서드
ClassValidatorAssertions.ArchitectureRules클래스 수준 아키텍처 규칙 Fluent API
MethodValidatorAssertions.ArchitectureRules메서드 수준 아키텍처 규칙 Fluent API
InterfaceValidatorAssertions.ArchitectureRules인터페이스 수준 아키텍처 규칙 Fluent API
DomainArchitectureTestSuiteAssertions.ArchitectureRules.Suites도메인 레이어 아키텍처 테스트 스위트
ApplicationArchitectureTestSuiteAssertions.ArchitectureRules.Suites애플리케이션 레이어 아키텍처 테스트 스위트

모든 네임스페이스는 Functorium.Testing 접두사를 가집니다.


Application 레이어 Usecase 테스트에서 Port/Adapter의 FinT<IO, T> 반환값을 생성하는 정적 헬퍼입니다.

public static class FinTFactory
{
public static FinT<IO, T> Succ<T>(T value);
public static FinT<IO, T> Fail<T>(Error error);
}
메서드반환 타입설명
Succ<T>(T value)FinT<IO, T>성공 값을 감싼 FinT 반환
Fail<T>(Error error)FinT<IO, T>실패 에러를 감싼 FinT 반환

호스트 테스트 (HostTestFixture<T>, QuartzTestFixture<T>)

섹션 제목: “호스트 테스트 (HostTestFixture<T>, QuartzTestFixture<T>)”

WebApplicationFactory를 사용하여 전체 DI 설정을 재사용하는 호스트 통합 테스트 Fixture입니다. 기본 환경은 "Test"이며, appsettings.Test.json을 자동으로 로드합니다.

public class HostTestFixture<TProgram> : IAsyncLifetime where TProgram : class
{
protected virtual string EnvironmentName => "Test";
public IServiceProvider Services { get; }
public HttpClient Client { get; }
protected virtual string GetTestProjectPath();
protected virtual void ConfigureHost(IWebHostBuilder builder);
}
멤버타입설명
EnvironmentNamestring (virtual)사용할 환경 이름 (기본값: "Test")
ServicesIServiceProviderDI 컨테이너 접근
ClientHttpClient테스트용 HTTP 클라이언트
GetTestProjectPath()string (virtual)테스트 프로젝트 경로 (기본: bin/ 기준 3단계 상위)
ConfigureHost(builder)void (virtual)Host 추가 설정 확장 포인트

설정 파일 로드 순서: TProgram 프로젝트의 appsettings.json (기본) -> 테스트 프로젝트의 appsettings.json (덮어씀)

Quartz.NET Job 통합 테스트를 위한 Fixture입니다. HostTestFixture와 동일한 환경/설정 구조이며, 스케줄러와 Job 리스너를 추가로 관리합니다.

public class QuartzTestFixture<TProgram> : IAsyncLifetime where TProgram : class
{
protected virtual string EnvironmentName => "Test";
public IServiceProvider Services { get; }
public JobCompletionListener JobListener { get; }
public IScheduler Scheduler { get; }
protected virtual void ConfigureWebHost(IWebHostBuilder builder);
public Task<JobExecutionResult> ExecuteJobOnceAsync<TJob>(TimeSpan timeout)
where TJob : IJob;
public Task<JobExecutionResult> ExecuteJobOnceAsync<TJob>(
string jobName, string jobGroup, TimeSpan timeout) where TJob : IJob;
}
멤버설명
JobListenerJob 완료 감지 리스너 (JobCompletionListener)
SchedulerQuartz 스케줄러 인스턴스
ExecuteJobOnceAsync<TJob>(timeout)Job을 즉시 1회 실행하고 완료 대기 (이름/그룹 자동 추출)
ExecuteJobOnceAsync<TJob>(name, group, timeout)Job을 즉시 1회 실행하고 완료 대기 (이름/그룹 명시)
public sealed record JobExecutionResult(
string JobName, bool Success, object? Result,
JobExecutionException? Exception, TimeSpan ExecutionTime);

Success는 예외 없이 완료되면 true입니다.


로그 테스트 (LogTestContext, StructuredTestLogger)

섹션 제목: “로그 테스트 (LogTestContext, StructuredTestLogger)”

Serilog 기반 인메모리 로그 캡처와 검증을 위한 컨텍스트 클래스입니다. IDisposable을 구현합니다.

public sealed class LogTestContext : IDisposable
{
public LogTestContext();
public LogTestContext(LogEventLevel minimumLevel);
public LogTestContext(LogEventLevel minimumLevel, bool enrichFromLogContext);
public IReadOnlyList<LogEvent> LogEvents { get; }
public int LogCount { get; }
public ILogger<T> CreateLogger<T>();
public LogEvent GetFirstLog();
public LogEvent GetSecondLog();
public LogEvent GetLogAt(int index);
public IReadOnlyList<LogEvent> GetLogsByLevel(LogEventLevel level);
public object ExtractFirstLogData();
public object ExtractSecondLogData();
public object ExtractLogDataAt(int index);
public IEnumerable<object> ExtractAllLogData();
public void Clear();
}

생성자:

생성자설명
LogTestContext()기본 최소 레벨(Debug)로 초기화
LogTestContext(minimumLevel)지정된 최소 로그 레벨로 초기화
LogTestContext(minimumLevel, enrichFromLogContext)최소 레벨 + LogContext enrichment 옵션. ctx.* 필드를 캡처하려면 true

주요 메서드:

메서드반환 타입설명
CreateLogger<T>()ILogger<T>구조화된 테스트 로거 생성
GetFirstLog() / GetSecondLog()LogEvent첫 번째/두 번째 로그 이벤트
GetLogAt(index)LogEvent지정 인덱스의 로그 이벤트
GetLogsByLevel(level)IReadOnlyList<LogEvent>지정 레벨의 모든 로그 이벤트
ExtractFirstLogData() / ExtractSecondLogData()objectVerify 스냅샷용 익명 객체 추출
ExtractAllLogData()IEnumerable<object>모든 로그를 익명 객체 목록으로 추출

LoggerMessage 어트리뷰트로 생성된 메서드의 구조화된 로깅을 올바르게 처리하는 ILogger<T> 구현체입니다. stateIReadOnlyList<KeyValuePair<string, object?>> 형태인 경우 OriginalFormat과 속성명을 파싱하여 Serilog LogEvent를 직접 생성합니다.

public class StructuredTestLogger<T> : ILogger<T>
{
public StructuredTestLogger(Serilog.ILogger serilogLogger);
public bool IsEnabled(LogLevel logLevel); // 항상 true
}

소스 생성기 테스트 (SourceGeneratorTestRunner)

섹션 제목: “소스 생성기 테스트 (SourceGeneratorTestRunner)”

IIncrementalGenerator를 테스트 환경에서 실행하고 결과를 반환하는 정적 유틸리티입니다.

public static class SourceGeneratorTestRunner
{
public static string? Generate<TGenerator>(
this TGenerator generator, string sourceCode)
where TGenerator : IIncrementalGenerator, new();
public static (string? GeneratedCode, ImmutableArray<Diagnostic> Diagnostics)
GenerateWithDiagnostics<TGenerator>(
this TGenerator generator, string sourceCode)
where TGenerator : IIncrementalGenerator, new();
}
메서드설명
Generate생성된 코드 반환. 컴파일러 에러 시 Shouldly로 실패
GenerateWithDiagnostics생성된 코드 + 진단 결과 반환. DiagnosticDescriptor 테스트용

필수 참조 어셈블리: System.Runtime, LanguageExt.Core, Microsoft.Extensions.Logging + 현재 로드된 모든 비동적 어셈블리


레이어별 에러 타입에 대한 타입 안전 검증 확장 메서드입니다. 모든 어설션은 Error, Fin<T>, Validation<Error, T>에 대해 동작합니다.

DomainErrorAssertions, ApplicationErrorAssertions, AdapterErrorAssertions는 동일한 패턴으로 레이어별 에러를 검증합니다. 각 클래스의 TContext 타입 매개변수와 에러 코드 접두사만 다릅니다.

클래스TContext 의미에러 코드 형식
DomainErrorAssertionsTDomain (도메인 타입)DomainErrors.{Name}.{ErrorName}
ApplicationErrorAssertionsTUsecase (유스케이스 타입)ApplicationErrors.{Name}.{ErrorName}
AdapterErrorAssertionsTAdapter (어댑터 타입)AdapterErrors.{Name}.{ErrorName}

공통 메서드 패턴 (Domain 기준):

// Error 검증 (0~3개 현재 값 오버로드)
error.ShouldBeDomainError<TDomain>(errorType);
error.ShouldBeDomainError<TDomain, TValue>(errorType, currentValue);
// Fin<T> 검증
fin.ShouldBeDomainError<TDomain, T>(errorType);
fin.ShouldBeDomainError<TDomain, T, TValue>(errorType, currentValue);
// Validation<Error, T> 검증
validation.ShouldHaveDomainError<TDomain, T>(errorType);
validation.ShouldHaveOnlyDomainError<TDomain, T>(errorType);
validation.ShouldHaveDomainErrors<TDomain, T>(errorType1, errorType2, ...);
validation.ShouldHaveDomainError<TDomain, T, TValue>(errorType, currentValue);

AdapterErrorAssertions의 추가 메서드 (예외 래핑 에러 검증):

error.ShouldBeAdapterExceptionalError<TAdapter>(errorType);
error.ShouldBeAdapterExceptionalError<TAdapter, TException>(errorType);
fin.ShouldBeAdapterExceptionalError<TAdapter, T>(errorType);

DomainErrorType 등에 의존하지 않는 범용 에러 코드 검증입니다.

public static class ErrorCodeAssertions
{
// Error 상태 검증
public static IHasErrorCode ShouldHaveErrorCode(this Error error);
public static void ShouldBeExpected(this Error error);
public static void ShouldBeExceptional(this Error error);
// ErrorCode 매칭
public static void ShouldHaveErrorCode(this Error error, string expectedErrorCode);
public static void ShouldHaveErrorCodeStartingWith(this Error error, string prefix);
public static void ShouldHaveErrorCode(this Error error, Func<string, bool> predicate, ...);
// ErrorCodeExpected 변형 (0~3개 값 오버로드 + predicate)
public static void ShouldBeErrorCodeExpected(this Error error, string code, string value);
public static void ShouldBeErrorCodeExpected<T>(this Error error, string code, T value);
// Fin<T> 검증
public static T ShouldSucceed<T>(this Fin<T> fin);
public static void ShouldSucceedWith<T>(this Fin<T> fin, T expectedValue);
public static void ShouldFail<T>(this Fin<T> fin);
public static void ShouldFail<T>(this Fin<T> fin, Action<Error> errorAssertion);
public static void ShouldFailWithErrorCode<T>(this Fin<T> fin, string expectedErrorCode);
// Validation<Error, T> 검증
public static T ShouldBeValid<T>(this Validation<Error, T> validation);
public static void ShouldBeInvalid<T>(..., Action<Seq<Error>> errorsAssertion);
public static void ShouldContainErrorCode<T>(..., string expectedErrorCode);
public static void ShouldContainOnlyErrorCode<T>(..., string expectedErrorCode);
public static void ShouldContainErrorCodes<T>(..., params string[] expectedErrorCodes);
}

예외 기반 에러(ErrorCodeExceptional) 검증을 위한 특화된 확장 메서드입니다.

public static class ErrorCodeExceptionalAssertions
{
// Error 검증
public static void ShouldBeErrorCodeExceptional(this Error error, string code);
public static void ShouldBeErrorCodeExceptional<TException>(this Error error, string code);
public static void ShouldWrapException<TException>(
this Error error, string code, string? message = null);
// Fin<T> 검증
public static void ShouldFailWithException<T>(this Fin<T> fin, string code);
public static void ShouldFailWithException<T, TException>(this Fin<T> fin, string code);
// Validation<Error, T> 검증
public static void ShouldContainException<T>(..., string code);
public static void ShouldContainException<T, TException>(..., string code);
public static void ShouldContainOnlyException<T>(..., string code);
}

ErrorValidation<Error, T>에 대한 공통 확장 속성입니다. C# 14 Extension Members 문법을 사용합니다.

public static class ErrorAssertionHelpers
{
extension(Error error)
{
public string? ErrorCode { get; } // IHasErrorCode 구현 시 코드 반환
public bool HasErrorCode { get; } // IHasErrorCode 구현 여부
}
extension<T>(Validation<Error, T> validation)
{
public IReadOnlyList<Error> Errors { get; } // 에러 목록 추출
}
}

아키텍처 규칙 (ClassValidator, MethodValidator, InterfaceValidator)

섹션 제목: “아키텍처 규칙 (ClassValidator, MethodValidator, InterfaceValidator)”

ArchUnitNET 기반 Fluent API로, 클래스/메서드/인터페이스 수준의 아키텍처 규칙을 검증합니다.

public static class ArchitectureValidationEntryPoint
{
public static ValidationResultSummary ValidateAllClasses(
this IObjectProvider<Class> classes, Architecture architecture,
Action<ClassValidator> validationRule, bool verbose = false);
public static ValidationResultSummary ValidateAllInterfaces(
this IObjectProvider<Interface> interfaces, Architecture architecture,
Action<InterfaceValidator> validationRule, bool verbose = false);
}

TypeValidator<Class, ClassValidator>를 상속하며, Fluent API로 체이닝합니다.

카테고리메서드
가시성RequirePublic(), RequireInternal()
한정자RequireSealed(), RequireNotSealed(), RequireStatic(), RequireNotStatic(), RequireAbstract(), RequireNotAbstract()
타입RequireRecord(), RequireNotRecord(), RequireAttribute(name)
상속RequireInherits(baseType)
생성자RequirePrivateAnyParameterlessConstructor(), RequireAllPrivateConstructors()
프로퍼티RequireNoPublicSetters(), RequireOnlyPrimitiveProperties(...)
필드RequireNoInstanceFields(...)
중첩 클래스RequireNestedClass(name, validation?), RequireNestedClassIfExists(name, validation?)
불변성RequireImmutable()

TypeValidator<TType, TSelf> (공통 기반)

섹션 제목: “TypeValidator<TType, TSelf> (공통 기반)”

ClassValidatorInterfaceValidator가 공유하는 CRTP 기반 추상 기반 클래스입니다.

카테고리메서드
네이밍RequireNameStartsWith(prefix), RequireNameEndsWith(suffix), RequireNameMatching(regex)
인터페이스RequireImplements(type), RequireImplementsGenericInterface(name)
의존성RequireNoDependencyOn(typeNameContains)
메서드RequireMethod(name, validation), RequireAllMethods(validation), RequireAllMethods(filter, validation), RequireMethodIfExists(name, validation)
프로퍼티RequireProperty(name)
합성Apply(IArchRule<TType> rule)
카테고리메서드
가시성/한정자RequireVisibility(v), RequireStatic(), RequireNotStatic(), RequireVirtual(), RequireNotVirtual(), RequireExtensionMethod()
반환 타입RequireReturnType(type), RequireReturnTypeOfDeclaringClass(), RequireReturnTypeOfDeclaringTopLevelClass(), RequireReturnTypeContaining(fragment)
매개변수RequireParameterCount(n), RequireParameterCountAtLeast(n), RequireFirstParameterTypeContaining(fragment), RequireAnyParameterTypeContaining(fragment)

TypeValidator<Interface, InterfaceValidator>를 상속합니다. TypeValidator의 공통 메서드만 사용합니다.

public interface IArchRule<in TType> where TType : IType
{
string Description { get; }
IReadOnlyList<RuleViolation> Validate(TType target, Architecture architecture);
}

ImmutabilityRule IArchRule<Class> 구현체로, 6가지 차원에서 불변성을 검증합니다: Writability, Constructors (all private), PropertySetters (no public), PublicFields (none), MutableCollections (List<> 등 금지), StateChangingMethods (팩토리/동등성/getter 제외).

타입설명
RuleViolation(TargetName, RuleName, Description)규칙 위반 sealed record
ValidationResultSummary결과 집계, ThrowIfAnyFailures(ruleName) 호출 시 ArchitectureViolationException 발생
ArchitectureViolationExceptionRuleName, Violations 속성 보유
CompositeArchRule<TType>여러 규칙을 AND 합성
DelegateArchRule<TType>람다 기반 커스텀 규칙

테스트 스위트 (DomainArchitectureTestSuite, ApplicationArchitectureTestSuite)

섹션 제목: “테스트 스위트 (DomainArchitectureTestSuite, ApplicationArchitectureTestSuite)”

도메인 레이어의 아키텍처 규칙을 일괄 검증하는 추상 테스트 스위트입니다. 총 21개의 [Fact] 테스트를 제공합니다.

public abstract class DomainArchitectureTestSuite
{
protected abstract Architecture Architecture { get; }
protected abstract string DomainNamespace { get; }
protected virtual IReadOnlyList<Type> ValueObjectExcludeFromFactoryMethods => [];
protected virtual string[] DomainServiceAllowedFieldTypes => [];
}

포함된 테스트 (21개):

카테고리테스트설명
Entity (7)AggregateRoot_ShouldBe_PublicSealedClasspublic sealed 클래스
AggregateRoot_ShouldHave_CreateAndCreateFromValidated정적 팩토리 메서드 필수
AggregateRoot_ShouldHave_GenerateEntityIdAttribute[GenerateEntityId] 필수
AggregateRoot_ShouldHave_AllPrivateConstructors모든 생성자 private
Entity_ShouldBe_PublicSealedClass비 AggregateRoot Entity도 public sealed
Entity_ShouldHave_CreateAndCreateFromValidatedEntity 팩토리 메서드 필수
Entity_ShouldHave_AllPrivateConstructorsEntity 생성자 private
ValueObject (4)ValueObject_ShouldBe_PublicSealedWithPrivateConstructorspublic sealed + private 생성자
ValueObject_ShouldBe_ImmutableImmutabilityRule 적용
ValueObject_ShouldHave_CreateFactoryMethodCreateFin<T> 반환
ValueObject_ShouldHave_ValidateMethodValidateValidation<Error, T> 반환
DomainEvent (2)DomainEvent_ShouldBe_SealedRecordsealed record 필수
DomainEvent_ShouldHave_EventSuffix"Event" 접미사 필수
Specification (3)Specification_ShouldBe_PublicSealedpublic sealed
Specification_ShouldInherit_SpecificationBaseSpecification<T> 상속
Specification_ShouldResideIn_DomainLayer도메인 레이어에만 위치
DomainService (5)DomainService_ShouldBe_PublicSealedpublic sealed
DomainService_ShouldBe_Stateless인스턴스 필드 없음
DomainService_ShouldNotDependOn_IObservablePort관측 의존 금지
DomainService_PublicMethods_ShouldReturn_Finpublic 메서드 Fin 반환
DomainService_ShouldNotBe_Recordrecord 금지

애플리케이션 레이어의 CQRS 구조를 검증하는 추상 테스트 스위트입니다. 총 4개의 [Fact] 테스트를 제공합니다.

public abstract class ApplicationArchitectureTestSuite
{
protected abstract Architecture Architecture { get; }
protected abstract string ApplicationNamespace { get; }
}
테스트설명
Command_ShouldHave_ValidatorNestedClassValidator 있으면 sealed + AbstractValidator 구현
Command_ShouldHave_UsecaseNestedClassUsecase 필수, sealed + ICommandUsecase 구현
Query_ShouldHave_ValidatorNestedClassValidator 있으면 sealed + AbstractValidator 구현
Query_ShouldHave_UsecaseNestedClassUsecase 필수, sealed + IQueryUsecase 구현