BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Testing
.NET 3.5+

A Single-Pass Sequence for Testing

When writing unit tests for methods that operate upon IEnumerable<T> sequences, it is easy to overlook the behaviour for sequences that can only be enumerated once. This article describes a helper class for such tests that provides a one pass collection.

Single-Pass Sequences

Following the introduction of Language-Integrated Query (LINQ), it became much more common to create library code that works with sequences that implement the IEnumerable<T> interface. Extension methods that work with such sequences, possibly returning a modified sequence, can be used and chained with LINQ standard query operators to produce powerful, readable and maintainable code.

If you are developing custom LINQ-style operators, there is a potential pitfall when working with sequences that can only be enumerated once. Some such sequences will throw an exception if you try to read their data for a second time. Others may be fully or partially exhausted during the first read and appear to be empty or to contain different data for subsequent attempts. In either case, if your method enumerates its source sequence more than once, it will produce unexpected results. However, if you only unit test your method using standard collections, this may go unnoticed.

To avoid releasing software that includes this type of bug, you should test your methods using a sequence that may only be read once. This article describes a single-pass sequence, designed for use within automated tests, which throws an exception when enumerated more than once.

TooManyEnumerationsException

We could make our one-pass sequence throw a standard exception, such as InvalidOperationException, when enumerated for a second time. However, to make the exception more descriptive, let's create a custom exception. For this we only need a very simple class that inherits from Exception and includes a standard message. The following class gives us this. The message is set by calling a base constructor from the default constructor of the TooManyEnumerationsExceptions type.

public class TooManyEnumerationsException : Exception
{
    public TooManyEnumerationsException()
        : base("Sequence may only be enumerated once") { }
}

OneTimeSequence

We can now create our new sequence class. Create a new class named, "OneTimeSequence" and, as we'll be using an interface from the System.Collections namespace, add the following using directive to the source code file.

using System.Collections;

Our sequence class should be able to supply any type of data, so we'll modify it to be a generic type and declare that it implements IEnumerable<T>. To do so, modify the class declaration as follows:

public class OneTimeSequence<T> : IEnumerable<T>
{
}

The OneTimeSequence class works by wrapping a sequence and only permitting it to be enumerated once. We need two fields to control this. The first of these is a Boolean that indicates whether or not the sequence has been enumerated. The second holds the sequence.

Add the following two fields to the class:

bool _enumerated;
IEnumerable<T> _sequence;

The sequence to be wrapped is provided using constructor injection. We could add validation checks to this to ensure that the constructor's parameter is not null. However, as this class is designed for use in test code, rather than in a production-quality library, I've omitted this test.

Add the constructor as follows:

public OneTimeSequence(IEnumerable<T> sequence)
{
    _sequence = sequence;
}

To complete the class we now need to implement the IEnumerable<T> interface members. This means adding two versions of the GetEnumerator method. One returns a generic IEnumerator<T> object and the other, implemented explicitly for the non-generic IEnumerable interface, returns an IEnumerator.

The GetEnumerator method holds the code that makes the OneTimeSequence work. The first task is to check the value in the Boolean field. If true, the sequence has been enumerated already so we throw an exception. If the flag is false, we set it to true to indicate that we have obtained an enumerator. We then return the result of calling the wrapped sequence's GetEnumerator method.

public IEnumerator<T> GetEnumerator()
{
    if (_enumerated) throw new TooManyEnumerationsException();
    _enumerated = true;

    return _sequence.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

Testing the One Time Sequence

We can now try out the new sequence. In the example code, which you can download using the link at the start of this article, I have not created a test project. Instead I've used a console application that exercises the OneTimeSequence class. This is sufficient for the two checks we will now perform.

The first test is that we can use the sequence as long as we only enumerate it once. We should also ensure that we can chain LINQ standard query operators together using a OneTimeSequence as the source data.

Try running the following code. You should find that it operates correctly, producing the expected results.

var sequence = new OneTimeSequence<int>(new int[] { 1, 2, 3 });
var second = sequence.Skip(1).Take(1).ToList();

To show what happens when you attempt to use the sequence twice, try running the code below. For a sequence that has no limitations, this would retrieve the first half of a sequence. First the number of items is found using the Count method. Next, the Take operator is used to obtain half of this number of items from the start of the sequence. When using the single-pass sequence the Count call is successful but causes the GetEnumerator method to be used. The second enumeration, required by Take, throws the TooManyEnumerationsException.

var firstHalf = sequence.Take(sequence.Count() / 2).ToList();
28 October 2012