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


98

Xunit具有一个不错的功能:您可以创建一个带有Theory属性的测试,然后将数据放入InlineData属性中,而xUnit将生成许多测试并将其全部测试。

我想有这样的事情,但参数我的方法不是“简单的数据”(如stringintdouble),但我的类的列表:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }

3
-如果在你的环境是有道理的,你能做到这一点在F#中有很多噪音较小stackoverflow.com/a/35127997/11635
鲁文Bartelink

1
完整的指南,将复杂对象作为参数发送给单元测试中的
Iman Bahrampour

Answers:


137

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

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

@dcastro:是的,我实际上是在原始的
xunit

2
@Nick:我同意这是类似于PropertyData,而且,你已经指出了它的原因:static。这就是为什么我不会的原因。ClassData是您想摆脱静态变量的时候。这样,您可以更轻松地重用(即嵌套)生成器。
quetzalcoatl 2014年

1
任何想法ClassData发生了什么?我无法在xUnit2.0中找到它,目前,我正在使用带有静态方法的MemberData,该方法会创建类的新实例并返回该实例。
2015年

14
@Erti,用于[MemberData("{static member}", MemberType = typeof(MyClass))]替换ClassData属性。
Junle Li 2015年

6
从C#6开始,建议使用nameof关键字而不是对属性名称进行硬编码(轻松但无声地破解)。
2016年

40

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

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

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


2
@davidbak codplex不见了。链接无效
Kishan Vaishnav

11

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

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

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

}

现在,您的个人测试和会员数据更易于编写和清理。

public class IngredientTests : TestBase
{
    [Theory]
    [MemberData(nameof(IsValidData))]
    public void IsValid(Ingredient ingredient, bool expectedResult, string testDescription)
    {
        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属性是当您的多个测试用例之一失败时让自己陷入困境


1
我喜欢这个; 它对于非常复杂的对象具有一定的实际潜力,我必须对90多个属性进行验证。我可以传入一个简单的JSON对象,对其进行反序列化,然后生成数据以进行测试迭代。做得好。
古斯塔因

1
IsValid Testmethod的参数不是混合在一起吗-它不应该是IsValid(ingrediant,exprectedResult,testDescription)吗?
pastacool

9

假设我们有一个具有Manufacturer类的复杂Car类:

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

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

因此,创建一个“ 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();
    }

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

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

理论上的复杂类型

祝好运


3
该答案明确解决了传递自定义类型作为“理论”输入的问题,该自定义类型似乎在所选答案中缺失。
JD该隐

1
这正是我正在寻找的用例,该用例是如何将复杂类型作为参数传递给Theory的。完美的作品!这对于测试MVP模式确实有回报。现在,我可以在各种状态下设置视图的许多不同实例,并将它们全部传递给同一理论,该理论测试Presenter方法对该视图的影响。爱它!
Denis M. Kitchen

3

您可以这样尝试:

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

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

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

1

对于我的需求,我只想通过一些测试来运行一系列的“测试用户”-但是[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);
}

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

结果看起来不错,可折叠,如果遇到错误,可以重新运行特定实例:

在此处输入图片说明


“结果不错,可折叠,如果遇到错误,可以重新运行特定实例”。很好。的主要缺点MemberData似乎是您无法使用特定的测试输入来查看或运行该测试。糟透了
奥利弗·皮尔曼

实际上,我刚刚得出结论,MemberData如果您使用TheoryData和可选,则可以实现IXunitSerializable。更多信息和例子在这里... github.com/xunit/xunit/issues/429#issuecomment-108187109
Oliver Pearmain

1

这是我解决您的问题的方法,我遇到了相同的情况。因此,内联自定义对象,并且每次运行时都有不同数量的对象。

    [Theory]
    [ClassData(typeof(DeviceTelemetryTestData))]
    public async Task ProcessDeviceTelemetries_TypicalDeserialization_NoErrorAsync(params DeviceTelemetry[] expected)
    {
        // Arrange
        var timeStamp = DateTimeOffset.UtcNow;

        mockInflux.Setup(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>())).ReturnsAsync("Success");

        // Act
        var actual = await MessageProcessingTelemetry.ProcessTelemetry(JsonConvert.SerializeObject(expected), mockInflux.Object);

        // Assert
        mockInflux.Verify(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>()), Times.Once);
        Assert.Equal("Success", actual);
    }

所以这是我的单元测试,请注意params参数。这允许发送不同数量的对象。现在我的DeviceTelemetryTestData类:

    public class DeviceTelemetryTestData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
            yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } };
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

希望能帮助到你 !


-1

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

此处的示例:Tom DuPont .NET

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.