ibe*_*dev 3 c# castle-dynamicproxy xunit autofac eventstoredb
我遇到了一个问题,已经很多天无法解决。\n我将 xUnit 与给定的当时抽象一起使用,以使测试更具可读性。
\n我正在使用 EventStore 的包装器并运行一些集成测试。它们都运行良好......除了一个在并行运行时失败(并且 xUnit 并行运行),但如果我按顺序运行它们,它们都会成功。
\n我无法理解为什么这会成为一个问题,因为每个事实都应该运行构造函数(给定的)和要测试的功能(何时)。在每个事实中,我都会实例化一个 Autofac ContainerBuilder,构建容器并解析它IComponentContext,因此理论上每个测试都应该按预期隔离和幂等。
这是我不断收到的异常:
\nAutofac.Core.DependencyResolutionException : An exception was thrown while activating SalesOrder.EventStore.Infra.EventStore.EventStore -> SalesOrder.EventStore.Infra.EventStore.DomainEventsRetriever -> SalesOrder.EventStore.Infra.EventStore.Factories.DomainEventFactory -> \xce\xbb:SalesOrder.EventStore.Infra.EventStore.EventTypeResolver.\n---- System.Reflection.ReflectionTypeLoadException : Unable to load one or more of the requested types.\nCould not load type \'Castle.Proxies.IReadinessProxy\' from assembly \'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\'.\n at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\InstanceLookup.cs:line 136\n at Autofac.Core.Resolving.InstanceLookup.Execute() in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\InstanceLookup.cs:line 85\n at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, IComponentRegistration registration, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\ResolveOperation.cs:line 130\n at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\ResolveOperation.cs:line 83\n at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance) in C:\\projects\\autofac\\src\\Autofac\\ResolutionExtensions.cs:line 1041\n at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\ResolutionExtensions.cs:line 871\n at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\ResolutionExtensions.cs:line 300\n at SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests.EventStoreExtensionsTests.ResolveTests.Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore.When() in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests\\EventStoreExtensionsTests\\ResolveTests.cs:line 53\n at SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests.EventStoreExtensionsTests.ResolveTests.Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore..ctor()\n----- Inner Stack Trace -----\n at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)\n at System.Reflection.RuntimeModule.GetTypes()\n at System.Reflection.Assembly.GetTypes()\n at SalesOrder.EventStore.Infra.EventStore.Autofac.EventStoreExtensions.<>c.<RegisterResolvers>b__6_2(Assembly s) in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore.Autofac\\EventStoreExtensions.cs:line 174\n at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()\n at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()\n at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)\n at SalesOrder.EventStore.Infra.EventStore.Factories.EventTypeResolverFactory.Create(IEnumerable`1 existingTypes, IReadOnlyDictionary`2 domainEventSerializerDeserializers) in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore\\Factories\\EventTypeResolverFactory.cs:line 16\n at SalesOrder.EventStore.Infra.EventStore.Autofac.EventStoreExtensions.<>c__DisplayClass6_0.<RegisterResolvers>b__1(IComponentContext context) in C:\\src\\SalesOrder.EventStore\\SalesOrder.EventStore.Infra.EventStore.Autofac\\EventStoreExtensions.cs:line 180\n at Autofac.Builder.RegistrationBuilder.<>c__DisplayClass0_0`1.<ForDelegate>b__0(IComponentContext c, IEnumerable`1 p) in C:\\projects\\autofac\\src\\Autofac\\Builder\\RegistrationBuilder.cs:line 62\n at Autofac.Core.Activators.Delegate.DelegateActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters) in C:\\projects\\autofac\\src\\Autofac\\Core\\Activators\\Delegate\\DelegateActivator.cs:line 71\n at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\\projects\\autofac\\src\\Autofac\\Core\\Resolving\\InstanceLookup.cs:line 118\nRun Code Online (Sandbox Code Playgroud)\n这是只有一个事实的测试,在与其他事实并行运行时会失败:
\npublic class Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore\n : Given_When_Then_Test\n{\n private IEventStore _sut;\n private IComponentContext _componentContext;\n\n protected override void Given()\n {\n var builder = new ContainerBuilder();\n builder\n .RegisterEventStore(\n ctx =>\n {\n var eventStoreOptions =\n new EventStoreOptions\n {\n Url = EventStoreTestConstants.TestUrl,\n Port = EventStoreTestConstants.TestPort\n };\n return eventStoreOptions;\n },\n ctx =>\n {\n var readinessOptions =\n new ReadinessOptions\n {\n Timeout = EventStoreTestConstants.TestTimeout\n };\n return readinessOptions;\n });\n\n var container = builder.Build();\n _componentContext = container.Resolve<IComponentContext>();\n }\n\n protected override void When()\n {\n _sut = _componentContext.Resolve<IEventStore>();\n }\n\n [Fact]\n public void Then_It_Should_Not_Be_Null()\n {\n _sut.Should().NotBeNull();\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n知道这里可能发生什么吗?\n我已经查看了 Autofac 关于并发性的指南:https ://autofaccn.readthedocs.io/en/latest/advanced/concurrency.html但我认为我已经正确使用了组件上下文。
\n更新 1:仅供参考,这是我使用的 GiveThenWhen 模板。但这里没什么特别的(我认为!)
\nnamespace ToolBelt.TestSupport\n{\n public abstract class Given_When_Then_Test\n : IDisposable\n {\n protected Given_When_Then_Test()\n {\n Setup();\n }\n\n private void Setup()\n {\n Given();\n When();\n }\n\n protected abstract void Given();\n\n protected abstract void When();\n\n public void Dispose()\n {\n Cleanup();\n }\n\n protected virtual void Cleanup()\n {\n }\n }\n \n public abstract class Given_WhenAsync_Then_Test\n : IDisposable\n {\n protected Given_WhenAsync_Then_Test()\n {\n Task.Run(async () => { await SetupAsync(); }).GetAwaiter().GetResult();\n }\n\n private async Task SetupAsync()\n {\n Given();\n await WhenAsync();\n }\n\n protected abstract void Given();\n\n protected abstract Task WhenAsync();\n\n public void Dispose()\n {\n Cleanup();\n }\n\n protected virtual void Cleanup()\n {\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n更新 2:这是我用于实现和所有测试的 IoCC Autofac 注册方法。有点复杂,因为我使用反射来保持 EventStore 包装器完全通用,但以防万一有人看到一些可能影响测试的奇怪内容。
\npublic static class EventStoreExtensions\n{\n public static void RegisterEventStore(\n this ContainerBuilder builder,\n Func<IComponentContext, EventStoreOptions> optionsRetriever,\n Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever,\n Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever = null)\n {\n RegisterEventStoreConnection(builder, optionsRetriever);\n RegisterFactories(builder);\n RegisterEventStoreManager(builder);\n RegisterConverters(builder);\n RegisterResolvers(builder, customDomainEventMappersOptionsRetriever);\n RegisterEventStoreServices(builder);\n RegisterEventStoreReadinessCheck(builder, readinessOptionsRetriever);\n }\n\n private static void RegisterEventStoreReadinessCheck(\n ContainerBuilder builder,\n Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever)\n {\n builder\n .Register(context =>\n {\n var ctx = context.Resolve<IComponentContext>();\n var readinessOptions = readinessOptionsRetriever.Invoke(ctx);\n\n var timeout = readinessOptions.Timeout;\n var eventStoreConnection = context.Resolve<IEventStoreConnection>();\n\n var eventStoreReadiness =\n new EventStoreReadiness(\n eventStoreConnection,\n timeout);\n\n return eventStoreReadiness;\n\n })\n .As<IEventStoreReadiness>()\n .As<IReadiness>()\n .SingleInstance();\n }\n\n private static void RegisterEventStoreConnection(\n ContainerBuilder builder,\n Func<IComponentContext, EventStoreOptions> optionsRetriever)\n {\n builder\n .Register(context =>\n {\n var ctx = context.Resolve<IComponentContext>();\n var eventStoreOptions = optionsRetriever.Invoke(ctx);\n\n ConnectionSettings connectionSetting =\n ConnectionSettings\n .Create()\n .KeepReconnecting();\n\n var urlString = eventStoreOptions.Url;\n var port = eventStoreOptions.Port;\n var ipAddress = IPAddress.Parse(urlString);\n var tcpEndPoint = new IPEndPoint(ipAddress, port);\n\n var eventStoreConnection = EventStoreConnection.Create(connectionSetting, tcpEndPoint);\n return eventStoreConnection;\n })\n .As<IEventStoreConnection>()\n .SingleInstance();\n }\n\n private static void RegisterFactories(\n ContainerBuilder builder)\n {\n builder\n .RegisterType<DomainEventFactory>()\n .As<IDomainEventFactory>()\n .SingleInstance();\n\n builder\n .RegisterType<EventDataFactory>()\n .As<IEventDataFactory>()\n .SingleInstance();\n\n builder\n .RegisterType<ExpectedVersionFactory>()\n .As<IExpectedVersionFactory>()\n .SingleInstance();\n\n builder\n .RegisterType<IdFactory>()\n .As<IIdFactory>()\n .SingleInstance();\n\n builder\n .RegisterType<StreamNameFactory>()\n .As<IStreamNameFactory>()\n .SingleInstance();\n\n builder\n .RegisterType<RetrievedEventFactory>()\n .As<IRetrievedEventFactory>()\n .SingleInstance();\n }\n\n private static void RegisterEventStoreManager(ContainerBuilder builder)\n {\n builder\n .RegisterType<EventStoreManager>()\n .As<IEventStoreManager>()\n .SingleInstance();\n }\n\n private static void RegisterConverters(ContainerBuilder builder)\n {\n builder\n .Register(context =>\n {\n var utf8Encoding = new BytesConverter(Encoding.UTF8);\n return utf8Encoding;\n })\n .As<IBytesConverter>()\n .SingleInstance();\n\n builder\n .RegisterType<NewtonsoftConverter>()\n .As<IJsonConverter>()\n .SingleInstance();\n }\n\n private static void RegisterResolvers(\n ContainerBuilder builder,\n Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever)\n {\n builder\n .Register(context =>\n {\n var ctx = context.Resolve<IComponentContext>();\n var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);\n var domainEventSerializerDeserializers =\n customDomainEventMappersOptions?.DomainEventSerializerDeserializers;\n var mapperResolver = MapperResolverFactory.Create(domainEventSerializerDeserializers);\n return mapperResolver;\n })\n .As<IMapperResolver>()\n .SingleInstance();\n\n builder\n .Register(context =>\n {\n var ctx = context.Resolve<IComponentContext>();\n\n var assembliesLoaded = AppDomain.CurrentDomain.GetAssemblies();\n var domainEventTypes =\n assembliesLoaded\n .SelectMany(s => s.GetTypes())\n .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)\n && x.IsClass);\n var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);\n var domainEventSerializerDeserializers =\n customDomainEventMappersOptions?.DomainEventSerializerDeserializers;\n var typeResolver =\n EventTypeResolverFactory.Create(\n domainEventTypes,\n domainEventSerializerDeserializers);\n return typeResolver;\n })\n .As<IEventTypeResolver>()\n .SingleInstance();\n }\n\n private static void RegisterEventStoreServices(ContainerBuilder builder)\n {\n builder\n .RegisterType<EventStoreRepository>()\n .As<IEventStoreRepository>();\n\n builder\n .RegisterType<DomainEventsPersister>()\n .As<IDomainEventsPersister>();\n\n builder\n .RegisterType<DomainEventsRetriever>()\n .As<IDomainEventsRetriever>();\n\n builder\n .RegisterType<EventStore>()\n .As<IEventStore>();\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n更新 3:这是整个存储库,以防有人感到无聊并想要尝试重现它。它是一个通用事件存储包装器,用于使用官方 C# 驱动程序为 Greg Young 的事件存储产品实现的事件源。
\nhttps://gitlab.com/DiegoDrivenDesign/DiDrDe.EventStore
\n有趣的是,这个问题似乎每隔一段时间就会消失一次。事实上,通常情况下,重新启动电脑后,所有测试都会正常通过。其他时候则不然,所以我怀疑这与我在运行时加载程序集并且出现问题有关:(
\n更新于 2021 年 9 月 9 日\n我所做的异步执行并不完全正确。有一种方法可以使用 xUnit 来拥抱异步执行,该方法可能在这种竞争条件中发挥了作用。given-when-then这是我所有测试继承的模式的更好实现。感谢IAsyncLifetime我能够异步(并等待)前提条件和操作本身。
using System.Threading.Tasks;\nusing Xunit;\n\nnamespace Rubiko.TestSupport.XUnit\n{\n public abstract class Given_When_Then_Test_Async\n : IAsyncLifetime\n {\n public async Task InitializeAsync()\n {\n await Given();\n await When();\n }\n\n public async Task DisposeAsync()\n {\n await Cleanup();\n }\n\n protected virtual Task Cleanup()\n {\n return Task.CompletedTask;\n }\n\n protected abstract Task Given();\n\n protected abstract Task When();\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n
DynamicProxyGenAssembly2 是由使用 CastleProxy 的模拟系统构建的临时程序集。NSubstitute 和 Moq 也有一些类似的未解决问题,表明该问题是 Castle.Core 甚至 .Net Framework 中的竞争条件(有关更多详细信息,请参阅:重型多线程代理生成下的 TypeLoadException 或 BadImageFormatException )
| 归档时间: |
|
| 查看次数: |
8164 次 |
| 最近记录: |