A Swiss Army Katana

Before now, test methods for the Fixie test framework had to have zero parameters. If your test method had a parameter, it would fail without being called. Fixie would have no idea what values to pass in. As of Fixie 0.0.1.98, you can define your own conventions for parameterized tests. As a convention author, you decide what it means for a test to have parameters. For example, let’s say you want your parameter values to come from attributes on the method, similar to xUnit theories:

public class CalculatorTests
{
    readonly Calculator calculator;

    public CalculatorTests()
    {
        calculator = new Calculator();
    }

    [Input(2, 3, 5)]
    [Input(3, 5, 8)]
    public void ShouldAdd(int a, int b, int expectedSum)
    {
        calculator.Add(a, b).ShouldEqual(expectedSum);
    }

    [Input(5, 3, 2)]
    [Input(8, 5, 3)]
    [Input(10, 5, 5)]
    public void ShouldSubtract(int a, int b, int expectedDifference)
    {
        calculator.Subtract(a, b).ShouldEqual(expectedDifference);
    }
}

Our intention is for these 2 test methods to be treated as 5 test cases, producing 5 individual pass/fail results. Out of the box, Fixie has no idea what [Input] means. In order to let Fixie know about our intentions, we can define the attribute and a custom convention:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class InputAttribute : Attribute
{
    public InputAttribute(params object[] parameters)
    {
        Parameters = parameters;
    }

    public object[] Parameters { get; private set; }
}

public class CustomConvention : Convention
{
    public CustomConvention()
    {
        Classes
            .NameEndsWith("Tests");

        Methods
            .Where(method => method.IsVoid());

        ClassExecution
            .CreateInstancePerTestClass();


        //This new hook lets you explain where
        //parameters should come from:

        Parameters(FromInputAttributes);
    }
    
    IEnumerable<object[]> FromInputAttributes(MethodInfo method)
    {
        var inputAttributes = method.GetCustomAttributes<InputAttribute>(true).ToArray();

        if (!inputAttributes.Any())
        {
            //No [Input] attributes?  Just call the
            //test method once with no arguments.

            yield return new object[] { };
        }
        else
        {
            //Call the test once for each [Input] attribute.

            foreach (var input in inputAttributes)
                yield return input.Parameters;
        }
    }
}

Your own convention wouldn’t have to be attribute-based. All that Fixie cares about is that you provide it some Func<MethodInfo, IEnumerable<object[]>>. That’s a mouthful, so let’s break it down:

  1. Parameters(…) accepts a function that explains what inputs to use for any given test method.
  2. Your function is given the MethodInfo that describes a single test method.
  3. Your function yields any number of object arrays.
  4. Each object array that you yield represents a single call to the test method. If the method takes in 3 parameters, your arrays better have 3 values with corresponding types.

Func<MethodInfo, IEnumerable<object[]>> is a Swiss Army Katana. It’s versatile, but sharp. It will enable us to do a wide variety of things, but it’s easy to misuse. It represents exactly what the .NET reflection API needs in order to call the method, so no matter what sugar I layer on top of it, Func<MethodInfo, IEnumerable<object[]>> is the truth under the hood. After developing a few more examples, it’ll be more clear how a few convenient overloads of the Parameters() method could make it easier to get things right in the most common situations.

In my next post, we’ll take a little detour to see why such a small change was so hard to implement.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Patrick Lioi

I am a Senior Consultant at Headspring in Austin, TX. I created the Parsley text parsing library and the Fixie test framework.
This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://devtools.korzh.com/ devtools.korzh

    Interesting to read.