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

C# 将复杂参数传递给[理论],c#,unit-testing,xunit,xunit.net,C#,Unit Testing,Xunit,Xunit.net,:您可以使用Theory属性创建一个测试,并将数据放入InlineData属性中,xUnit将生成许多测试,并对所有测试进行测试 我想要这样的东西,但是我的方法的参数不是“简单数据”(比如string,int,double),而是我的类的列表: public static void WriteReportsToMemoryStream( IEnumerable<MyCustomClass> listReport, MemoryStream ms, Stream

:您可以使用
Theory
属性创建一个测试,并将数据放入
InlineData
属性中,xUnit将生成许多测试,并对所有测试进行测试

我想要这样的东西,但是我的方法的参数不是“简单数据”(比如
string
int
double
),而是我的类的列表:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }
public静态无效WriteReportsToMemoryStream(
IEnumerable列表报告,
MemoryStream ms,
StreamWriter编写器){…}

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

可以实现返回
IEnumerable
的属性。然后,此方法生成的每个
对象[]
将作为单个调用
[理论]
方法的参数“解包”

另一个选项是
ClassData
,其工作原理相同,但允许在不同类/名称空间中的测试之间轻松共享“生成器”,并将“数据生成器”与实际测试方法分离

见,即:

属性数据示例

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 }
            };
        }
    }
}
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(); }
}
公共类StringTests2
{
[理论,属性数据(名称(SplitCountData))]
public void SplitCount(字符串输入,int expectedCount)
{
var actualCount=input.Split(“”).Count();
Assert.Equal(预期计数、实际计数);
}
公共静态IEnumerable SplitCountData
{
得到
{
//或者这可以从文件中读取。:)
返回新的[]
{
新对象[]{“xUnit”,1},
新对象[]{“很有趣”,2},
新对象[]{“要测试的对象”,3}
};
}
}
}
类数据示例

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 }
            };
        }
    }
}
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(); }
}
公共类StringTests3
{
[理论,类数据(类型(IndexOfData))]
public void IndexOf(字符串输入,字符字母,应为int)
{
var实际值=input.IndexOf(字母);
断言。相等(预期、实际);
}
}
公共类索引数据:IEnumerable
{
私有只读列表_data=新列表
{
新对象[]{“hello world”,“w',6},
新对象[]{“晚安月亮”,“w',-1}
};
公共IEnumerator GetEnumerator()
{返回_data.GetEnumerator();}
IEnumerator IEnumerable.GetEnumerator()
{返回GetEnumerator();}
}

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

示例如下:


示例可能不正确-我很长时间没有使用xUnit更新@Quetzalcatl的答案:属性
[PropertyData]
已被
[MemberData]
取代,该属性将返回
IEnumerable
的任何静态方法、字段或属性的字符串名称作为参数。(我发现有一个迭代器方法特别好,它实际上可以一次计算一个测试用例,在计算时生成测试用例。)

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

(请参见和。)

您可以尝试以下方法:

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;
      }
   }
}
公共类测试用例
{
公共静态只读列表IsSaturdayTestCase=新列表
{
新对象[]{new DateTime(2016,1,23),true},
新对象[]{new DateTime(2016,1,24),false}
};
公共静态IEnumerable IsSaturdayIndex
{
得到
{
List tmp=新列表();
for(int i=0;i
创建匿名对象数组不是构造数据的最简单方法,因此我在项目中使用了这种模式

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

//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, TExpectedOutput> : 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;
    }

}
//http://stackoverflow.com/questions/22093843
公共接口ITheoryDatum
{
对象[]TopParameterArray();
}
公共抽象类TheoryDatum:ITheoryDatum
{
公共抽象对象[]ToParameterArray();
公共静态ITHEORYDATA工厂(TSystemUnderTest sut,TExpectedOutput expectedOutput,字符串描述)
{
var datum=新的TheoryDatum();
datum.SystemUnderTest=sut;
数据描述=描述;
datum.ExpectedOutput=预期输出;
返回数据;
}
}
公共类TheoryDatum:TheoryDatum
{
公共TSystemUnderTest SystemUnderTest{get;set;}
公共字符串说明{get;set;}
公共文本预期输出预期输出{get;set;}
公共重写对象[]TopParameterArray()
{
var输出=新对象[3];
输出[0]=SystemUnderTest;
输出[1]=预期输出;
输出[2]=描述;
返回输出;
}
}
现在,您的单个测试和成员数据更易于编写和清理

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());
        }
    }
}
公共类IngreditTests:TestBase
{
[理论]
[MemberData(名称)(IsValidDa
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();
    }
[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
     var output = car;
     var result = _myRepository.BuyCar(car);
}
    [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);
    }
    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();
    }