【问题标题】:AutoFixture: Configuring an Open Generics Specimen BuilderAutoFixture:配置一个开放的泛型样本生成器
【发布时间】:2012-04-22 23:04:42
【问题描述】:

我有一个使用开放泛型的对象模型(是的,是的,现在我有两个问题;这就是我在这里的原因:) :-

public interface IOGF<T>
{
}

class C
{
}

class D
{
    readonly IOGF<C> _ogf;

    public D( IOGF<C> ogf )
    {
        _ogf = ogf;
    }
} 

我正在尝试让 AutoFixture 生成上述 D 的匿名实例。然而,就其本身而言,AutoFixture 没有内置策略来构建 IOGF&lt;&gt;,因此我们观察到:

public class OpenGenericsBinderDemo
{
    [Fact]
    public void X()
    {
        var fixture = new Fixture();

        Assert.Throws<Ploeh.AutoFixture.ObjectCreationException>( () =>
            fixture.CreateAnonymous<D>() );
    }

基本信息是:

Ploeh.AutoFixture.ObjectCreationException : AutoFixture 无法从 IOGF`1[C] 创建实例,很可能是因为它没有公共构造函数,是抽象或非公共类型。

我很高兴为它提供一个具体的实现:

public class OGF<T> : IOGF<T>
{
    public OGF( IX x )
    {
    }
}

public interface IX
{
}

public class X : IX
{
}

还有一个关联的绑定:

fixture.Register<IX,X>();

我如何(或者我什至应该这样看待问题??)使以下测试通过?

public class OpenGenericsLearning
{
    [Fact]
    public void OpenGenericsDontGetResolved()
    {
        var fixture = new Fixture();
        fixture.Inject<IX>( fixture.Freeze<X>() );

        // TODO register or do something that will provide 
        //      OGF<C> to fulfill D's IOGF<C> requirement

        Assert.NotNull( fixture.CreateAnonymous<D>());
    }
}

(在 codeplex 网站上有关于此的讨论和问题 - 我只需要快速了解一下,如果这只是一个坏主意和/或我遗漏了什么,我愿意删除它)

编辑 2:(另请参阅对 Mark 的回答的评论)这里的(诚然人为的)上下文是对大型“几乎完整系统”系统被测对象图的验收测试,而不是小型(受控/易于理解 :)单元或集成测试场景中的一对或三组类。正如自我问题的括号陈述中提到的那样,我并不完全相信这种类型的测试是否有意义。

【问题讨论】:

  • AutoMoq、AutoRhinoMocks 和 AutoFakeItEasy 是允许您将 AutoFixture 用作自动模拟容器的扩展。这是一个选项吗? (因为这样您就可以成功创建 D 的匿名实例。)
  • @Nikos 我知道自动模拟扩展并在心理上排除了它们 - 应该提到这一点;有关更多背景信息,请参阅我对 Mark 的回答的 cmets)。把这个评论和马克的回答放在心上,我会加倍努力避免依赖我的拐杖!
  • @RubenBartelink 很好奇,这个开放的通用代码是用来做什么的?我喜欢人们使用开放的泛型来解决问题,但是泛型的 C# 不规则类型系统,带有极端情况和陷阱,有时让我觉得付出的努力是不值得的。
  • @JohnZabroski Ha,我现在不记得了 - 使用一些通用存储库反模式测试东西?!

标签: c# generics autofixture open-generics


【解决方案1】:

您可以创建一个自定义,其工作方式如下:

public class AnOpenGenericsBinderDemo
{
    [Fact]
    public void RegisteringAGenericBinderShouldEnableResolution()
    {
        var fixture = new Fixture();
        fixture.Inject<IX>( fixture.Freeze<X>() );
        fixture.RegisterOpenGenericImplementation( typeof( IOGF<> ), typeof( OGF<> ) );

        Assert.IsType<OGF<C>>( fixture.CreateAnonymous<D>().Ogf );
    }
}

并且是这样实现的:

public static class AutoFixtureOpenGenericsExtensions
{
    public static void RegisterOpenGenericImplementation( this IFixture that, Type serviceType, Type componentType )
    {
        if ( !serviceType.ContainsGenericParameters )
            throw new ArgumentException( "must be open generic", "serviceType" );
        if ( !componentType.ContainsGenericParameters )
            throw new ArgumentException( "must be open generic", "componentType" );
        // TODO verify number of type parameters is 1 in each case
        that.Customize( new OpenGenericsBinderCustomization( serviceType, componentType ) );
    }

    public class OpenGenericsBinderCustomization : ICustomization
    {
        readonly Type _serviceType;
        readonly Type _componentType;

        public OpenGenericsBinderCustomization( Type serviceType, Type componentType )
        {
            _serviceType = serviceType;
            _componentType = componentType;
        }

        void ICustomization.Customize( IFixture fixture )
        {
            fixture.Customizations.Add( new OpenGenericsSpecimenBuilder( _serviceType, _componentType ) );
        }

        class OpenGenericsSpecimenBuilder : ISpecimenBuilder
        {
            readonly Type _serviceType;
            readonly Type _componentType;

            public OpenGenericsSpecimenBuilder( Type serviceType, Type componentType )
            {
                _serviceType = serviceType;
                _componentType = componentType;
            }

            object ISpecimenBuilder.Create( object request, ISpecimenContext context )
            {
                var typedRequest = request as Type;
                if ( typedRequest != null && typedRequest.IsGenericType && typedRequest.GetGenericTypeDefinition() == _serviceType )
                    return context.Resolve( _componentType.MakeGenericType( typedRequest.GetGenericArguments().Single() ) );
                return new NoSpecimen( request );
            }
        }
    }
}

我假设有人有比这更好的实现和/或有一个内置的实现。

编辑:以下是更新后的 D 与感测属性:

class D
{
    readonly IOGF<C> _ogf;

    public D( IOGF<C> ogf )
    {
        _ogf = ogf;
    }

    public IOGF<C> Ogf
    {
        get { return _ogf; }
    }
}

【讨论】:

  • 我遇到了一个异常:System.ArgumentException:“Ploeh.AutoFixture.Kernel.OmitSpecimen”类型的对象无法转换为“SelogerCity.Data.Entities.User”类型。与上述定制。类型应该是 ISet 或 IList... 使这个库有用的方法。
  • @Sam 抱歉,直到现在才看到。你有没有失败的测试? (我最终使用了这段代码,它迄今为止满足了我的需求,但我不知道你在做什么,因此无法真正开始猜测我的代码中的哪个演员或比较需要改进)
  • 太棒了,在 Rx 库中对 IObservable&lt;&gt; -&gt; Subject&lt;&gt; 工作正常
【解决方案2】:

AFICT 看不到开放的泛型。 D 依赖于 IOGF&lt;C&gt;,这是一个构造类型。

错误消息不是因为开放泛型,而是因为IOGF&lt;C&gt; 是一个接口。

您可以像这样从IOGF&lt;C&gt;OGF&lt;C&gt; 提供a mapping

fixture.Register<IOGF<C>>(() => fixture.CreateAnonymous<OGF<C>>());

由于OGF&lt;C&gt; 依赖于IX,您还需要提供到X 的映射:

fixture.Register<IX>(() => fixture.CreateAnonymous<X>());

这应该可以解决问题。

但是,正如 Nikos Baxevanis 在他的评论中指出的那样,如果您使用三个提供的自动模拟扩展之一,这基本上可以开箱即用 - 例如

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var d = fixture.CreateAnonymous<D>();

【讨论】:

  • +1 谢谢马克。我想这个问题有点做作,我没有投入足够的精力让它变得可信。我所追求的是能够匹配 DI 框架(和约定扩展)的开放通用绑定功能的愿望。这源于我/我们在验收和集成测试中大量使用 AF 的事实,其中构建各种复杂的上下文类和助手的能力大放异彩(但对于过度复杂的测试也是一个滑坡) .您的观点说得很好,在任何明智的单元测试中都不需要这种绑定。
  • (我知道您引用的大部分内容,并同意不无偿提供采用`Type 的重载(例如Register)的设计决定,这可能会使这样更整洁。有可能通过重新设计存在这种愿望的测试上下文类/装置来解决实际问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-29
  • 1970-01-01
  • 1970-01-01
  • 2013-04-09
相关资源
最近更新 更多