53

在开始之前,我是 AutoFixture 的忠实粉丝,我仍在学习如何使用该工具。因此,感谢开发 Autofixture 先生 Ploeh 和所有贡献者。

那么让我们从我的问题开始吧。

根据 AutoFixture/AutoMoq 忽略注入的实例/冻结模拟

上面链接的有趣部分给出了这段代码

Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);

ISettings settings = settingsMock.Object;
fixture.Inject(settings);

可以将其重写为 Mark 的答案

fixture.Freeze<Mock<ISettings>>()
       .Setup(s => s.Get(settingKey)).Returns(xmlString);

它看起来像一个语法糖,使用 Freeze 方法是一种在流畅的界面中编写模拟的创建、配置和在 autofixture 容器中注入的方法。

在网上做了一些研究之后,Freeze 和 Inject 之间实际上存在功能差异。我发现了这个问题: https ://github.com/AutoFixture/AutoFixture/issues/59 这指向了 如何在 AutoFixture 中冻结空实例的答案

上述链接的作者将 Freeze 方法描述如下:

在内部,Freeze 创建一个请求类型的实例(例如 IPayPalConfiguration),然后注入它,这样当您再次请求它时,它总是会返回该实例

我明白当我们这样做时

var customer = fixture.Freeze<Order>();

每当我们的代码请求 Order 类型时,它将始终使用相同的 Order 实例。但是,如果我在 Freeze 构造函数中指定我希望它使用特定实例怎么办?

这是一个小代码示例:

[Fact]
public void MethodeName()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    fixture.Freeze<OrderLine>(new OrderLine("Foo"));
    var order = fixture.Create<Order>();
}

public class Order
{
    private readonly OrderLine _line;

    public Order(OrderLine line)
    {
        _line = line;
    }
}
public class OrderLine
{
    private readonly string _name;

    public OrderLine(string name)
    {
        _name = name;
    }
}

OrderLine 的名称不应该等于“Foo”而不是 namefe48163a-d5a0-49a5-b349-7b11ba5f804b 吗?Freeze 方法的文档说:

<typeparam name="T">The type to freeze.</typeparam>
<param name="fixture">The fixture.</param>
<param name="seed">Any data that adds additional information when creating the anonymous object. Hypothetically, this value might be the value being frozen, but this is not likely.</param>

为什么作者不确定何时返回值?如果我在 Freeze 的构造函数中指定我的实例,我希望 autofixture 使用这个实例?

然后

请注意,除非您已自定义执行此操作,否则不太可能将其用作冻结值。如果您希望将特定值注入到 Fixture 中,则应使用该方法。`

看来我必须自定义种子参数。谁能澄清一下?文档指出的解决方案是使用 Inject 方法。事实上,它适用于我的 OrderLine 代码示例。

我正在寻求您的帮助,以了解 Freeze、Inj​​ect 和 Register 之间的区别,根据源代码,它只是由 Inject 方法调用,但它需要一个 lambda。

4

3 回答 3

67

注册并注入

曾几何时,没有Inject也没有FreezeRegister统治了代码。

那时,有一个Register重载是这样定义的:

public static void Register<T>(this IFixture fixture, T item)

但是,它必须与这个近亲共享 API:

public static void Register<T>(this IFixture fixture, Func<T> creator)

AutoFixture 的创建者认为这很好,但遗憾的是:用户感到困惑。最糟糕的是,用户可以写:

fixture.Register(() => universe.LightUp());

但是也

fixture.Register(universe.LightUp);

这意味着完全相同的事情,因为universe.LightUp是对方法的引用,因此与委托匹配。

但是,该语法看起来像属性引用,因此如果LightUp是属性而不是方法,编译器将选择第一个重载。

这引起了很多混乱,因此Register<T>(this IFixture fixture, T item)重载被重命名为Inject<T>(this IFixture fixture, T item).

冻结

冻结有不同的历史。很久以前,当我还在以命令式的方式使用 AutoFixture 时,我注意到我反复写了这样的代码:

var foo = fixture.Create<Foo>();
fixture.Inject(foo);

所以我决定这是一个概念并将其命名为Freeze。该Freeze方法只是这两行代码的简写。

我正在寻求您的帮助以了解 Freeze、Inj​​ect 和 Register 之间的区别,根据源代码,它只是由 Inject 方法调用,但它需要一个 lambda

Inject一般来说,区分和不应该太难Register,因为它们的签名不会冲突。因此,如果您尝试使用这两种方法之一来实现目标,并且您的代码可以编译,那么您可能选择了正确的版本。

Freeze如果不是 OP 中使用的重载,情况也是如此:

[EditorBrowsable(EditorBrowsableState.Never)]
public static T Freeze<T>(this IFixture fixture, T seed)

请注意,这个重载实际上有EditorBrowsableState.Never,因为它总是让人们感到困惑。然而,尽管如此,显然人们仍然发现过载,所以我认为它应该在 AutoFixture 4 中移动。这是存在的功能之一,因为它很容易实现......

于 2013-08-11T13:35:25.567 回答
18

Freeze, Inject,Register都在自定义创建算法。

使用Inject并且Register明确指定应该以特定方式创建对象,在您的示例中通过new OrderLine("Foo")手动提供。

由于Freeze您没有指定应如何创建对象 - 您要求 AutoFixture 为您提供一个实例。

最后,上述所有方法都使用相同的低级 API:

fixture.Customize<T>(c => c.FromFactory(creator).OmitAutoProperties());


之所以fixture.Freeze<OrderLine>(new OrderLine("Foo"));不创建OrderLine具有指定种子值的实例,是因为默认情况下会忽略种子

要支持特定类型的种子值,您可以创建一个SeedFavoringRelay<T>

public class SeedFavoringRelay<T> : ISpecimenBuilder where T : class
{
    public object Create(object request, ISpecimenContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        var seededRequest = request as SeededRequest;
        if (seededRequest == null || !seededRequest.Request.Equals(typeof(T)))
            return new NoSpecimen(request);

        var seed = seededRequest.Seed as T;
        if (seed == null)
            return new NoSpecimen(request);

        return seed;
    }
}

然后你可以像下面这样使用它:

fixture.Customizations.Add(
    new SeedFavoringRelay<OrderLine>());

fixture.Freeze<OrderLine>(new OrderLine("Foo"));
// -> Now fixture.Create<Order>() creates an Order with OrderLine's Name = "Foo".
于 2013-08-10T19:55:16.510 回答
1

我修改了你的测试(它目前没有断言任何东西,顺便说一句),如果你逐步执行它,你会看到一个OrderLine带有“Foo”的,因为它的私有 _line 成员值被注入到Order.

我有另一个版本的测试,我在其中添加了OrderLineinOrderNamein 的只读属性,OrderLine以便您可以对这些对象进行断言,但这既不是这里也不是那里。

此测试直接使用该方法设置夹具FromFactory,这有时可能会有所帮助:

[Fact]
public void MethodName()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    const string expected = "Foo";
    fixture.Customize<OrderLine>(o => o
        .FromFactory(() =>
            new OrderLine(expected)));
    var order = fixture.Create<Order>();
}
于 2013-08-10T21:01:20.897 回答