首页 > 解决方案 > 在 Autofixture 中每次测试运行时生成相同的“随机”值

问题描述

我想使用 Autofixture 创建具有随机值的(复杂)类型。只要所有值都不同,我不关心创建数据的价值。我想让Autofixture在每次测试运行时创建相同的数据。

默认情况下,使用 Autofixture 的数据可能会导致测试有时通过有时失败的不稳定测试。

让它更清楚一点。这是我正在寻找的行为的一个例子。这也应该适用于复杂类型,但我没有在示例中实现。

[Fact]
public void AlwaysTheSameValues()
{
    var semiRandomValues = new SemiRandomValues();
    int value1 = semiRandomValues.GetRandomInt(); // Always 145
    int value2 = semiRandomValues.GetRandomInt(); // Always 163
    int value3 = semiRandomValues.GetRandomInt(); // Always 153

    string value4 = semiRandomValues.GetRandomString(); // Always "NFNWLYGHLPLYAVYQHUVY"
    string value5 = semiRandomValues.GetRandomString(); // Always "ARNXRNCELHYQTAJIXMRC"

    Assert.True(value2>50); // This wil never fail
}

[Fact]
public void DifferentValuesEachRun()
{
    var semiRandomValues = new Fixture();
    int value1 = semiRandomValues.Create<int>(); // Produces a different result in each run
    int value2 = semiRandomValues.Create<int>(); // Produces a different result in each run
    int value3 = semiRandomValues.Create<int>(); // Produces a different result in each run

    string value4 = semiRandomValues.Create<string>(); // Produces a different result in each run
    string value5 = semiRandomValues.Create<string>(); // Produces a different result in each run

    Assert.True(value2 > 50); // This wil sometimes fail
}

使用这个类

public class SemiRandomValues
{
    private readonly Random _random = new Random(0); // The 0 makes all value the same each run

    public int GetRandomInt()
    {
        return _random.Next(1, 200);
    }

    public string GetRandomString()
    {
        var chars = Enumerable
            .Range(1, 20)
            .Select(_ => (char)_random.Next(65, 90))
            .ToArray();
        return new string(chars);
    }
}

注意:我不想依赖随机值(故意),但使用默认的 Autofixture 行为,我可以(意外地)检查未失败的工作单元测试。之后,当同事稍后运行该测试时,它可能会失败。对于那个同事来说,也很难重现失败的测试,因为测试可以在以后成功。

标签: autofixture

解决方案


我做了一个例子来说明几种方法。一种方法是使用 DataAnnotations,另一种方法是创建自定义项,以确保您获得对您的情况有意义的默认值。

马克·西曼十多年前就写过这篇文章

void Main()
{
    var fixture = new Fixture().Customize(new MySpecimenBuilder().ToCustomization());
    for (int i = 0; i < 3; i++)
    {
        var foo = fixture.Create<Foo>();
        Console.WriteLine($"{i} Bar: {foo.Bar}, Baz: {foo.Baz}, Qux: {foo.Qux}");
        // Example output
        //0 Bar: 42, Baz: ABCDE983, Qux: 42
        //1 Bar: 42, Baz: ABCDE821, Qux: 42
        //2 Bar: 42, Baz: ABCDE974, Qux: 42
    }
}

public class Foo
{
    public int Bar { get; set; }

    [RegularExpression(@"ABCDE\d{3}")]
    public string Baz { get; set; }

    [Range(42, 42)]
    public int Qux { get; set; }
}

public class MySpecimenBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        return request switch
        {
            PropertyInfo pi when pi.Name == nameof(Foo.Bar) => 42,
            _ => new NoSpecimen()
        };
    }
}

推荐阅读