7

我正在尝试AutoPropertyDataAttribute根据CompositeDataAttribute此示例AutoFixture: PropertyData and heterogeneous parameters创建。

它适用于单组参数,但会因更多组参数而失败。这是代码:

public static IEnumerable<object[]> NumericSequence
{
    get
    {
        yield return new object[] {1};
        //yield return new object[] {2};
    }
}

[Theory]
[AutoPropertyData("NumericSequence")]
public void Test(int? p1, int? p2, int? p3)
{
    Assert.NotNull(p1);
    Assert.NotNull(p2);
}

public class AutoPropertyDataAttribute : CompositeDataAttribute
{
    public AutoPropertyDataAttribute(string propertyName)
        : base(
              new DataAttribute[] { 
                  new PropertyDataAttribute(propertyName), 
                  new AutoDataAttribute()
              })
    {
    }
}

尝试取消注释第二个yield将破坏测试并显示消息:

System.InvalidOperationException: Expected 2 parameters, got 1 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)

同样发生在ClassDataAttribute

4

3 回答 3

4

我遇到了这个问题,并决定实现一个自定义 DataAttribute 来解决这个问题。我不能使用任何一个属性作为基类(原因如下),所以我只是从每个属性的来源中获取我需要的东西。谢谢 OSS :)

注意事项:

  • 我想稍微改变语义,以便我可以选择生成单个对象而不是数组。只是让单对象参数的代码看起来更整洁。这意味着我不能PropertyDataAttribute用作基类
  • 每次生成一组新参数时都需要创建夹具。这意味着我不能AutoDataAttribute用作基类

要旨

或下面的内联源:

public class AutoPropertyDataAttribute : DataAttribute
{
    private readonly string _propertyName;
    private readonly Func<IFixture> _createFixture;

    public AutoPropertyDataAttribute(string propertyName)
        : this(propertyName, () => new Fixture())
    { }

    protected AutoPropertyDataAttribute(string propertyName, Func<IFixture> createFixture)
    {
        _propertyName = propertyName;
        _createFixture = createFixture;
    }

    public Type PropertyHost { get; set; }

    private IEnumerable<object[]> GetAllParameterObjects(MethodInfo methodUnderTest)
    {
        var type = PropertyHost ?? methodUnderTest.DeclaringType;
        var property = type.GetProperty(_propertyName, BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);

        if (property == null)
            throw new ArgumentException(string.Format("Could not find public static property {0} on {1}", _propertyName, type.FullName));
        var obj = property.GetValue(null, null);
        if (obj == null)
            return null;

        var enumerable = obj as IEnumerable<object[]>;
        if (enumerable != null)
            return enumerable;

        var singleEnumerable = obj as IEnumerable<object>;
        if (singleEnumerable != null)
            return singleEnumerable.Select(x => new[] {x});

        throw new ArgumentException(string.Format("Property {0} on {1} did not return IEnumerable<object[]>", _propertyName, type.FullName));
    }

    private object[] GetObjects(object[] parameterized, ParameterInfo[] parameters, IFixture fixture)
    {
        var result = new object[parameters.Length];

        for (int i = 0; i < parameters.Length; i++)
        {
            if (i < parameterized.Length)
                result[i] = parameterized[i];
            else
                result[i] = CustomizeAndCreate(fixture, parameters[i]);
        }

        return result;
    }

    private object CustomizeAndCreate(IFixture fixture, ParameterInfo p)
    {
        var customizations = p.GetCustomAttributes(typeof (CustomizeAttribute), false)
            .OfType<CustomizeAttribute>()
            .Select(attr => attr.GetCustomization(p));

        foreach (var c in customizations)
        {
            fixture.Customize(c);
        }

        var context = new SpecimenContext(fixture);
        return context.Resolve(p);
    }

    public override IEnumerable<object[]> GetData(MethodInfo methodUnderTest, Type[] parameterTypes)
    {
        foreach (var values in GetAllParameterObjects(methodUnderTest))
        {
            yield return GetObjects(values, methodUnderTest.GetParameters(), _createFixture());
        }
    }
}
于 2015-02-27T14:52:32.330 回答
3

实际发生了什么

NumericSequence [PropertyData]定义了两次迭代

with的组成假设每次迭代都有足够的数据。NumericSequence [PropertyData][AutoData]

然而,实际的组成是:

1st iteration:  [PropertyData], [AutoData]

2nd iteration:  [PropertyData], [n/a]

这就是为什么在第二次迭代中您最终会用完数据。

作品

从某种意义上说,它CompositeDataAttribute尊重LSP ,它是针对所有数据理论的基础(类)进行编程的DataAttribute

(也就是说,没有假设所有属性[AutoData]最后都由 with 组成。)

出于这个原因,它不能简单地从第二次迭代跳到第一次迭代并获取一些[AutoData]值——这会破坏 LSP。

能做什么

使实际的构图看起来像:

1st iteration:  [PropertyData], [AutoData]

2nd iteration:  [PropertyData], [AutoData]

通过定义两个属性:

public static IEnumerable<object[]> FirstPropertyData { get { 
    yield return new object[] { 1 }; } }

public static IEnumerable<object[]> OtherPropertyData { get { 
    yield return new object[] { 9 }; } }

然后,原始测试可以写成:

[Theory]
[AutoPropertyData("FirstPropertyData")]
[AutoPropertyData("OtherPropertyData")]
public void Test(int n1, int n2, int n3)
{
}

测试执行两次,n1总是由[PropertyData]while提供n2n3总是由 提供[AutoData]

于 2013-09-21T07:44:33.427 回答
1

作为一种解决方法,您可以重组AutoPropertyDataAttribute一点并在CompositeDataAttribute内部使用,而不是从它派生。派生自PropertyDataAttribute

public class AutoPropertyDataAttribute : PropertyDataAttribute
{
    public AutoPropertyDataAttribute(string propertyName)
            : base(propertyName)
    {
    }

然后重写该GetData方法以遍历由返回的值PropertyDataAttribute,并利用 AutoFixture InlineAutoData(它派生自CompositeDataAttribute)来填充其余参数:

    public override IEnumerable<object[]> GetData(System.Reflection.MethodInfo methodUnderTest, Type[] parameterTypes)
    {
        foreach (var values in base.GetData(methodUnderTest, parameterTypes))
        {
            // The params returned by the base class are the first m params, 
            // and the rest of the params can be satisfied by AutoFixture using
            // its InlineAutoDataAttribute class.
            var iada = new InlineAutoDataAttribute(values);
            foreach (var parameters in iada.GetData(methodUnderTest, parameterTypes))
                yield return parameters;
        }
    }

外部循环遍历返回的值PropertyData(每次迭代都是一行,填充了一些单元格)。内部循环填充剩余的单元格。

这不是最漂亮的东西,但它似乎工作。我喜欢马克的想法,让 AutoFixture 尝试填充剩余的单元格。少写一段胶水代码:)

希望这会有所帮助,
杰夫。

于 2013-10-11T03:01:59.680 回答