Autofixture: 请求:向 IFixture 添加克隆方法

创建于 2020-09-24  ·  9评论  ·  资料来源: AutoFixture/AutoFixture

介绍

我想如果IFixture有一个Clone方法或类似的东西来创建夹具的副本。

目前我可以创建一个扩展方法来做到这一点:

public static IFixture Clone( this IFixture fixture)
{
   var cloneFixture = new Fixture();

   cloneFixture.Behaviors.Clear();
   foreach ( var behavior in fixture.Behaviors)
   {
      cloneFixture.Behaviors.Add( behavior );
   }

   cloneFixture.Customizations.Clear();
   foreach ( var customization in fixture.Customizations )
   {
      cloneFixture.Customizations.Add( customization );
   }

   cloneFixture.OmitAutoProperties = fixture.OmitAutoProperties;
   cloneFixture.RepeatCount = fixture.RepeatCount;

   cloneFixture.ResidueCollectors.Clear();
   foreach ( var residueCollector in fixture.ResidueCollectors )
   {
      cloneFixture.ResidueCollectors.Add( residueCollector );
   }

   return cloneFixture;
}

但我觉得这种复制的能力应该在项目本身中。

细节

我的场景是我在测试中构造了一个带有很多参数的类的几个实例。 此外,我希望构造函数参数为一个实例的某个值和不同实例的不同构造函数值。 有几种方法可以做到这一点; 我一直在这样做的方式是:

fixture.Customizations.Add( new FilteringSpecimenBuilder(
            new FixedBuilder( mediaId ),
            new ParameterSpecification( 
                typeof( ObjectId ), 
                "mediaId" ) ) );
var mediaVM = fixture.Build<MediaViewModel>()   
                     .With( mvm => mvm.ParentMixerId, mixerId )
                     .With( mvm => mvm.TrackId, trackId )
                     .Create();

_ = fixture.Customizations.Remove( fixture.Customizations.Last() );

//...

去掉自定义的原因是我试过没有想过可能最后添加的自定义优先级更高,会被使用; 但事实并非如此。

如果我尝试使用某种扩展方法来简化它,如下所示:

public static IFixture AddCustomization<T>( this IFixture fixture, T value, string name )
{
   fixture.Customizations.Add( new FilteringSpecimenBuilder(
         new FixedBuilder( value ),
         new ParameterSpecification(
            typeof( T ),
            name ) ) );
   return fixture;
}

它可能在某些地方有效,但在其他地方无效,因为在构建对象后,夹具包含我不再需要的定制,我需要删除。

我不希望有一行删除添加的自定义项。 因此,如果相反,我的扩展方法制作了我的装置的副本,我将修改添加到该副本,那么我的创作会按预期工作,并且原始装置不会受到影响。

还可以添加在创建对象后自动删除的自定义。

希望这一切都有意义。

感谢您考虑我的问题 :smiley:

feature request

最有用的评论

@ajorians由于不活动,我正要关闭这个问题,但我无法走过那个巨大的构造函数。
我不得不问,你考虑过改变你的设计吗?

通过查看您的构造函数,我可以看到您的问题的几个问题和潜在的解决方案。

首先,您正在混合可新实体和可注入实体的概念。
最直接的解决方案是将可注入的依赖项(又名服务接口)移动到属性并使用属性注入来注入它们。
这将解决您必须复制Fixture的直接问题,并使构造函数更易于管理。

其次是解决过度注入代码的味道。
这意味着将最常用的一起服务移到单独的抽象中。
这应该有助于您的设计尊重 SRP,并且通过扩展它应该减少您目前在测试中必须进行的大量设置。

由于该类名为ViewModel我假设您有一个 MVVM 应用程序。 您可能可以通过引入消息传递模型来进一步解耦您的应用程序。 也许是一个事件聚合器。 大多数 MVVM 框架都内置了它们,因此您无需进行大量设置工作即可从中受益。

如果这有帮助,请告诉我。

所有9条评论

@ajorians美好的一天! 从我对你的例子的理解来看,听起来我们已经有了你需要的东西。 Fixture 的Build()方法实际上是创建内部图的不可变副本,因此您可以在 AutoFixture 之上应用“一次性”自定义,而无需保留它们。

查看此游乐场演示:

```c#
公共类模型
{
公共 int Id { 获取; 放; }
公共字符串名称{获取; 放; }
}

[事实]
公共无效演示()
{
var fixture = new Fixture();
夹具.定制(c => c.With(m => m.Id, 42));

var value1 = fixture.Create<Model>();
Assert.Equal(42, value1.Id);
Assert.NotNull(value1.Name);

var value2 = fixture.Build<Model>()
    .With(m => m.Id, (int genInt) => genInt * 2 /* make it always even */)
    .Without(x => x.Name)
    .Create();
Assert.NotEqual(42, value2.Id);
Assert.Equal(value2.Id % 2, 0);
Assert.Null(value2.Name);

var value3 = fixture.Create<Model>();
Assert.Equal(42, value3.Id);
Assert.NotNull(value3.Name);

}
```

让我们知道它是否能解决您的需求。 如果没有,请更具体地描述为什么它对您不起作用,以便我们可以尝试提供帮助。

谢谢!

@zvirja

再会!

如果允许我的话; 我们的Model类没有set并且只能通过构造函数设置。 所以如果你用这个代替它:

public class Model
{
   public Model( int id, string name )
   {
      Id = id;
      Name = name;
   }

   public int Id { get; }
   public string Name { get; }
}

抱歉,最初没有指定。

因此,据我所知,我必须添加自定义才能执行构造函数参数。 这些自定义设置在Build()

如果对我们在构造函数中做了很多并且没有设置器的一些原因感兴趣,那么我们可以使用readonly关键字并确保成员变量已设置并且在方法出现时不能取消设置叫。

希望这更有意义。 感谢您的帮助! :笑脸:

@ajorians我是否正确理解您的问题,您有一个包含大量输入参数的模型,因此您只想自定义其中一个,而不必明确担心它。 对?

否则,你总是可以写一些类似的东西
c# var value2 = fixture.Build<Model>() .FromFactory((int id, string name) => new Model(id, name)) .Create();

不幸的是,随着参数数量的增加,它不能很好地扩展😟

@ajorians我是否正确理解您的问题,您有一个包含大量输入参数的模型,因此您只想自定义其中一个,而不必明确担心它。 对?

是的,完全正确。

不幸的是,随着参数数量的增加,它不能很好地扩展😟

幸运的是, Freeze适用于我们许多参数越来越多的类。 但是是的,我想到的使用自定义来执行构造函数参数的类目前有多个30参数:
image

并且可能会在今年年底之前增加4左右。

我知道这可能不寻常。

但是是的,我有大量不断增长的输入构造函数参数,我只想自定义其中一个(或几个)而不考虑其余的。 无需在整个测试中注册或冻结该类型。 以简洁的方式添加自定义大约是5行代码。

幸运的是,我能够做到这一切; 但我这样做的方式涉及在我的客户端代码中创建一个clone扩展方法。

希望这一切都有意义。 谢谢你和我一起看这个 :smiley:

@ajorians由于不活动,我正要关闭这个问题,但我无法走过那个巨大的构造函数。
我不得不问,你考虑过改变你的设计吗?

通过查看您的构造函数,我可以看到您的问题的几个问题和潜在的解决方案。

首先,您正在混合可新实体和可注入实体的概念。
最直接的解决方案是将可注入的依赖项(又名服务接口)移动到属性并使用属性注入来注入它们。
这将解决您必须复制Fixture的直接问题,并使构造函数更易于管理。

其次是解决过度注入代码的味道。
这意味着将最常用的一起服务移到单独的抽象中。
这应该有助于您的设计尊重 SRP,并且通过扩展它应该减少您目前在测试中必须进行的大量设置。

由于该类名为ViewModel我假设您有一个 MVVM 应用程序。 您可能可以通过引入消息传递模型来进一步解耦您的应用程序。 也许是一个事件聚合器。 大多数 MVVM 框架都内置了它们,因此您无需进行大量设置工作即可从中受益。

如果这有帮助,请告诉我。

@aivascu

@ajorians由于不活动,我正要关闭该问题

如果有什么我可以做的,请告诉我。 作为一个功能请求,在实施之前我可以看到它可能有相当多的不活动。

我不得不问,你考虑过改变你的设计吗?

目前我们正在考虑成为棱镜应用程序。 这将有助于您提到的一些项目。 但遗憾的是,我没有看到我们改变了将所有东西都放在构造函数中的理念,这样它就可以检查一次null并且是readonly并且总是非null在整个类的生命周期中。 我们需要做点什么。 但我不认为这会在明年左右有所改善。

如果这有帮助,请告诉我。

所有这些都有帮助。 我会让我的团队知道,即使是世界上的人也认为我们需要考虑做出改变。

但是,如果我问IFixture使用Clone或类似方法是否合理? 或者以某种方式允许人们添加自定义,然后pop最后添加的自定义关闭或以某种方式返回添加自定义之前的状态? 我同意理解引发这种期望行为的场景很重要,而且这个场景首先肯定有很多问题。 而且我确实看到您不想增加额外的复杂性,除非有一个好的用例。 好吧,你可以选择接下来会发生什么,但如果我能帮忙,请告诉我。

感谢阅读和考虑 :smiley:

@ajorians我的意见是我们不应该在Fixture类中添加.Clone()方法。 在我看来,任何单个测试在任何给定时间都应该只有一个测试夹具。

过去有一些要求实现解冻/弹出/冻结一次功能,但没有人必须实现它。
我认为原因是没有简单的方法可以可靠地确定哪个构建器将返回冻结值或最后注入的值是什么。

您可以做的是实现一个中继,在一定数量的成功解析后忽略请求,如下所示。 您还可以更新实现以跳过一定数量的请求。 其余部分应包含在您在原始帖子中使用的自定义内容中。

public class CountingRelay : ISpecimenBuilder
{
    public CountingRelay(ISpecimenBuilder builder, int maxCount)
    {
        this.MaxCount = maxCount;
        this.Builder = builder;
    }

    public int Count { get; private set; }
    public int MaxCount { get; }
    public ISpecimenBuilder Builder { get; }

    public object Create(object request, ISpecimenContext context)
    {
        if (this.Count == this.MaxCount) return new NoSpecimen();
        var result = this.Builder.Create(request, context);
        if (!(result is NoSpecimen)) this.Count++;
        return result;
    }
}

@aivascu

@ajorians我的意见是我们不应该在Fixture类中添加.Clone()方法。

好的。 嗯,谢谢你的考虑。

您可以做的是实现一个中继,在一定数量的成功解析后忽略请求

我会看一看。

再次感谢! :笑脸:

@ajorians我已经创建了一个更正式的功能请求 #1214,用于显式和自动自定义重置。 您可以在那里跟踪此功能的进度。

此页面是否有帮助?
0 / 5 - 0 等级