2

我遇到了很多天都无法解决的问题。我使用 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();
    }
}
4

3 回答 3

3

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

于 2019-05-30T08:56:39.830 回答
2

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

无法从程序集“DynamicProxyGenAssembly2,版本=0.0.0.0,文化=中性,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);
    }
}

调用 Assembly.GetTypes() 时如何防止 ReflectionTypeLoadException 的代码

你可以在这里使用它:

var domainEventTypes = assembliesLoaded
                          .SelectMany(s => s.GetLoadableTypes())
                          .Where(x => typeof(IDomainEvent).IsAssignableFrom(x)
                                      && x.IsClass);
于 2019-05-27T15:04:34.130 回答
0

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

嵌入互操作类型

于 2021-06-15T14:42:27.273 回答