TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3118 2.09K
День 2596. #ЗаметкиНаПолях
5 Архитектурных Тестов Необходимых Каждому .NET-Проекту. Продолжение

Начало

2. Проверка правил именования
Этот пункт может показаться незначительным, но несогласованности быстро накапливаются. ArchUnitNET позволяет обеспечивать соблюдение правил именования, выбирая классы на основе интерфейсов, которые они реализуют:
public class NamingTests : BaseTest
{
[Fact]
public void Controllers_ShouldEndWith_Controller()
{
Classes().That()
.AreAssignableTo(typeof(Controller))
.Should().HaveNameEndingWith("Controller")
.Check(Application);
}

[Fact]
public void Validators_ShouldEndWith_Validator()
{
Classes().That()
.HaveNameEndingWith("Validator")
.Should().ResideInAssembly(ApplicationAssembly)
.Check(Architecture);
}
}

Проверка валидатора работает в обратном направлении. Она говорит: «классы, заканчивающиеся на Validator, должны находиться в сборке Application». Иногда валидаторы случайно размещают в инфраструктуре.

3. Тесты на размещение в одном пространстве имён
При использовании CQRS вы получаете пары: команду (или запрос) и её обработчик. Их полезно хранить в одном пространстве имен, чтобы всё для конкретного варианта использования существовало вместе. Application.TodoItems.Create будет содержать как CreateTodoItemCommand, так и CreateTodoItemCommandHandler. Но ничто не мешает кому-либо перемещать эти классы:
public class ColocationTests : BaseTest
{
[Theory]
[MemberData(nameof(GetHandlerAndCommandPairs))]
public void Handlers_ShouldBeWithCommandOrQuery(
Type handlerType,
Type commandType)
{
handlerType.Namespace.ShouldBe(
commandType.Namespace,
$"{handlerType.Name} должен быть в одном пространстве имён с {commandType.Name}");
}

public static TheoryData<Type, Type>
GetHandlerAndCommandPairs()
{
Type[] handlerInterfaces =
[
typeof(ICommandHandler<>),
typeof(IQueryHandler<,>)
];

var pairs = new TheoryData<Type, Type>();
var handlers = ApplicationAssembly
.GetTypes()
.Where(t => t is { IsClass: true, IsAbstract: false, IsGenericTypeDefinition: false })
.Where(t => t.DeclaringType is null);

foreach (Type handler in handlers)
{
foreach (Type iface in handler.GetInterfaces())
{
if (!iface.IsGenericType)
continue;

var genDef = iface.GetGenericTypeDefinition();

if (!handlerInterfaces.Contains(genDef))
continue;

var commandType = iface.GetGenericArguments()[0];
pairs.Add(handler, commandOrQueryType);
}
}

return pairs;
}
}

Этот тест не использует ArchUnitNET. Он использует чистую рефлексию в сочетании с [Theory] и [MemberData] из xUnit. ArchUnitNET не может выразить правило типа «этот класс должен находиться в том же пространстве имен, что и аргумент обобщённого типа его интерфейса». Поэтому мы прибегаем к рефлексии.

GetHandlerAndCommandPairs сканирует сборку Application, находит все классы, реализующие интерфейс обработчика, извлекает тип команды/запроса из обобщённого аргумента и возвращает пары для проверки в тесте. Сюда же можно добавить типы запросов и ответов, валидаторы - всё, что должно быть размещено в одном месте.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/5-architecture-tests-you-should-add-to-your-dotnet-projects
  • 👍 2
More from @netdeveloperdiary
  1. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  2. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  3. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
  4. Sep 24, 2026День 2794. #Оффтоп #Здоровье Сегодня будет необычный пост. Завтра в Москве стартует конфер…
  5. Sep 23, 2026День 2793. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
  6. Sep 22, 2026День 2792. #ЗаметкиНаПолях #SQL 10 Редких Возможностей SQL, Которые Стоит Знать Каждому. Ч…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →