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

问题描述 投票:76回答:7

Xunit has a nice feature:您可以使用Theory属性创建一个测试并将数据放入InlineData属性中,xUnit将生成许多测试,并对它们进行全部测试。

我希望有这样的东西,但我方法的参数不是'简单数据'(如stringintdouble),而是我的班级列表:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }
c# unit-testing xunit xunit.net
7个回答
122
投票

XUnit中有许多xxxxData属性。查看例如PropertyData属性。

您可以实现一个返回IEnumerable<object[]>的属性。此方法生成的每个object[]将被“解压缩”为单个调用[Theory]方法的参数。

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

见ie these examples from here

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 }
            };
        }
    }
}

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(); }
}

35
投票

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

枚举器返回的序列中的每个元素都是[MemberData],每个数组的长度必须相同,并且该长度必须是测试用例的参数个数(使用属性release notes for xUnit.net March 2014注释,并且每个元素的类型必须与对应的相同方法参数。(或者它们可以是可转换类型,我不知道。)

(见the actual patch with example code//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; } } 。)


9
投票

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

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

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());
        }
    }
}

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

Description

当你的许多测试用例中的一个失败时,字符串public class TestClass { bool isSaturday(DateTime dt) { string day = dt.DayOfWeek.ToString(); return (day == "Saturday"); } [Theory] [MemberData("IsSaturdayIndex", MemberType = typeof(TestCase))] public void test(int i) { // parse test case var input = TestCase.IsSaturdayTestCase[i]; DateTime dt = (DateTime)input[0]; bool expected = (bool)input[1]; // test bool result = isSaturday(dt); result.Should().Be(expected); } } 属性就是给你自己一个骨头


3
投票

你可以这样试试:

public class TestCase
{
   public static readonly List<object[]> IsSaturdayTestCase = new List<object[]>
   {
      new object[]{new DateTime(2016,1,23),true},
      new object[]{new DateTime(2016,1,24),false}
   };

   public static IEnumerable<object[]> IsSaturdayIndex
   {
      get
      {
         List<object[]> tmp = new List<object[]>();
            for (int i = 0; i < IsSaturdayTestCase.Count; i++)
                tmp.Add(new object[] { i });
         return tmp;
      }
   }
}

创建另一个类来保存测试数据:

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; }
}

3
投票

假设我们有一个具有Manufacturer类的复杂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();
    }

我们将填写并通过Car类进行理论测试。

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

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

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

complex type in theory

[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); }

祝好运


0
投票

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

所以我做了以下内容,测试中有一个数组 - 从外部索引:

enter image description here

这实现了我的目标,同时保持了测试的意图。您只需要保持索引同步,但这就是全部。

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

Theory


-1
投票

我想你错了。什么xUnit InlineData属性实际上意味着:您希望通过发送特殊/随机值作为此测试功能接收的参数来测试此功能。这意味着您定义的下一个属性,例如:PropertyDataClassDataClassData等将是这些参数的来源。这意味着您应该构造源对象以提供这些参数。在你的情况下,我猜你应该使用ClassData对象作为源。另外 - 请注意IEnumerable<>继承自:IEnumerable<> - 这意味着每次生成的另一组参数将用作被测函数的传入参数,直到Tom DuPont .NET产生值。

示例:qazxswpoi

示例可能不正确 - 我很长时间没有使用xUnit

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.