Autofixture: 基于定制的随机样本生成方法

创建于 2018-01-08  ·  11评论  ·  资料来源: AutoFixture/AutoFixture

嗨,我希望能够使用ISpecimenBuilder.CreateMany基于ICustomization生成不同的值。 我想知道最好的解决方案是什么,因为 AutoFixture 将为所有实体生成相同的值。

```c#
公共类 FooCustomization : ICustomization
{
公共无效定制(IFixture装置)
{
var 标本 = fixture.Build()
.OmitAutoProperties()
.With(x => x.CreationDate, DateTime.Now)
.With(x => x.Identifier, Guid.NewGuid().ToString().Substring(0, 6)) // 应该生成不同的值
.With(x => x.Mail, $"[email protected]")
。创建();

        fixture.Register(() => specimen);
}

}
``

PS.:主要目标是减少我的测试代码量,所以我必须避免调用With()来自定义每个测试的值。 有没有正确的方法来做到这一点?

更新:我刚刚从@ploeh读到了这个回复: https :
这种方法有很多缺点:首先,调用Create<List<Foo>>()似乎真的违反直觉,因为它有点违背了对CreateMany<Foo>期望; 这将生成一个硬编码大小的列表> (?)。 另一个缺点是我必须为每个实体进行两次自定义; 一个创建自定义集合,另一个创建单个实例,因为我们正在覆盖Create<T>的行为来创建一个集合。

question

所有11条评论

这似乎是Stack Overflow问题的一个很好的候选者。 在这里提出问题并没有错,但我认为这个问题的答案也可能使其他人受益,而且与 GitHub 问题相比,在 Stack Overflow 上更容易找到问题和答案。 一方面,谷歌在索引 Stack Overflow 问题方面做得很好,而它似乎将(关闭的)GitHub 问题排在较低的位置……

@ploeh我同意你的看法....
我刚刚基于此问题创建了一个 SO 问题。 这是未来参考

@ploeh感谢您回答关于 SO 的问题:脸红:

@thiagomajesk实际上,在这里回答问题没有问题。 Mark 建议 SO 的原因是它提供的搜索功能。 目前马克也在监控 SO,所以如果你在那里发布问题,有更多机会获得他的专业知识(就像你这次得到的一样):sweat_smile:

@zvirja感谢您澄清这一点😄

@zvirja我将根据@ploeh的精彩回答结束关于 SO 的问题。
但我想请教一下关于这个实现细节的更多信息。
因为我不想离题更远,我认为最好在这里询问这个细节。 所以我有这个实现:

```c#
公共类 UniqueShortGuidBuilder: ISpecimenBuilder
{
私有只读字符串 propName;
私有只读长度;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression)expr.Body).Member.Name;
    this.lenght = lenght;
}

公共对象创建(对象请求,ISpecimenContext 上下文)
{
var pi = 请求为 PropertyInfo;

if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
    return new NoSpecimen();

return Guid.NewGuid().ToString().Substring(0, lenght);

}
``

我期望在使用 AutoFixture 创建许多对象时, ISpecimenBuilder.Create会被多次调用,为我的实体生成不同的 Guid; 但显然这不是真的,他们实际上共享同一个。

好吧,我刚刚测试了你的代码,它完全符合预期:

```c#
公开课 Foo
{
公共字符串 PropertyToCustomize { 获取; 放; }
}

公共类 UniqueShortGuidBuilder: ISpecimenBuilder
{
私有只读字符串 propName;
私有只读长度;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression) expr.Body).Member.Name;
    this.lenght = lenght;
}

public object Create(object request, ISpecimenContext context)
{
    var pi = request as PropertyInfo;

    if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
        return new NoSpecimen();

    return Guid.NewGuid().ToString().Substring(0, lenght);
}

}

[事实]
public void TestCustomization()
{
var fixture = new Fixture();
fixture.Customizations.Add(新的UniqueShortGuidBuilder(x => x.PropertyToCustomize, 5));

var manyFoos = fixture.CreateMany<Foo>(10);

var uniqueIds = manyFoos
    .Select(x => x.PropertyToCustomize)
    .Where(x => x.Length == 5)
    .Distinct()
    .ToArray();
Assert.Equal(10, uniqueIds.Length);

}
``

因此,我想再次请您看看您的实际代码有何不同,以便我们了解为什么这种差异很重要 😟

@zvirja这很奇怪。 下面的代码是我遇到的问题的最小重现。
如果我删除Fixture.Customize(new FooCustomization());测试将通过,否则不会。

```c#
使用系统;
使用 Microsoft.VisualStudio.TestTools.UnitTesting;
使用 AutoFixture.Kernel;
使用 System.Linq.Expressions;
使用 System.Reflection;
使用 AutoFixture;
使用 System.Linq;

命名空间 UnitTestProject1
{
公开课 Foo
{
公共字符串 PropertyToCustomize { 获取; 放; }
}

public class FooCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var specimen = fixture.Build<Foo>()
            .OmitAutoProperties()
            .With(x => x.PropertyToCustomize)
            .Create();

        fixture.Register(() => specimen);
    }
}

public class UniqueShortGuidBuilder<TEntity> : ISpecimenBuilder
{
    private readonly string propName;
    private readonly int lenght;

    public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
    {
        propName = ((MemberExpression)expr.Body).Member.Name;
        this.lenght = lenght;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;

        if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
            return new NoSpecimen();

        return Guid.NewGuid().ToString().Substring(0, lenght);
    }
}

[TestClass]
public class UnitTest1
{
    public static Fixture Fixture { get; private set; }

    [ClassInitialize]
    public static void ClassInitialize(TestContext context)
    {
        Fixture = new Fixture();
        Fixture.Customizations.Add(new UniqueShortGuidBuilder<Foo>(x => x.PropertyToCustomize, 5));
        Fixture.Customize(new FooCustomization());
    }

    [TestMethod]
    public void TestMethod1()
    {
        var manyFoos = Fixture.CreateMany<Foo>(10);

        var uniqueIds = manyFoos
            .Select(x => x.PropertyToCustomize)
            .Where(x => x.Length == 5)
            .Distinct()
            .ToArray();

        Assert.AreEqual(10, uniqueIds.Length);
    }
}

}
``

建议:如果您依赖于从 GUID 字符串中取出前 _n_ 个字符,请不要将类命名为UniqueShortGuidBuilder 。 当长度为 5 时,这些值可能相当独特,但如果将其设置为 1,您可能会看到冲突。 在任何情况下,都不能保证唯一性。

如果你只关心字符串的长度,加上非保证的唯一性,为什么不简单地向 AutoFixture 询问一个介于 0 和 99,999 之间的数字,然后把它变成一个字符串? IIRC,数字在范围耗尽之前是唯一的。

如果我删除 Fixture.Customize(new FooCustomization()); 测试会通过,否则不会。

好吧,其实如果你仔细查看情况,这就是预期的行为😅请看在#962中的FooCustomization创建了Foo类型的实例,并将夹具配置为始终返回该实例。 因此,当您稍后创建Foo类型的多个实例时,每次都返回相同的对象并且不会进一步调用您的构建器。

如果您希望每次请求Foo时都创建一个新实例,请使用我在这里建议的Customize<> API。

@ploeh感谢您的提示,但该代码只是用于测试目的的粗略示例。 对于实际情况,长度不会短到会影响 Guids 的唯一性😄。

如果你只关心字符串的长度,加上非保证的唯一性,为什么不简单地向 AutoFixture 询问一个介于 0 和 99,999 之间的数字,然后把它变成一个字符串?

我敢打赌,对于大多数情况来说,这是一个不错的解决方案,但由于我的系统中的字母数字验证,我无法针对这种特定情况执行此操作。 除此之外,我还有涉及该领域的更复杂的验证。

好吧,事实上,如果您更仔细地查看情况,这就是预期的行为

@zvirja在您在 #962 中回复后很清楚。 但对我来说,这种行为并不明显,因为没有关于 AutoFixture 的前期(明确)文档(就像我在这里指出的那样)😁。

最后,感谢你们的耐心和所有出色的解释。
我相信这对未来的其他人也很有用😉。

好吧,很公平,我可能倾向于把事情看得太字面化 😄

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

相关问题

zvirja picture zvirja  ·  4评论

tiesmaster picture tiesmaster  ·  7评论

josh-degraw picture josh-degraw  ·  4评论

gtbuchanan picture gtbuchanan  ·  3评论

malylemire1 picture malylemire1  ·  7评论