네이밍컨벤션 테스트트
테스트 준비
NamingConvention
cs
public static partial class Constants
{
public static class NamingConvention
{
// CQRS
public const string Command = nameof(Command);
public const string CommandUsecase = nameof(CommandUsecase);
public const string Query = nameof(Query);
public const string QueryUsecase = nameof(QueryUsecase);
public const string ValidatorSuffix = ".*(Command|Query|Options)Validator$";
}
}
Internal 접근 허용
xml
<ItemGroup>
<InternalsVisibleTo Include="{T}.Tests.Unit" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
- 테스트 프로젝트에서 Application 프로젝트의
internal
클래스를 접근하기 위해InternalsVisibleTo
을 지정합니다.
C# 네이밍컨벤션
규칙
- 인터페이스는
^I[A-Z].*
로 시작한다. - 메서드는 대문자로 시작한다.
테스트
cs
[Fact]
public void Interfaces_ShouldStartWith_I()
{
IEnumerable<Interface> sut = ArchRuleDefinition
.Interfaces()
.GetObjects(Architecture);
if (!sut.Any())
return;
sut.ShouldAllBe(i => Regex.IsMatch(i.Name, "^I[A-Z].*"));
}
[Fact]
public void AllMethods_ShouldStartWith_CapitalLetter()
{
ArchRuleDefinition
.MethodMembers()
// 제외
.That().DoNotHaveNameStartingWith(".ctor") // 클래스 생성자
.And().DoNotHaveNameStartingWith(".cctor") // 정적 클래스 생성자
.And().DoNotHaveNameStartingWith("get_") // property
.And().DoNotHaveNameStartingWith("set_") // property
.And().DoNotHaveNameStartingWith("op_") // 연산자 오버로딩: 예. + op_Addition, == op_Equality, ...
// 순수 메서드
.Should().HaveName(@"^[A-Z]", true)
.Check(Architecture);
}
Validator 네이밍컨벤션
규칙
IValidator<T>
을 상속받는 모든 클래스는 internal sealed 이어야 한다.- 클래스 이름은 반드시 Validator 접미사를 가져야 한다.
테스트
cs
[Fact]
public void ValidatorClasses_ShouldBe_InternalSealed_And_Have_ValidatorSuffix()
{
var sut = ArchRuleDefinition
.Classes()
.That()
.ImplementInterface(typeof(IValidator<>));
if (!sut.GetObjects(Architecture).Any())
return;
sut.Should().BeInternal()
.AndShould().BeSealed()
.AndShould().HaveName(NamingConvention.ValidatorSuffix, true)
.Check(Architecture);
}
Options 네이밍컨벤션
규칙
OptionsValidator : Options = 1 : 1
관계이다.- Options 클래스는 public sealed 클래스이다.
- public const string SectionName 필드를 갖는다.
테스트
cs
[Fact]
public void OptionsClasses_ShouldBe_Sealed_And_Have_SectionName()
{
// Arrange
var optionsValidatorClasses = ArchRuleDefinition
.Classes()
.That().ImplementInterface(typeof(IValidator<>))
.And().HaveNameEndingWith("OptionsValidator")
.GetObjects(Architecture);
foreach (var optionsValidatorClass in optionsValidatorClasses)
{
// Act
Class? optionsClass = GetCorrespondingOptionsClass(optionsValidatorClass);
// Assert
// - public sealed 클래스
// - public const string SectionName
// - TODO: 값 예. XyzOption -> Xyz
optionsClass
.ShouldNotBeNull($"Corresponding Options class for '{optionsValidatorClass.FullName}' not found.");
optionsClass
.Visibility.ShouldBe(Visibility.Public, $"Options class '{optionsClass.FullName}' should be public.");
optionsClass
.IsSealed!.Value.ShouldBeTrue($"Options class '{optionsClass.FullName}' should be sealed.");
optionsClass
.GetFieldMembers()
.ShouldContain(field =>
field.Name == "SectionName" &&
field.Visibility == Visibility.Public &&
field.IsStatic.Value &&
field.Type.FullName == "System.String",
$"Options class '{optionsClass.FullName}' should have a public const string SectionName."
);
}
}
private static Class? GetCorrespondingOptionsClass(Class optionsValidatorClass)
{
var optionClassName = optionsValidatorClass.Name[..optionsValidatorClass.Name.LastIndexOf("Validator")];
return ArchRuleDefinition
.Classes()
.That().HaveName(optionClassName)
.GetObjects(Architecture)
.FirstOrDefault();
}
CQRS 네이밍컨벤션
규칙
- 메시지
- 접미사: Command, Query
- 클래스: public, sealed, record
- 핸들러
- 접미사: CommandUsecase, QueryUsecase
- 클래스: internal, sealed
테스트
cs
[Trait(nameof(UnitTest), UnitTest.Architecture)]
public sealed partial class NamingConventionsTests : ArchitectureTestBase
{
[Fact]
public void CommandMessages_ShouldComplyWith_DesignRules()
{
var suts = ArchRuleDefinition
.Classes()
.That()
.ImplementInterface(typeof(ICommand));
if (!suts.GetObjects(Architecture).Any())
return;
suts.Should().BePublic()
.AndShould().BeSealed()
.AndShould().BeRecord()
.AndShould().HaveNameEndingWith(NamingConvention.Command)
.Check(Architecture);
}
[Fact]
public void CommandMessagesT_ShouldComplyWith_DesignRules()
{
var suts = ArchRuleDefinition
.Classes()
.That()
.ImplementInterface(typeof(ICommand<>));
if (!suts.GetObjects(Architecture).Any())
return;
suts.Should().BePublic()
.AndShould().BeSealed()
.AndShould().BeRecord()
.AndShould().HaveNameEndingWith(NamingConvention.Command)
.Check(Architecture);
}
[Fact]
public void CommandUsecases_ShouldComplyWith_DesignRules()
{
var suts = ArchRuleDefinition
.Classes()
.That()
.ImplementInterface(typeof(ICommandUsecase<>));
if (!suts.GetObjects(Architecture).Any())
return;
suts.Should().BeInternal()
.AndShould().BeSealed()
.AndShould().HaveNameEndingWith(NamingConvention.CommandUsecase)
.Check(Architecture);
}
[Fact]
public void CommandUsecasesT_ShouldComplyWith_DesignRules()
{
var suts = ArchRuleDefinition
.Classes()
.That()
.ImplementInterface(typeof(ICommandUsecase<,>));
if (!suts.GetObjects(Architecture).Any())
return;
suts.Should().BeInternal()
.AndShould().BeSealed()
.AndShould().HaveNameEndingWith(NamingConvention.CommandUsecase)
.Check(Architecture);
}
[Fact]
public void QueryMessagesT_ShouldComplyWith_DesignRules()
{
var suts = ArchRuleDefinition
.Classes()
.That()
.ImplementInterface(typeof(IQuery<>));
if (!suts.GetObjects(Architecture).Any())
return;
suts.Should().BePublic()
.AndShould().BeSealed()
.AndShould().BeRecord()
.AndShould().HaveNameEndingWith(NamingConvention.Query)
.Check(Architecture);
}
[Fact]
public void QueryUsecasesT_ShouldComplyWith_DesignRules()
{
var suts = ArchRuleDefinition
.Classes()
.That()
.ImplementInterface(typeof(IQueryUsecase<,>));
if (!suts.GetObjects(Architecture).Any())
return;
suts.Should().BeInternal()
.AndShould().BeSealed()
.AndShould().HaveNameEndingWith(NamingConvention.QueryUsecase)
.Check(Architecture);
}
}