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:
- Parameters(…) accepts a function that explains what inputs to use for any given test method.
- Your function is given the MethodInfo that describes a single test method.
- Your function yields any number of object arrays.
- 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.