我看过这两个类似的SO问题:
他们很棒,让我差不多到了那里。但是这两个示例在发出的IEnumerable PropertyData中只使用了一个条目(即:yield return new object[] { 2, 4 };
- 请参阅:https://stackoverflow.com/a/16843837/201308)这有效,但每当我想对多个对象进行测试时它会爆炸[]测试数据。我有一整套想要发送的测试数据。
我在想这里的答案(https://stackoverflow.com/a/19309577/201308)与我的需求类似,但我无法弄清楚。我基本上需要AutoFixture为PropertyData的每次迭代创建一个sut
实例。
一些参考:
public static IEnumerable<object[]> TestData
{
get
{
// totally doesn't work
return new List<object[]>()
{
new object[] { new MsgData() { Code = "1" }, CustomEnum.Value1 },
new object[] { new MsgData() { Code = "2" }, CustomEnum.Value2 },
new object[] { new MsgData() { Code = "3" }, CustomEnum.Value3 },
new object[] { new MsgData() { Code = "4" }, CustomEnum.Value4 },
};
// totally works
//yield return new object[] { new MsgData() { Code = "1" }, CustomEnum.Value1 };
}
}
返回列表会产生“预期的3个参数,得到2个参数”异常。如果我只返回单个yield语句,它就可以了。 (我也尝试在列表上循环并产生每个项目 - 没有区别,这是有道理的,看看它与返回完整列表几乎完全相同。)
xUnit测试方法:
[Theory]
[AutoMoqPropertyData("TestData")]
public void ShouldMapEnum(MsgData msgData, CustomEnum expectedEnum, SomeObject sut)
{
var customEnum = sut.GetEnum(msgData);
Assert.Equal(expectedEnum, customEnum);
}
AutoMoqPropertyData
实施:
public class AutoMoqPropertyDataAttribute : CompositeDataAttribute
{
public AutoMoqPropertyDataAttribute(string dataProperty)
: base(new DataAttribute[]
{
new PropertyDataAttribute(dataProperty),
new AutoDataAttribute(new Fixture().Customize(new AutoMoqCustomization()))
})
{ }
}
我错过了什么?想要多次迭代PropertyData数据时,我可以混合使用PropertyData和AutoData驱动的AutoFixture属性吗?
修改 这是异常堆栈跟踪:
System.InvalidOperationException: Expected 3 parameters, got 2 parameters
at Ploeh.AutoFixture.Xunit.CompositeDataAttribute.<GetData>d__0.MoveNext()
at Xunit.Extensions.TheoryAttribute.<GetData>d__7.MoveNext()
at Xunit.Extensions.TheoryAttribute.EnumerateTestCommands(IMethodInfo method)
Result StackTrace:
at Xunit.Extensions.TheoryAttribute.<>c__DisplayClass5.<EnumerateTestCommands>b__1()
at Xunit.Extensions.TheoryAttribute.LambdaTestCommand.Execute(Object testClass)
答案 0 :(得分:1)
您必须按照this answer Ruben Bartelink points out中所述提供测试用例。
[Theory]
[AutoMoqPropertyData("Case1")]
[AutoMoqPropertyData("Case2")]
[AutoMoqPropertyData("Case3")]
[AutoMoqPropertyData("Case4")]
public void ShouldMapEnum(
MsgData msgData, CustomEnum expectedEnum, SomeObject sut)
{
var customEnum = sut.GetEnum(msgData);
Assert.Equal(expectedEnum, customEnum);
}
public static IEnumerable<object[]> Case1 { get {
yield return new object[] {
new MsgData { Code = "1" }, CustomEnum.Value1 }; } }
public static IEnumerable<object[]> Case2 { get {
yield return new object[] {
new MsgData { Code = "2" }, CustomEnum.Value2 }; } }
public static IEnumerable<object[]> Case3 { get {
yield return new object[] {
new MsgData { Code = "3" }, CustomEnum.Value3 }; } }
public static IEnumerable<object[]> Case4 { get {
yield return new object[] {
new MsgData { Code = "4" }, CustomEnum.Value4 }; } }
然而,问题往往更通用(而非具体),因为:
对于1.
和2.
以及参数化测试的现有xUnit.net模型,没有什么可做的。
对于3.
如果代码是用F#编写的,大多数类型声明噪声(以及一些大括号)都会消失:
let Case1 : seq<obj[]> = seq {
yield [| { Code = "1" }; Value1 |] }
let Case2 : seq<obj[]> = seq {
yield [| { Code = "2" }; Value2 |] }
let Case3 : seq<obj[]> = seq {
yield [| { Code = "3" }; Value3 |] }
let Case4 : seq<obj[]> = seq {
yield [| { Code = "4" }; Value4 |] }
[<Theory>]
[<AutoMoqPropertyData("Case1")>]
[<AutoMoqPropertyData("Case2")>]
[<AutoMoqPropertyData("Case3")>]
[<AutoMoqPropertyData("Case4")>]
let ShouldMapEnum (msgData, expected, sut : SomeObject) =
let actual = sut.GetEnum(msgData)
Assert.Equal(expected, actual.Value)
以下是用于通过测试的类型:
type MsgData = { Code : string }
[<AutoOpen>]
type Custom = Value1 | Value2 | Value3 | Value4
type SomeObject () =
member this.GetEnum msgData =
match msgData.Code with
| "1" -> Some(Value1)
| "2" -> Some(Value2)
| "3" -> Some(Value3)
| "4" -> Some(Value4)
| _ -> None
[<AttributeUsage(AttributeTargets.Field, AllowMultiple = true)>]
type AutoMoqPropertyDataAttribute (dataProperty) =
inherit CompositeDataAttribute(
PropertyDataAttribute(dataProperty),
AutoDataAttribute())
答案 1 :(得分:0)
我自己需要这个,我写了一个新的PropertyAutoData
课程,它结合了PropertyData
和AutoFixture,类似于InlineAutoData
结合InlineData
和AutoFixture的方式。用法是:
[Theory]
[PropertyAutoData("ColorPairs")]
public void ReverseColors([TestCaseParameter] TestData testData, int autoGenValue) { ... }
public static IEnumerable<object[]> ColorPairs
{
get
{
yield return new object[] { new TestData { Input = Color.Black, Expected = Color.White } };
yield return new object[] { new TestData { Input = Color.White, Expected = Color.Black } };
}
}
请注意param [TestCaseParameter]
上的testData
属性。这表示将从属性提供参数值。它需要明确指定,因为AutoFixture类改变了参数化测试的含义。
运行此操作会产生2个预期的测试,其中autoGenValue
具有相同的自动生成的值。您可以通过设置自动生成数据的Scope
来更改此行为:
[PropertyAutoData("ColorPairs", Scope = AutoDataScope.Test)] // default is TestCase
您也可以将其与SubSpec Thesis
:
[Thesis]
[PropertyAutoData("ColorPairs")]
public void ReverseColors([TestCaseParameter] TestData testData, int autoGenValue)
要在Moq中使用它,你需要扩展它,即
public class PropertyMockAutoDataAttribute : PropertyAutoDataAttribute
{
public PropertyFakeAutoDataAttribute(string propertyName)
: base(propertyName, new Fixture().Customize(new AutoMoqCustomization()))
{
}
}
以下是代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Ploeh.AutoFixture.Xunit;
using Xunit.Extensions;
/// <summary>
/// Provides a data source for a data theory, with the data coming from a public static property on the test class combined with auto-generated data specimens generated by AutoFixture.
/// </summary>
public class PropertyAutoDataAttribute : AutoDataAttribute
{
private readonly string _propertyName;
public PropertyAutoDataAttribute(string propertyName)
{
_propertyName = propertyName;
}
public PropertyAutoDataAttribute(string propertyName, IFixture fixture)
: base(fixture)
{
_propertyName = propertyName;
}
/// <summary>
/// Gets or sets the scope of auto-generated data.
/// </summary>
public AutoDataScope Scope { get; set; }
public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, Type[] parameterTypes)
{
var parameters = methodUnderTest.GetParameters();
var testCaseParametersIndices = GetTestCaseParameterIndices(parameters);
if (!testCaseParametersIndices.Any())
{
throw new InvalidOperationException(string.Format("There are no parameters marked using {0}.", typeof(TestCaseParameterAttribute).Name));
}
if (testCaseParametersIndices.Length == parameters.Length)
{
throw new InvalidOperationException(string.Format("All parameters are provided by the property. Do not use {0} unless there are other parameters that AutoFixture should provide.", typeof(PropertyDataAttribute).Name));
}
// 'split' the method under test in 2 methods: one to get the test case data sets and another one to get the auto-generated data set
var testCaseParameterTypes = parameterTypes.Where((t, i) => testCaseParametersIndices.Contains(i)).ToArray();
var testCaseMethod = CreateDynamicMethod(methodUnderTest.Name + "_TestCase", testCaseParameterTypes);
var autoFixtureParameterTypes = parameterTypes.Where((t, i) => !testCaseParametersIndices.Contains(i)).ToArray();
var autoFixtureTestMethod = CreateDynamicMethod(methodUnderTest.Name + "_AutoFixture", autoFixtureParameterTypes);
// merge the test case data and the auto-generated data into a new array and yield it
// the merge depends on the Scope:
// * if the scope is TestCase then auto-generate data once for all tests
// * if the scope is Test then auto-generate data for every test
var testCaseDataSets = GetTestCaseDataSets(methodUnderTest.DeclaringType, testCaseMethod, testCaseParameterTypes);
object[] autoGeneratedDataSet = null;
if (Scope == AutoDataScope.TestCase)
{
autoGeneratedDataSet = GetAutoGeneratedData(autoFixtureTestMethod, autoFixtureParameterTypes);
}
var autoFixtureParameterIndices = Enumerable.Range(0, parameters.Length).Except(testCaseParametersIndices).ToArray();
foreach (var testCaseDataSet in testCaseDataSets)
{
if (testCaseDataSet.Length != testCaseParameterTypes.Length)
{
throw new ApplicationException("There is a mismatch between the values generated by the property and the test case parameters.");
}
var mergedDataSet = new object[parameters.Length];
CopyAtIndices(testCaseDataSet, mergedDataSet, testCaseParametersIndices);
if (Scope == AutoDataScope.Test)
{
autoGeneratedDataSet = GetAutoGeneratedData(autoFixtureTestMethod, autoFixtureParameterTypes);
}
CopyAtIndices(autoGeneratedDataSet, mergedDataSet, autoFixtureParameterIndices);
yield return mergedDataSet;
}
}
private static int[] GetTestCaseParameterIndices(ParameterInfo[] parameters)
{
var testCaseParametersIndices = new List<int>();
for (var index = 0; index < parameters.Length; index++)
{
var parameter = parameters[index];
var isTestCaseParameter = parameter.GetCustomAttributes(typeof(TestCaseParameterAttribute), false).Length > 0;
if (isTestCaseParameter)
{
testCaseParametersIndices.Add(index);
}
}
return testCaseParametersIndices.ToArray();
}
private static MethodInfo CreateDynamicMethod(string name, Type[] parameterTypes)
{
var method = new DynamicMethod(name, typeof(void), parameterTypes);
return method.GetBaseDefinition();
}
private object[] GetAutoGeneratedData(MethodInfo method, Type[] parameterTypes)
{
var autoDataSets = base.GetData(method, parameterTypes).ToArray();
if (autoDataSets == null || autoDataSets.Length == 0)
{
throw new ApplicationException("There was no data automatically generated by AutoFixture");
}
if (autoDataSets.Length != 1)
{
throw new ApplicationException("Multiple sets of data were automatically generated. Only one was expected.");
}
return autoDataSets.Single();
}
private IEnumerable<object[]> GetTestCaseDataSets(Type testClassType, MethodInfo method, Type[] parameterTypes)
{
var attribute = new PropertyDataAttribute(_propertyName) { PropertyType = testClassType };
return attribute.GetData(method, parameterTypes);
}
private static void CopyAtIndices(object[] source, object[] target, int[] indices)
{
var sourceIndex = 0;
foreach (var index in indices)
{
target[index] = source[sourceIndex++];
}
}
}
/// <summary>
/// Defines the scope of auto-generated data in a theory.
/// </summary>
public enum AutoDataScope
{
/// <summary>
/// Data is auto-generated only once for all tests.
/// </summary>
TestCase,
/// <summary>
/// Data is auto-generated for every test.
/// </summary>
Test
}
/// <summary>
/// Indicates that the parameter is part of a test case rather than being auto-generated by AutoFixture.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class TestCaseParameterAttribute : Attribute
{
}