C# AutoFixture混合属性数据,具有多个条目和AutoData(使用AutoMoqCustomization)

C# AutoFixture混合属性数据,具有多个条目和AutoData(使用AutoMoqCustomization),c#,moq,xunit,xunit.net,autofixture,C#,Moq,Xunit,Xunit.net,Autofixture,我已经研究了这两个类似的问题: 他们很棒,让我快到了。但这两个示例在发出的IEnumerable PropertyData中只使用了一个条目(即:yield return new object[]{2,4};--see:)这是可行的,但每当我想对多个object[]测试数据进行测试时,它就会崩溃。我想发送一整套测试数据 我认为这里的答案()与我需要的答案相似,但我想不出来。我基本上需要AutoFixture为PropertyData的每个迭代创建一个sut实例 一些参考: public

我已经研究了这两个类似的问题:

他们很棒,让我快到了。但这两个示例在发出的IEnumerable PropertyData中只使用了一个条目(即:
yield return new object[]{2,4};
--see:)这是可行的,但每当我想对多个object[]测试数据进行测试时,它就会崩溃。我想发送一整套测试数据

我认为这里的答案()与我需要的答案相似,但我想不出来。我基本上需要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 };
    }
}
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)
System.InvalidOperationException:应为3个参数,得到2个参数
在Ploeh.AutoFixture.Xunit.CompositeDataAttribute.d__0.MoveNext()中
在Xunit.Extensions.TheoryAttribute.d_u7.MoveNext()中
在Xunit.Extensions.TheoryAttribute.EnumerateTestCommands(IMethodInfo方法)中
结果跟踪:
在Xunit.Extensions.TheoryAttribute.c__DisplayClass5.b__1()中
在Xunit.Extensions.TheoryAttribute.LambdaTestCommand.Execute(对象测试类)

您必须提供该链接中描述的测试用例

以下是用于通过测试的类型:

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())
type MsgData={code:string}
[]
类型自定义=值1 |值2 |值3 |值4
键入SomeObject()=
成员this.GetEnum msgData=
将msgData.Code与匹配
|“1”->部分(值1)
|“2”->部分(值2)
|“3”->部分(值3)
|“4”->部分(值4)
|无
[]
类型AutoMoqPropertyDataAttribute(dataProperty)=
继承复合属性(
PropertyDataAttribute(数据属性),
AutoDataAttribute())

我自己也需要它,我编写了一个新类
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 } };
  }
}
您还可以将其与SubSec的
论文一起使用:

[Thesis]
[PropertyAutoData("ColorPairs")]
public void ReverseColors([TestCaseParameter] TestData testData, int autoGenValue)
要将其用于最小起订量,您需要扩展它,即

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
{
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
运用系统反思;
使用System.Reflection.Emit;
使用Ploeh.AutoFixture.Xunit;
使用Xunit.Extensions;
/// 
///为数据理论提供数据源,数据来自测试类上的公共静态属性,并与AutoFixture生成的自动生成数据样本相结合。
/// 
公共类PropertyAutoDataAttribute:AutoDataAttribute
{
私有只读字符串_propertyName;
公共属性自动数据属性(字符串propertyName)
{
_propertyName=propertyName;
}
公共PropertyAutoDataAttribute(字符串propertyName,iTexture fixture)
:底座(固定装置)
{
_propertyName=propertyName;
}
/// 
///获取或设置自动生成数据的范围。
/// 
公共AutoDataScope作用域{get;set;}
公共重写IEnumerable GetData(MethodInfo methodUnderTest,类型[]参数类型)
{
var parameters=methodUnderTest.GetParameters();
var testCaseParametersIndices=GetTestCaseParameterIndices(参数);
如果(!testCaseParametersIndices.Any())
{
抛出新的InvalidOperationException(string.Format(“没有使用{0}.”标记的参数,typeof(TestCaseParameterAttribute.Name));
}
if(testCaseParametersIndices.Length==parameters.Length)
{
抛出新的InvalidOperationException(string.Format(“所有参数都由属性提供。除非AutoFixture应该提供其他参数,否则不要使用{0}”),,typeof(PropertyDataAttribute.Name));
}
//将测试中的方法“拆分”为两种方法:一种用于获取测试用例数据集,另一种用于获取自动生成的数据集
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);
//将测试用例数据和自动生成的数据合并到一个新数组中并生成它
//合并取决于范围:
//*如果范围是TestCase,则自动为所有测试生成一次数据
//*如果范围为测试,则自动为每个测试生成数据
var testCaseDataSets=GetTestCaseDataSets(methodUnderTest.DeclaringType、testCaseMethod、testCaseParameterTypes);
对象[]自动生成的数据集=null;
if(Scope==AutoDataScope.TestCase)
{
autoGeneratedDataSet=GetAutoGeneratedData(autoFixtureTestMethod,autoFixtureParameterTypes);
}
var autoFixtureParameterIndices=Enumerable.Range(0,parameters.Length).Except(testCaseParametersIndices.ToArray();
foreach(testCaseDataSets中的var testCaseDataSet)
{
if(testCaseDataSet.Length!=testCaseParameterTypes.Length)
{
抛出新的ApplicationException(“属性生成的值与测试用例参数不匹配”);
}
var mergedDataSet=新对象[parameters.Length];
复制数据集(testCaseDataSet、mergedDataSet、testCaseParametersIndices);
[PropertyAutoData("ColorPairs", Scope = AutoDataScope.Test)] // default is TestCase
[Thesis]
[PropertyAutoData("ColorPairs")]
public void ReverseColors([TestCaseParameter] TestData testData, int autoGenValue)
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
{
}