Encapsulating Enumeration
While writing a unit test for a particular feature, I was faced with an interesting problem. My unit test has different scenarios. These scenarios test that a certain data item is propagated all the way down from the called function.
For eg, I have a method that I need to test - lets call it CFoo:DomSometing().
We pass in a data item to this method - so as part of the changes I change the method to CFoo::DoSomething(int data)
I need to make sure, in my tests, that the 'data' item is properly consumed by the function.
It turns out that for different functions that I call as part of each of my scenario, the way of checking is different. For eg, for one scenario, I have to call the function 'n' times, whereas for another scenario, I have to call the function for a particular duration.
I want to encapsulate all of this, so that I can write one test function that does the following
bool RunTest ( RunTestDelegate testMethod, int data, ILoopIterator iterator)
{
foreach (int i in iterator.GetValues())
{
// call the test method
testMethod (data);
}
}
Here, the RunTestDelegate will be used to encapsulate each method that I need to test, so that I dont have to write one RunTestXXX method per each method I want to test.
The question is: how do you implement the ILoopIterator? Clearly, the first requirement is that it should be returning an IEnumerable<int> because that is what the foreach loop is iterating over.
Also, as discussed above, we need it so that we can iterate over both a range of values, as well as for a time duration.
In order to solve this, I implemented the interface as follows:
interface ILoopIterator
{
IEnumerable<int> GetValues();
}class LoopIterator : ILoopIterator
{
private int count;
private TimeSpan duration;
private bool isCounted;
private LoopIterator (TimeSpan duration)
{
this.duration = duration;
this.count = -1;
this.isCounted = false;
}private LoopIterator (int iterationCount)
{
this.count = iterationCount;
this.isCounted = true;
this.duration = TimeSpan.MaxValue;
}public static LoopIterator CreateTimed(TimeSpan duration)
{
return new LoopIterator (duration);
}public static LoopIterator CreateCounted(int iterationCount)
{
return new LoopIterator (iterationCount);
}public IEnumerable<int> GetValues()
{
if (this.isCounted)
{
for (int i = 0; i < this.count; i++)
{
yield return i;
}
}
else
{
int i = 0;
DateTime end = DateTime.Now + this.duration;
while (DateTime.Now < end)
{
yield return i;
++i;
}
}
}
}
This code used the .NET/2.0 C# features - i.e generics & yield statement for implementing enumerators.
The caller will use a different factory method, depending on how he wants the iteration to be performed.
For duration based iteration, use LoopIterator .CreateTimed(TimeSpan duration)
For normal iteration (like a for loop) use LoopIterator .CreateCounted(int count)
The utility of this pattern, is that the client (which is using ILoopIterator ) does not need to change!