ArchUnit测试异常排查:合规Command类触发失败提示
问题描述
使用ArchUnit进行架构测试时,配置规则要求:实现IRequest的非抽象且名称以Command结尾的类,需位于匹配.+\.Commands\.的命名空间下。但符合条件的Command类(如SynchronizeMasterDataCommand等)却触发了FailedArchRuleException异常,错误提示反而显示这些类确实处于对应Commands命名空间中。
错误信息
Message: ArchUnitNET.xUnit.FailedArchRuleException : "Classes that are Classes that are Classes that implement 'IRequest' and are not abstract and have name matching ".+Command$" and have name matching ".+Command$" should reside in namespace with full name matching ".+\.Commands\."" failed: Core.Application.MasterDataSync.Commands.SynchronizeMasterDataCommand does reside in Core.Application.MasterDataSync.Commands Core.Application.ManagerChain.Commands.CalculateManagerChainCommand does reside in Core.Application.ManagerChain.Commands Core.Application.EmployeeSync.Commands.SynchronizeEmployeeCommand does reside in Core.Application.EmployeeSync.Commands Core.Application.EmployeeFixedPaymentSummaries.Commands.SynchronizeLatestYearlyPaymentsCommand does reside in Core.Application.EmployeeFixedPaymentSummaries.Commands
相关代码
加载程序集代码
using System.Reflection; using ArchUnitNET.Domain; using ArchUnitNET.Loader; using Assembly = System.Reflection.Assembly; namespace Corsica.Tests.ArchUnit; public static class Corsica { public static readonly Architecture ARCHITECTURE = new ArchLoader() .LoadAssemblies(GetSolutionAssemblies()) .Build(); private static Assembly[] GetSolutionAssemblies() { var assemblies = Directory .GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll") .Select(x => Assembly.Load(AssemblyName.GetAssemblyName(x))) .Where(x => x.FullName!.StartsWith("Core")); return assemblies.ToArray(); } }
Command类代码
using MediatR; namespace Core.Application.EmployeeFixedPaymentSummaries.Commands; public class SynchronizeLatestYearlyPaymentsCommand : IRequest { } using MediatR; namespace Core.Application.EmployeeSync.Commands; public class SynchronizeEmployeeCommand : IRequest { } using MediatR; namespace Core.Application.MasterDataSync.Commands; public class SynchronizeMasterDataCommand : IRequest { } using MediatR; namespace Core.Application.ManagerChain.Commands; public class CalculateManagerChainCommand : IRequest { }
测试类代码
using ArchUnitNET.Domain; using ArchUnitNET.xUnit; using MediatR; using static ArchUnitNET.Fluent.ArchRuleDefinition; namespace Corsica.Tests.ArchUnit; public class Cqrs { private static readonly IObjectProvider<IType> NON_ABSTRACT_CLASSES_IMPLEMENTING_IREQUEST = Classes() .That().AreAssignableTo(typeof(IRequest<>)).Or().AreAssignableTo(typeof(IRequest)).As("Classes that implement 'IRequest'") .And().AreNotAbstract() .And().HaveName(".+Command$", true); private static readonly IObjectProvider<IType> COMMANDS = Classes().That().Are(NON_ABSTRACT_CLASSES_IMPLEMENTING_IREQUEST) .And().HaveName(".+Command$", true); private static readonly IObjectProvider<IType> QUERIES = Classes().That().Are(NON_ABSTRACT_CLASSES_IMPLEMENTING_IREQUEST) .And().HaveName(".+Query$", true); [Fact] public void ClassesInheritingFromIRequestShouldHaveNameEndingWithCommandOrQuery() { Classes().That().Are(NON_ABSTRACT_CLASSES_IMPLEMENTING_IREQUEST) .Should().HaveName(".+(Command|Query)$", true).As("should have names ending with 'Command' or 'Query'") .Check(Corsica.ARCHITECTURE); } [Fact] public void CommandsShouldResideInCommandsNamespace() { Classes().That().Are(COMMANDS) .Should().ResideInNamespace(".+\.Commands\.", true) .Check(Corsica.ARCHITECTURE); } [Fact] public void QueriesShouldResideInQueriesNamespace() { Classes().That().Are(QUERIES) .Should().ResideInNamespace(".+\.Queries\.", true) .Check(Corsica.ARCHITECTURE); } }
原因分析
- 正则表达式匹配错误:规则中使用的命名空间正则
.+\.Commands\.要求命名空间必须包含.Commands.(末尾带点),但实际类的命名空间是Core.Application.MasterDataSync.Commands(末尾无点),导致正则不匹配,触发异常。 - 重复过滤条件冗余:
NON_ABSTRACT_CLASSES_IMPLEMENTING_IREQUEST已经过滤了名称以Command结尾的类,COMMANDS又重复添加了相同过滤条件,导致规则描述出现冗余(错误信息里的重复文字就是这个原因)。
解决方案
修正命名空间正则:将
.+\.Commands\.改为.+\.Commands$,确保匹配以Commands结尾的命名空间:[Fact] public void CommandsShouldResideInCommandsNamespace() { Classes().That().Are(COMMANDS) .Should().ResideInNamespace(".+\.Commands$", true) .Check(Corsica.ARCHITECTURE); }也可以使用更直观的ArchUnit API:
ResideInNamespaceContaining("Commands"),不过正则.+\.Commands$的匹配精度更高。移除重复过滤条件:优化
NON_ABSTRACT_CLASSES_IMPLEMENTING_IREQUEST的定义,去掉提前过滤.+Command$的逻辑,因为后续需要区分Command和Query:private static readonly IObjectProvider<IType> NON_ABSTRACT_CLASSES_IMPLEMENTING_IREQUEST = Classes() .That().AreAssignableTo(typeof(IRequest<>)).Or().AreAssignableTo(typeof(IRequest)).As("Classes that implement 'IRequest'") .And().AreNotAbstract();
内容的提问来源于stack exchange,提问作者Lombardo
相关产品推荐
相关产品推荐

