Xunit has a nice feature:您可以使用Theory
属性创建一个测试,并将数据放入InlineData
属性中,xUnit将生成许多测试,并对所有测试进行测试。
我希望有类似的内容,但我方法的参数不是“简单数据”(例如string
,int
,double
),而是我的班级列表:
public static void WriteReportsToMemoryStream(
IEnumerable<MyCustomClass> listReport,
MemoryStream ms,
StreamWriter writer) { ... }
答案 0 :(得分:118)
XUnit中有许多xxxxData
个属性。查看例如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(); }
}
答案 1 :(得分:33)
更新@ Quetzalcoatl的答案:[PropertyData]
已取代属性[MemberData]
,它将任何返回IEnumerable<object[]>
的静态方法,字段或属性的字符串名称作为参数。 (我发现有一个迭代器方法可以实际一次一个地计算测试用例,并在计算它们时产生它们。)
枚举器返回的序列中的每个元素都是object[]
,每个数组的长度必须相同,并且该长度必须是测试用例的参数数量(使用属性[MemberData]
注释并且每个元素必须与相应的方法参数具有相同的类型。(或者它们可以是可转换类型,我不知道。)
(请参阅release notes for xUnit.net March 2014和the actual patch with example code。)
答案 2 :(得分:8)
创建匿名对象数组不是构建数据的最简单方法,因此我在项目中使用了这种模式
首先定义一些可重用的共享类
//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, 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
属性是在您的许多测试用例中的一个失败时将自己投入骨骼
答案 3 :(得分:2)
您可以尝试这种方式:
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;
}
}
}
答案 4 :(得分:2)
假设我们有一个复杂的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; }
}
我们将填写Car类并将其通过理论考试。
因此,创建一个“ 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);
}
祝你好运
答案 5 :(得分: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();
}
希望有帮助!
答案 6 :(得分:0)
出于我的需要,我只想通过一些测试来运行一系列的“测试用户”-但是[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);
}
这实现了我的目标,同时保持了测试的意图。您只需要保持索引同步就可以了。
看起来不错,可以折叠,如果遇到错误,可以重新运行特定实例:
答案 7 :(得分:-1)
我猜你错了。 xUnit Theory
属性的实际含义是:您希望通过发送特殊/随机值作为此测试函数接收的参数来测试此函数。这意味着您定义为下一个属性的内容,例如:InlineData
,PropertyData
,ClassData
等将成为这些参数的来源。这意味着您应该构造源对象以提供这些参数。在你的情况下,我猜你应该使用ClassData
对象作为源。另外 - 请注意ClassData
继承自:IEnumerable<>
- 这意味着每次生成的另一组参数将用作被测函数的传入参数,直到IEnumerable<>
生成值。< / p>
此处示例:Tom DuPont .NET
示例可能不正确 - 我没有长时间使用xUnit