【问题标题】:Creating a struct with autofixture throws no public constructor error使用 autofixture 创建结构不会引发公共构造函数错误
【发布时间】:2012-10-17 09:17:07
【问题描述】:

我有一个结构,例如:

public struct Foo
{
    public int Bar;
    public string Baz;
}

并想在我的单元测试中使用 autofixture 创建它。我尝试使用以下内容:

IFixture fixture = new Fixture();
var f = fixture.CreateAnonymous<Foo>();

但这会引发AutoFixture was unable to create an instance 错误。是否可以使用 autofixture 自动创建结构?怎么可能做到这一点?请注意,我正在处理遗留代码,因此需要处理结构。 :)

编辑:

改变了

IFixture fixture = new Fixture().Customize(new AutoMoqCustomization());

IFixture fixture = new Fixture();

因为它与问题无关。

【问题讨论】:

  • 如果你去掉 AutoMoq 定制,它有什么不同吗?
  • 没什么区别。
  • 酷 - 把它排除在外,@Peter Porfy 可以消除他的猜测......
  • 通常情况下,您不需要AutoMoqCustomization。 (Foo 类型中没有可以使用 Moq 自动模拟的抽象类或接口。)
  • @PeterPorfy 模拟(/mock 代理)位 - AF Core 只是大量反射,依赖很少。 AutoMoqCustomization 所在的 AutoFixture.AutoMoq 扩展是唯一可以发挥作用的东西。 (并不是说这是一个错误的猜测或不合理的 - 只是如果 AMC 被取出它会在四肢上表明动态代理正在发挥作用)

标签: c# unit-testing autofixture


【解决方案1】:

这本质上是 C# 编译器如何处理值类型的人工制品。而the documentation seems to indicate otherwise,从Reflection的角度来看,Foo结构没有公共构造函数。

例如如果你执行这段代码:

var ctors = typeof(Foo).GetConstructors();

结果是一个空数组。

但是,这段代码可以编译:

var f = new Foo();

因此您可以争辩说 AutoFixture 应该能够创建 Foo 的实例。

但是,最后,mutable structs are evil 应该不惜一切代价避免。更好的选择是将 Foo 实现更改为:

public struct Foo
{
    public Foo(int bar, string baz)
    {
        this.Bar = bar;
        this.Baz = baz;
    }

    public readonly int Bar;
    public readonly string Baz;
}

如果你这样做,你现在不仅有一个(更)正确的值类型,而且 AutoFixture 还能够创建一个实例而无需进一步修改。

因此,这是GOOS 范式的一个很好的例子,你应该听你的测试。如果它们存在摩擦,则可能是有关您的生产代码的反馈。在这种情况下,这正是由于值类型实现存在缺陷而导致您即将自取其辱的反馈。

附:即使您像上面概述的那样“修复” Foo 结构,使其成为结构而不是类的意义何在?它仍然包含一个字符串(引用类型)字段,因此即使结构本身将存在于堆栈中,字符串字段仍将指向堆中的数据。


我已添加 this issue 作为 AutoFixture 的一个可能的新功能。

【讨论】:

  • 感谢您详尽的回答!我将向结构添加构造函数,因为这不会影响遗留代码,但出于同样的原因,我不能使它们不可变。至于结构,它们被遗留代码用作 DTO。由于在我看来类更适合这项工作,我正在尝试重构部分代码以使用类。但由于代码的其他部分仍将使用结构,因此我需要测试它们与替换类之间的转换。我希望我用这个做一个用例。 :) 如果功能得到实施,请回帖!
【解决方案2】:

blog post 中得到启发,我创建了一个实现 ISpecimenBuilder 的类

class FooBuilder : ISpecimenBuilder
{
  public object Create(object request, ISpecimenContext context)
  {
    var sr = request as SeededRequest;
    if (sr == null)
    {
        return new NoSpecimen(request);
    }
    if (sr.Request != typeof(Foo))
    {
        return new NoSpecimen(request);
    }

    var foo = new Foo();
    foo.Bar = context.CreateAnonymous<int>();
    foo.Baz = context.CreateAnonymous<string>();
    return foo;
  }
}

并将类添加为自定义

fixture.Customizations.Add(new FooBuilder());

导致对CreateAnonymous&lt;Foo&gt; 的调用正常工作。

如果有更多开箱即用的解决方案,请发布,我会接受它作为答案。

【讨论】:

  • +1 您也许可以使用AutoPropertiesCommand 中所示的this discussion 来自动分配 -
  • @anonympous Downvoter。你有什么愿意分享的理由吗?在没有更好的解决方案的情况下,这是一种可行的解决方法(我敢说它几乎是#most 惯用的,因为它以正确的方式挂钩管道)(尤其是其他泛化)