I've been building an extensions library, and I've utilised a great extension method found at http://www.extensionmethod.net for inclusion. In my unit test (using NUnit 1.5.2), I've come across an interesting issue. Firstly, lets look at the code:
/// <summary>
/// Groups and aggregates the sequence of elements.
/// </summary>
/// <typeparam name="TSource">The source type in the sequence.</typeparam>
/// <typeparam name="TFirstKey">The first key type to group by.</typeparam>
/// <typeparam name="TSecondKey">The second key type to rotate by.</typeparam>
/// <typeparam name="TValue">The type of value that will be aggregated.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="firstKeySelector">The first key selector.</param>
/// <param name="secondKeySelector">The second key selector.</param>
/// <param name="aggregator">The aggregating function.</param>
/// <returns>A <see cref="Dictionary{TKey,TValue}" /> representing the pivoted data.</returns>
public static Dictionary<TFirstKey, Dictionary<TSecondKey, TValue>> Pivot<TSource, TFirstKey, TSecondKey, TValue>
(this IEnumerable<TSource> source,
Func<TSource, TFirstKey> firstKeySelector,
Func<TSource, TSecondKey> secondKeySelector,
Func<IEnumerable<TSource>, TValue> aggregator)
{
return source.GroupBy(firstKeySelector).Select(
x => new
{
X = x.Key,
Y = x.GroupBy(secondKeySelector).Select(
z => new { Z = z.Key, V = aggregator(z) }).ToDictionary(e => e.Z, o => o.V)
}).ToDictionary(e => e.X, o => o.Y);
}
What the function does, is takes in an IEnumerable of type TSource, and pivots the items into a dictionary, and aggregates the items using whatever function you define. My sample set of data is an array of people (in a type called Person).
private static readonly Person[] people =
new[]
{
new Person { Forename = "Matt", Surname = "Someone", Email = "[email protected]", Age = 25, IsMale = true },
new Person { Forename = "Chris", Surname = "Someone", Email = "[email protected]", Age = 28, IsMale = false },
new Person { Forename = "Andy", Surname = "Someone", Email = "[email protected]", Age = 30, IsMale = true },
new Person { Forename = "Joel", Surname = "Someone", Email = "[email protected]", Age = 30, IsMale = true },
new Person { Forename = "Paul", Surname = "Someone", Email = "[email protected]", Age = 30, IsMale = true }
};
And lastly, we do our test:
/// <summary>
/// Performs a pivot function on the sample array.
/// </summary>
[Test]
public void Pivot()
{
/* Our sample data is an array of Person instances.
* Let's organise it first by gender (IsMale), and then by Age.
* Finally, we'll return a count. */
var organised = people.Pivot(p => p.IsMale, p => p.Age, l => l.Count());
Assert.IsTrue(organised.Count == 2, "More than two genders were returned.");
Assert.IsTrue(organised[true].Count == 2, "More than two ages were returned for males.");
Assert.IsTrue(organised[false].Count == 1, "More than 1 age was returned for females.");
int count = organised[true][30];
Assert.IsTrue(count == 3, "There are more than 3 male 30 year olds in our data.");
}
What is being returned in this test case, is a Dictionary> instance. The boolean is a result of the IsMale group by, and in our sample data, correctly returns 2 items, true and false. The inner dictionary has a key of the age, and a value of the count. In our test data, organised[true][30] reflects all males of the age of 30 in the set.
The problem is not the pivot function itself, but for some reason, when we run this through both the NUnit Test Runner, and Resharper's Unit Test Runner, the test fails, reporting a KeyNotFoundException for the line "int count = organised[true][30];". When we debug this test, it correctly returns the value 3 (as in our sample data, we have 3 males of the age 30).
Any thoughts?
Did you try to configure NUnit to run it from within VS (as an external program)? This way you will be able to have NUint run your test. under debugger