7

我之前在 SO 上问过一个类似的问题,我得到了答案。当时,为了方便起见,我机械地应用了答案,但现在我试图了解声明式设置夹具的机制是如何的。

因此,我目前正在查看Mark Seemann 的在没有公共构造函数的情况下处理类型的博客文章并将其转换为声明性的。它与我的原始查询非常相似,但我无法让它工作。请注意,给出的代码实际上并不是生产代码,这是一个学习练习。

现在,如果它有帮助,我已经在 GitHub 上 获得了命令式代码,并且有问题的代码在下面复制:

[Fact]
public static void CanOverrideCtorArgs()
{
    var fixture = new Fixture();

    var knownText = "This text is not anonymous";
    fixture.Register<int, IMyInterface>( i => new FakeMyInterface( i, knownText ) );

    var sut = fixture.Create<MyClass>();
}

这是与本文中给出的代码相似的代码

因此,我的问题是我应该知道/阅读什么才能将这段命令式代码转换为声明性的

4

2 回答 2

6

首先,我将在 OP 的 GitHub 存储库TypesWithoutPublicCtrs定义的假设下回答这个问题:

public class TypesWithoutPublicCtrs
{
    private readonly IMyInterface _mi;

    public TypesWithoutPublicCtrs(IMyInterface mi)
    {
        _mi = mi;
    }
}

我明确指出这一点的原因是因为这个名字是一个红鲱鱼:它确实有一个公共构造函数;它只是没有默认构造函数。

无论如何,AutoFixture 很容易处理默认构造函数的缺失。这里的问题不是TypesWithoutPublicCtrs类本身,而是IMyInterface接口。接口是有问题的,因为它们根本无法初始化。

因此,您需要以某种方式将接口映射到具体类。有多种方法可以做到这一点。

一次性解决方案

偶尔,我会使用这种一次性解决方案,尽管我觉得它很难看。但是,它很容易,不需要很多复杂的设置。

[Theory, AutoData]
public void TestSomething(
    [Frozen(As = typeof(IMyInterface))]FakeMyInterface dummy,
    TypesWithoutPublicCtrs sut)
{
    // use sut here, and ignore dummy
}

这不是特别好,因为它依赖于属性的副作用[Frozen],但它可以作为一个独立的一次性解决方案。

习俗

但是,我更喜欢对其进行约定,以便相同的约定适用于测试套件中的所有测试。使用这种约定的测试可能如下所示:

[Theory, MyTestConventions]
public void TestSomething(TypesWithoutPublicCtrs sut)
{
    // use sut here; it'll automatically have had FakeMyInterface injected
}

[MyTestConventions]属性可能如下所示:

public class MyTestConventionsAttribute : AutoDataAttribute
{
    public MyTestConventionsAttribute() :
        base(new Fixture().Customize(new MyTestConventions())
    {}
}

MyTestConventions必须实现接口ICustomization。您可以通过多种方式映射IMyInterfaceFakeMyInterface; 这是一个:

public class MyTestConventions : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(
            new TypeRelay(typeof(IMyInterface), typeof(FakeMyInterface)));
    }
}

自动模拟

但是,您可能厌倦了必须创建和维护所有这些 Fakes,因此您也可以将 AutoFixture 变成Auto-Mocking Container。有多种选择可以做到这一点,利用 MoqNSubstituteFakeItEasyRhino Mocks 。

于 2013-05-24T11:21:58.057 回答
6

去阅读

有关自定义的出色示例以及如何打包、混合和混合它们。

主要原则是使您的自定义尽可能细化。

然后,您需要通过以下任一方式将它们输入处理管道:

  • AutoData- 全局东西的派生属性(即像MyTestConventions马克的回答)
  • CustomizeWith助手 [1] 或类似的
  • 诡计如做[Freeze( As ... )]

我的实现

自动化这个,我会写:

[Theory, AutoData]
public static void OutOfBandCustomization( 
    [CustomizeWith( typeof( MyFakerCustomization ) )] MyClass sut )
{
}

使用此自定义:

public class MyFakerCustomization : ICustomization
{
    void ICustomization.Customize( IFixture fixture )
    {
        var knownText = "This text is not anonymous";
        fixture.Register<int, IMyInterface>( i => 
            new FakeMyInterface( i, knownText ) );
    }
}

显然注册 astring和/或 usingAutoMoqCustomization也可能有用。

我的一般帮手

[1]是这个助手属性( Adam JasinskiCustomizeWith的帽子提示):

[AttributeUsage( AttributeTargets.Parameter, AllowMultiple = true )]
sealed class CustomizeWithAttribute : CustomizeAttribute
{
    readonly Type _type;

    public CustomizeWithAttribute( Type customizationType )
    {
        if ( customizationType == null )
            throw new ArgumentNullException( "customizationType" );
        if ( !typeof( ICustomization ).IsAssignableFrom( customizationType ) )
            throw new ArgumentException( 
                "Type needs to implement ICustomization" );
        _type = customizationType;
    }

    public override ICustomization GetCustomization( ParameterInfo parameter )
    {
        return (ICustomization)Activator.CreateInstance( _type );
    }
}

在旁边

提示一:你可以表达

fixture.Register<int, IMyInterface>( i => 
    new FakeMyInterface( i, knownText ) );

作为

 fixture.Customize<IMyInterface>(c =>c.FromFactory((int i)=>
     new FakeMyInterface(i,knownText)));

也。虽然这不会让您的案例变得更容易,但它是一种更通用的自定义正在发生的事情的方式。

在内部,Register是[当前]:

fixture.Customize<T>(c => c.FromFactory(creator).OmitAutoProperties());
于 2013-05-24T12:59:09.890 回答