【问题标题】:Could not load type Castle.Proxies.IReadinessProxy when running xUnit integration tests in parallel with Autofac与 Autofac 并行运行 xUnit 集成测试时无法加载类型 Castle.Proxies.IReadinessProxy
【发布时间】:2019-10-08 19:54:39
【问题描述】:

我遇到了很多天都无法解决的问题。 我使用 xUnit 和 given-then-when 抽象来使测试更具可读性。

我正在使用 EventStore 的包装器并运行一些集成测试。它们都运行良好.. 除了 一个在并行运行时失败(并且 xUnit 并行运行),但如果我按顺序运行它们,它们都会成功。

我不明白为什么这会是一个问题,因为每一个事实都应该运行构造函数(给定的)和测试的功能(何时)。在每一个事实中,我都会实例化一个 Autofac ContainerBuilder,构建容器并解析它的 IComponentContext,因此理论上每个测试都应该按照预期进行隔离和幂等。

这是我不断收到的例外情况:

Autofac.Core.DependencyResolutionException : An exception was thrown while activating SalesOrder.EventStore.Infra.EventStore.EventStore -> SalesOrder.EventStore.Infra.EventStore.DomainEventsRetriever -> SalesOrder.EventStore.Infra.EventStore.Factories.DomainEventFactory -> λ:SalesOrder.EventStore.Infra.EventStore.EventTypeResolver.
---- System.Reflection.ReflectionTypeLoadException : Unable to load one or more of the requested types.
Could not load type 'Castle.Proxies.IReadinessProxy' from assembly 'DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 136
   at Autofac.Core.Resolving.InstanceLookup.Execute() in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 85
   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
   at Autofac.Core.Resolving.ResolveOperation.Execute(IComponentRegistration registration, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\Core\Resolving\ResolveOperation.cs:line 83
   at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 1041
   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 871
   at Autofac.ResolutionExtensions.Resolve[TService](IComponentContext context, IEnumerable`1 parameters) in C:\projects\autofac\src\Autofac\ResolutionExtensions.cs:line 300
   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
   at SalesOrder.EventStore.Infra.EventStore.Autofac.IntegrationTests.EventStoreExtensionsTests.ResolveTests.Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore..ctor()
----- Inner Stack Trace -----
   at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
   at System.Reflection.RuntimeModule.GetTypes()
   at System.Reflection.Assembly.GetTypes()
   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
   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
   at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   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
   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
   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
   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
   at Autofac.Core.Resolving.InstanceLookup.Activate(IEnumerable`1 parameters, Object& decoratorTarget) in C:\projects\autofac\src\Autofac\Core\Resolving\InstanceLookup.cs:line 118

这是只有一个 Fact 与其他 Fact 并行运行时失败的测试

public class Given_A_Container_With_Event_Store_Registered_When_Resolving_An_IEventStore
    : Given_When_Then_Test
{
    private IEventStore _sut;
    private IComponentContext _componentContext;

    protected override void Given()
    {
        var builder = new ContainerBuilder();
        builder
            .RegisterEventStore(
                ctx =>
                {
                    var eventStoreOptions =
                        new EventStoreOptions
                        {
                            Url = EventStoreTestConstants.TestUrl,
                            Port = EventStoreTestConstants.TestPort
                        };
                    return eventStoreOptions;
                },
                ctx =>
                {
                    var readinessOptions =
                        new ReadinessOptions
                        {
                            Timeout = EventStoreTestConstants.TestTimeout
                        };
                    return readinessOptions;
                });

        var container = builder.Build();
        _componentContext = container.Resolve<IComponentContext>();
    }

    protected override void When()
    {
        _sut = _componentContext.Resolve<IEventStore>();
    }

    [Fact]
    public void Then_It_Should_Not_Be_Null()
    {
        _sut.Should().NotBeNull();
    }
}

有什么线索吗? 我已经查看了 Autofac 的并发指南:https://autofaccn.readthedocs.io/en/latest/advanced/concurrency.html,但我认为我已经正确使用了组件上下文。

更新 1:仅供参考,这是我使用的 GivenThenWhen 模板。但这里没有什么特别的(我认为!)

namespace ToolBelt.TestSupport
{
    public abstract class Given_When_Then_Test
        : IDisposable
    {
        protected Given_When_Then_Test()
        {
            Setup();
        }

        private void Setup()
        {
            Given();
            When();
        }

        protected abstract void Given();

        protected abstract void When();

        public void Dispose()
        {
            Cleanup();
        }

        protected virtual void Cleanup()
        {
        }
    }
    
    public abstract class Given_WhenAsync_Then_Test
        : IDisposable
    {
        protected Given_WhenAsync_Then_Test()
        {
            Task.Run(async () => { await SetupAsync(); }).GetAwaiter().GetResult();
        }

        private async Task SetupAsync()
        {
            Given();
            await WhenAsync();
        }

        protected abstract void Given();

        protected abstract Task WhenAsync();

        public void Dispose()
        {
            Cleanup();
        }

        protected virtual void Cleanup()
        {
        }
    }
}

更新 2: 这是我用于实现和所有测试的 IoCC Autofac 注册方法。有点复杂,因为我使用反射来保持 EventStore 包装器完全通用,但以防万一有人在那里看到可能影响测试的时髦东西..

public static class EventStoreExtensions
{
    public static void RegisterEventStore(
        this ContainerBuilder builder,
        Func<IComponentContext, EventStoreOptions> optionsRetriever,
        Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever,
        Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever = null)
    {
        RegisterEventStoreConnection(builder, optionsRetriever);
        RegisterFactories(builder);
        RegisterEventStoreManager(builder);
        RegisterConverters(builder);
        RegisterResolvers(builder, customDomainEventMappersOptionsRetriever);
        RegisterEventStoreServices(builder);
        RegisterEventStoreReadinessCheck(builder, readinessOptionsRetriever);
    }

    private static void RegisterEventStoreReadinessCheck(
        ContainerBuilder builder,
        Func<IComponentContext, ReadinessOptions> readinessOptionsRetriever)
    {
        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();
                var readinessOptions = readinessOptionsRetriever.Invoke(ctx);

                var timeout = readinessOptions.Timeout;
                var eventStoreConnection = context.Resolve<IEventStoreConnection>();

                var eventStoreReadiness =
                    new EventStoreReadiness(
                        eventStoreConnection,
                        timeout);

                return eventStoreReadiness;

            })
            .As<IEventStoreReadiness>()
            .As<IReadiness>()
            .SingleInstance();
    }

    private static void RegisterEventStoreConnection(
        ContainerBuilder builder,
        Func<IComponentContext, EventStoreOptions> optionsRetriever)
    {
        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();
                var eventStoreOptions = optionsRetriever.Invoke(ctx);

                ConnectionSettings connectionSetting =
                    ConnectionSettings
                        .Create()
                        .KeepReconnecting();

                var urlString = eventStoreOptions.Url;
                var port = eventStoreOptions.Port;
                var ipAddress = IPAddress.Parse(urlString);
                var tcpEndPoint = new IPEndPoint(ipAddress, port);

                var eventStoreConnection = EventStoreConnection.Create(connectionSetting, tcpEndPoint);
                return eventStoreConnection;
            })
            .As<IEventStoreConnection>()
            .SingleInstance();
    }

    private static void RegisterFactories(
        ContainerBuilder builder)
    {
        builder
            .RegisterType<DomainEventFactory>()
            .As<IDomainEventFactory>()
            .SingleInstance();

        builder
            .RegisterType<EventDataFactory>()
            .As<IEventDataFactory>()
            .SingleInstance();

        builder
            .RegisterType<ExpectedVersionFactory>()
            .As<IExpectedVersionFactory>()
            .SingleInstance();

        builder
            .RegisterType<IdFactory>()
            .As<IIdFactory>()
            .SingleInstance();

        builder
            .RegisterType<StreamNameFactory>()
            .As<IStreamNameFactory>()
            .SingleInstance();

        builder
            .RegisterType<RetrievedEventFactory>()
            .As<IRetrievedEventFactory>()
            .SingleInstance();
    }

    private static void RegisterEventStoreManager(ContainerBuilder builder)
    {
        builder
            .RegisterType<EventStoreManager>()
            .As<IEventStoreManager>()
            .SingleInstance();
    }

    private static void RegisterConverters(ContainerBuilder builder)
    {
        builder
            .Register(context =>
            {
                var utf8Encoding = new BytesConverter(Encoding.UTF8);
                return utf8Encoding;
            })
            .As<IBytesConverter>()
            .SingleInstance();

        builder
            .RegisterType<NewtonsoftConverter>()
            .As<IJsonConverter>()
            .SingleInstance();
    }

    private static void RegisterResolvers(
        ContainerBuilder builder,
        Func<IComponentContext, CustomDomainEventMappersOptions> customDomainEventMappersOptionsRetriever)
    {
        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();
                var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);
                var domainEventSerializerDeserializers =
                    customDomainEventMappersOptions?.DomainEventSerializerDeserializers;
                var mapperResolver = MapperResolverFactory.Create(domainEventSerializerDeserializers);
                return mapperResolver;
            })
            .As<IMapperResolver>()
            .SingleInstance();

        builder
            .Register(context =>
            {
                var ctx = context.Resolve<IComponentContext>();

                var assembliesLoaded = AppDomain.CurrentDomain.GetAssemblies();
                var domainEventTypes =
                    assembliesLoaded
                        .SelectMany(s => s.GetTypes())
                        .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)
                                    && x.IsClass);
                var customDomainEventMappersOptions = customDomainEventMappersOptionsRetriever?.Invoke(ctx);
                var domainEventSerializerDeserializers =
                    customDomainEventMappersOptions?.DomainEventSerializerDeserializers;
                var typeResolver =
                    EventTypeResolverFactory.Create(
                        domainEventTypes,
                        domainEventSerializerDeserializers);
                return typeResolver;
            })
            .As<IEventTypeResolver>()
            .SingleInstance();
    }

    private static void RegisterEventStoreServices(ContainerBuilder builder)
    {
        builder
            .RegisterType<EventStoreRepository>()
            .As<IEventStoreRepository>();

        builder
            .RegisterType<DomainEventsPersister>()
            .As<IDomainEventsPersister>();

        builder
            .RegisterType<DomainEventsRetriever>()
            .As<IDomainEventsRetriever>();

        builder
            .RegisterType<EventStore>()
            .As<IEventStore>();
    }
}

更新 3: 这是整个存储库,以防有人感到无聊并想尝试复制它。它是一个通用的事件存储包装器,用于使用官方 C# 驱动程序为 Greg Young 的事件存储产品实现的事件源。

https://gitlab.com/DiegoDrivenDesign/DiDrDe.EventStore

有趣的是,这个问题似乎每隔一段时间就会消失。事实上,通常在重新启动 PC 后,所有测试都会正常通过。其他时候情况并非如此,所以我怀疑这与我在运行时加载程序集的事实有关,并且有些东西出了问题:(


2021 年 9 月 9 日更新 我所做的异步执行并不完全正确。有一种方法可以使用 xUnit 来接受异步执行,这可能在这种竞争条件中发挥了作用。这是我所有测试都继承自 given-when-then 模式的更好实现。感谢IAsyncLifetime,我能够异步(和等待)前提条件和操作本身。

using System.Threading.Tasks;
using Xunit;

namespace Rubiko.TestSupport.XUnit
{
    public abstract class Given_When_Then_Test_Async
        : IAsyncLifetime
    {
        public async Task InitializeAsync()
        {
            await Given();
            await When();
        }

        public async Task DisposeAsync()
        {
            await Cleanup();
        }

        protected virtual Task Cleanup()
        {
            return Task.CompletedTask;
        }

        protected abstract Task Given();

        protected abstract Task When();
    }
}

【问题讨论】:

  • 我尝试使用第三个参数(可选)调用RegisterEventStore,以防可选参数导致反射出现问题,但这也无济于事。
  • 你是the same guy吗?仅用于解决方案跟踪:)
  • 您是否在某处使用城堡动态代理?
  • 不是同一个人。动态代理必须由 Moq 或 Autofac 内部使用,或者..我没有直接使用它
  • 虽然 Moq 确实使用 DynamicProxy,但在您的代码中,也没有在异常堆栈跟踪中表明 Moq 涉及此处。在我看来,您更有可能使用的是一些与 DynamicProxy 集成的未命名 Autofac 扩展。

标签: c# autofac xunit castle-dynamicproxy eventstoredb


【解决方案1】:

DynamicProxyGenAssembly2 是一个临时程序集,由使用 CastleProxy 的模拟系统构建。 NSubstitute 和 Moq 也有一些类似的未解决问题,表明该问题是 Castle.Core 甚至 .Net Framework 中的竞争条件(有关详细信息,请参阅:TypeLoadException or BadImageFormatException under heavy multi-threaded proxy generation

【讨论】:

    【解决方案2】:

    内部异常表示无法加载类型

    无法从程序集“DynamicProxyGenAssembly2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null”加载类型“Castle.Proxies.IReadinessProxy”。

    它来自这一行:

    at System.Reflection.Assembly.GetTypes()
    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
    at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
    at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
    

    对应于代码中的这一行

    var domainEventTypes = assembliesLoaded
                              .SelectMany(s => s.GetTypes())
                              .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)
                                          && x.IsClass);
    

    这意味着其中一个程序集包含一个类型,该类型包含对另一个未加载的程序集的引用。根据错误类型的名称,它似乎与城堡自动生成的类型有关。

    您可以订阅静态的AppDomain.CurrentDomain.AssemblyResolveAppDomain.CurrentDomain.TypeResolve 事件,以更好地了解程序集未加载的原因,并可能手动加载它。请参阅Assembly.GetTypes() - ReflectionTypeLoadException 了解更多信息。

    在某些情况下,异常是“正常的”,可以使用如下代码忽略:

    public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
    {
        // TODO: Argument validation
        try
        {
            return assembly.GetTypes();
        }
        catch (ReflectionTypeLoadException e)
        {
            return e.Types.Where(t => t != null);
        }
    }
    

    来自How to prevent ReflectionTypeLoadException when calling Assembly.GetTypes()的代码

    你可以在这里使用它:

    var domainEventTypes = assembliesLoaded
                              .SelectMany(s => s.GetLoadableTypes())
                              .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)
                                          && x.IsClass);
    

    【讨论】:

    • 我会尽快尝试。所以你认为这与加载程序集的速度有关,而不是我在 autofac 中注册的方式?如前所述,它仅在并行运行所有测试时发生。谢谢
    • 与 Autofac 无关。异常来自GetTypes 方法。我猜您的 appdomain 中有缺少程序集的程序集。缺少的似乎与 Castle.Core 动态代理有关。您可以使用 AssemblyResolveTypeResolve AppDomain.Current 事件找到更多信息。
    【解决方案3】:

    如果您有 COM 依赖项,请确保在这些 COM 依赖项的单元测试项目中将 Embed Interop Types 设置为 No。默认情况下,此属性设置为 Yes。当您从这些 COM 对象模拟单元测试中的对象时,不应嵌入它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-13
      • 1970-01-01
      • 1970-01-01
      • 2023-03-09
      • 1970-01-01
      • 2013-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多