将复杂参数传递给[理论]

zch*_*pit 83 c# unit-testing xunit xunit.net

Xunit有一个很好的功能:你可以用Theory属性创建一个测试并将数据放在InlineData属性中,xUnit将生成许多测试,并测试它们.

我想有这样的事情,但参数我的方法不是"简单的数据"(如string,int,double),但我的类的列表:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }
Run Code Online (Sandbox Code Playgroud)

que*_*atl 122

xxxxDataXUnit中有许多属性.查看例如PropertyData属性.

您可以实现返回的属性IEnumerable<object[]>.object[]然后,此方法生成的每个方法都将"解压缩"为单个调用[Theory]方法的参数.

另一种选择是ClassData相同的,但允许在不同类/命名空间中的测试之间轻松共享"生成器",并且还将"数据生成器"与实际测试方法分开.

从这里这些例子:

PropertyData示例

public class StringTests2
{
    [Theory, PropertyData(nameof(SplitCountData))]
    public void SplitCount(string input, int expectedCount)
    {
        var actualCount = input.Split(' ').Count();
        Assert.Equal(expectedCount, actualCount);
    }

    public static IEnumerable<object[]> SplitCountData
    {
        get
        {
            // Or this could read from a file. :)
            return new[]
            {
                new object[] { "xUnit", 1 },
                new object[] { "is fun", 2 },
                new object[] { "to test with", 3 }
            };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ClassData示例

public class StringTests3
{
    [Theory, ClassData(typeof(IndexOfData))]
    public void IndexOf(string input, char letter, int expected)
    {
        var actual = input.IndexOf(letter);
        Assert.Equal(expected, actual);
    }
}

public class IndexOfData : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "hello world", 'w', 6 },
        new object[] { "goodnight moon", 'w', -1 }
    };

    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}
Run Code Online (Sandbox Code Playgroud)

  • @Erti,使用`[MemberData("{static member}",MemberType = typeof(MyClass))]`来替换`ClassData`属性. (11认同)
  • 从C#6开始,它建议使用`nameof`关键字而不是硬编码属性名称(轻松但无声地中断). (5认同)
  • @尼克:我同意这与PropertyData类似,但是,您也指出了原因:“静态”。这就是为什么我不会的原因。ClassData是您要摆脱静态的时候。这样,您可以更轻松地重用(即嵌套)生成器。 (2认同)

dav*_*bak 35

更新@Quetzalcoatl的答案:该属性[PropertyData]已被取代,[MemberData]其中将任何返回的静态方法,字段或属性的字符串名称作为参数IEnumerable<object[]>.(我发现有一个迭代器方法可以实际上一次计算一个测试用例,并在计算它们时产生它们.)

枚举器返回的序列中的每个元素都是一个,object[]并且每个数组必须是相同的长度,并且该长度必须是测试用例的参数数量(使用属性注释,[MemberData]并且每个元素必须与相应的方法参数具有相同的类型(或者他们可能是可转换的类型,我不知道.)

(请参阅2014年3月xUnit.net的发行说明以及带有示例代码的实际补丁.)

  • @davidbak 综合体已经消失了。链接失效了 (4认同)

fia*_*iat 10

创建匿名对象数组不是构建数据的最简单方法,因此我在项目中使用了这种模式

首先定义一些可重用的共享类

//http://stackoverflow.com/questions/22093843
public interface ITheoryDatum
{
    object[] ToParameterArray();
}

public abstract class TheoryDatum : ITheoryDatum
{
    public abstract object[] ToParameterArray();

    public static ITheoryDatum Factory<TSystemUnderTest, TExpectedOutput>(TSystemUnderTest sut, TExpectedOutput expectedOutput, string description)
    {
        var datum= new TheoryDatum<TSystemUnderTest, TExpectedOutput>();
        datum.SystemUnderTest = sut;
        datum.Description = description;
        datum.ExpectedOutput = expectedOutput;
        return datum;
    }
}

public class TheoryDatum<TSystemUnderTest, TExecptedOutput> : TheoryDatum
{
    public TSystemUnderTest SystemUnderTest { get; set; }

    public string Description { get; set; }

    public TExpectedOutput ExpectedOutput { get; set; }

    public override object[] ToParameterArray()
    {
        var output = new object[3];
        output[0] = SystemUnderTest;
        output[1] = ExpectedOutput;
        output[2] = Description;
        return output;
    }

}
Run Code Online (Sandbox Code Playgroud)

现在您的个人测试和成员数据更容易编写和更清洁......

public class IngredientTests : TestBase
{
    [Theory]
    [MemberData(nameof(IsValidData))]
    public void IsValid(Ingredient ingredient, string testDescription, bool expectedResult)
    {
        Assert.True(ingredient.IsValid == expectedResult, testDescription);
    }

    public static IEnumerable<object[]> IsValidData
    {
        get
        {
            var food = new Food();
            var quantity = new Quantity();
            var data= new List<ITheoryDatum>();

            data.Add(TheoryDatum.Factory(new Ingredient { Food = food }                       , false, "Quantity missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity }               , false, "Food missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity, Food = food }  , true,  "Valid"));

            return data.ConvertAll(d => d.ToParameterArray());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Description当你的许多测试用例中的一个失败时,字符串属性就是给自己一个骨头


Sim*_*ver 6

对于我的需求,我只想通过一些测试来运行一系列“测试用户” - 但 [ClassData] 等似乎对于我所需要的来说太过分了(因为项目列表已本地化到每个测试)。

所以我做了以下操作,在测试内部使用一个数组 - 从外部索引:

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Account_ExistingUser_CorrectPassword(int userIndex)
{
    // DIFFERENT INPUT DATA (static fake users on class)
    var user = new[]
    {
        EXISTING_USER_NO_MAPPING,
        EXISTING_USER_MAPPING_TO_DIFFERENT_EXISTING_USER,
        EXISTING_USER_MAPPING_TO_SAME_USER,
        NEW_USER

    } [userIndex];

    var response = await Analyze(new CreateOrLoginMsgIn
    {
        Username = user.Username,
        Password = user.Password
    });

    // expected result (using ExpectedObjects)
    new CreateOrLoginResult
    {
        AccessGrantedTo = user.Username

    }.ToExpectedObject().ShouldEqual(response);
}
Run Code Online (Sandbox Code Playgroud)

这实现了我的目标,同时保持了测试意图的明确。您只需要保持索引同步即可。

结果看起来不错,它是可折叠的,如果出现错误,您可以重新运行特定实例:

在此输入图像描述


Ima*_*our 6

假设我们有一个复杂的Car类,其中有一个Manufacturer类:

public class Car
{
     public int Id { get; set; }
     public long Price { get; set; }
     public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
    public string Name { get; set; }
    public string Country { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我们将填写汽车课程并将其通过理论考试。

因此,创建一个“ CarClassData”类,该类返回Car类的实例,如下所示:

public class CarClassData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] {
                new Car
                {
                  Id=1,
                  Price=36000000,
                  Manufacturer = new Manufacturer
                  {
                    Country="country",
                    Name="name"
                  }
                }
            };
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
Run Code Online (Sandbox Code Playgroud)

现在是时候创建一种测试方法(CarTest)并将汽车定义为参数了:

[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
     var output = car;
     var result = _myRepository.BuyCar(car);
}
Run Code Online (Sandbox Code Playgroud)

理论上的复杂类型

祝好运

  • 该答案明确解决了将自定义类型作为“理论”输入传递的问题,该自定义类型似乎在所选答案中缺失。 (2认同)
  • 如何返回汽车类数据中的多个对象? (2认同)