views:

1570

answers:

3

I have a unit test that will test to see if a method that returns the correct IEnumerable. The method builds the IEnumerable using yield return. The class that it is an IEnumerable of is below:

enum TokenType
{
    NUMBER,
    COMMAND,
    ARITHMETIC,
}

internal class Token
{
    public TokenType type {get; set;}
    public string text {get; set;}
    public static bool operator == (Token lh, Token rh) { return (lh.type == rh.type) && (lh.text == rh.text);}
    public static bool operator !=(Token lh, Token rh) { return !(lh == rh); }
    public override int GetHashCode()
    {
        return text.GetHashCode() % type.GetHashCode();
    }
    public override bool Equals(object obj)
    { return this == (Token)obj; }
}

This is the relevant part of the method:

 foreach (var lookup in REGEX_MAPPING)
 {
     if (lookup.re.IsMatch(s))
     {
         yield return new Token { type = lookup.type, text = s };
         break;
     }
 }

If I store the result of this method in actual and make another enumerable expected and compare them like this:

  Assert.AreEqual(expected, actual);

...the assertion fails.

I wrote an extension method for IEnumerable that is similar to Python's zip function (it combines two IEnumerables into a set of pairs), and tried this:

foreach(Token[] t in expected.zip(actual))
{
    Assert.AreEqual(t[0], t[1]);
}

...and it worked! So what is the difference between these two Assert.AreEquals?

+2  A: 

Assert.AreEqual is going to compare the two objects at hand. IEnumerables are types in and of themselves, and provide a mechanism to iterate over some collection...but they are not actually that collection. Your original comparison compared two IEnumerables, which is a valid comparison...but that wasn't what you needed. You needed to compare what the two IEnumerables were intended to enumerate.

Here is how I compare two enumerables:

Assert.AreEqual(t1.Count(), t2.Count());

IEnumerator<Token> e1 = t1.GetEnumerator();
IEnumerator<Token> e2 = t2.GetEnumerator();

while (e1.MoveNext() && e2.MoveNext())
{
    Assert.AreEqual(e1.Current, e2.Current);
}

I am not sure if the above is less code than what your .Zip method or not, but its about as simple as it gets.

jrista
This makes sense. Is there not any way to compare the two IEnumerables object by object without having to write as much code as it took?
Jason Baker
I would just use a while loop. I've updated my answer with an example.
jrista
I found another way that works and is simpler. I'll accept this since you showed why my code wasn't working though. :-)
Jason Baker
Some pointers: that approach involves checking both sequences twice (once for count, once per item) - not very efficient. Plus, IEnumerator<T> is IDisposable, so should involve "using". Finally, you can use EqualityComparer<T>.Default.Equals(e1.Current,e2.Current) to avoid boxing etc.
Marc Gravell
Yes, it does iterate twice...but it is a simple implementation. ;) There is a more complex version that gets the job done more efficiently, but with less clarity. If your comparing two very large enumerations, a more efficient method would be required, but I like the clarity of mine. (However, using statements should indeed be used...left out for brevity.)
jrista
+12  A: 

Found it:

Assert.IsTrue(expected.SequenceEqual(actual));
Jason Baker
Nice find! I didn't even know there was a SequenceEqual() extension. Ty!
jrista
+4  A: 

Have you considered using the CollectionAssert class instead... considering that it is intended to perform equality checks on collections?

Addendum:
If the 'collections' being compared are enumerations, then simply wrapping in a 'new List<T>(enumeration)' is the easiest way to perform the comparison. Constructing a new list causes some overhead ofcourse, but in the context of a unit test this should not matter too much I hope?

jerryjvl
All the methods in CollectionAssert are set to compare against ICollections though. I have IEnumerables. Is there any easy way to change them to ICollections?
Jason Baker
In cases like that I normally just do a quick wrap-up in a `new List<T>(enumeration)` so that I can perform the comparison. It's not like the overhead is likely to be a problem in the context of a unit test.
jerryjvl