How do you test the IEnumerable.GetEnumerator() method?
Asked Answered
B

4

19

Let's say I for example have this class that generates Fibonacci numbers:

public class FibonacciSequence : IEnumerable<ulong>
{
    public IEnumerator<ulong> GetEnumerator()
    {
        var a = 0UL;
        var b = 1UL;
        var c = a + b;
        while (true)
        {
            yield return c;
            c = a + b;
            a = b;
            b = c;
        }
    }

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

I can then write a test that makes sure that the n first numbers in the sequence are correct.

[Test]
public void GetEnumerator_FirstFifteenNumbers_AreCorrect()
{
    var sequence = new FibonacciSequence().Take(15).ToArray();
    CollectionAssert.AreEqual(sequence,
        new[] {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610});
}

When I check for coverage however, I will see that the non-generic IEnumerable.GetEnumerator() method is untested, and my coverage will be lower than it really needs to be. Fair enough. But how should I test that method?

How do you usually deal with this?

Braud answered 2/10, 2009 at 14:54 Comment(3)
hmm, out of curiosity how do you check for test coverage? Sounds like an interesting feature.Herbartian
Yeah, I wondered about that too, hehe. But found a button for it in TestDriven.Net, which is pretty awesome by the way. If you haven't tried it, you should! After it is installed, you can right-click on your solution (in solution explorer) and select Test With -> Coverage. Easy as that :)Braud
If you have VS Team System edition, the test tools include a coverage tool as well, which you can trigger with TestDriven.Net or in the regular interface. Otherwise, if you google test coverage tools for Visual Studio, there are several out there. NCover might be the most used.Bio
S
14

EDIT: Updated based on what Marc said.

Well you could get the coverage up by doing:

// Helper extension method
public static IEnumerable AsWeakEnumerable(this IEnumerable source)
{
    foreach (object o in source)
    {
        yield return o;
    }
}

...

[Test]
public void GetEnumerator_FirstFifteenNumbers_AreCorrect()
{
    IEnumerable weak = new FibonacciSequence().AsWeakEnumerable();
    var sequence = weak.Cast<int>().Take(15).ToArray();
    CollectionAssert.AreEqual(sequence, 
        new[] {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610});
}

Note that weak is declared to be the nongeneric IEnumerable type... which means you need to call Cast on it to cast each returned object to int.

I'm not sure I'd bother though...

Sruti answered 2/10, 2009 at 14:57 Comment(4)
No - I checked, and Cast<T> actually checks via as for IEnumerable<T>, so you'll end up using IEnumerable<T>.GetEnumerator(), not IEnumerable.GetEnumerator()Ardelia
That's what I saw too. So then I wasn't quite sure how to go about doing it, hehe.Braud
Actually ended up doing something similar to this. And I figured I could just skip the whole Cast and Take thing too, just sent it into the CollectionAssert like it is. Back to 100% test coverage :pBraud
What I ended up doing, since as I commented to Marc have some infinite sequences, was to implement a Take method for nongeneric IEnumerable :)Braud
H
5

I wouldn't test it. I would try to filter the method out of the coverage tool. I think coverage should check things I want to have covered and not everything. From other comments you seem to be using TestDriven.Net. I don't know how well that filters but it was possible with NCover. You could try PartCover also.

Harbert answered 2/10, 2009 at 15:2 Comment(5)
Of course, and I am not beating myself up over this or anything. Just curious to see how others deal with it :)Braud
@Braud - sorry, the answer does sound snippier than I intended. But I would really look into filtering things out. I think it is better than trying to add a test for it.Harbert
Unit tests are the one kind of test where it's most economically feasible to achieve 100% code coverage. If you don't get it there, you'll probably never achieve it at all, as the cost for coverage increases exponentially with number of components in integration/functional tests. If you don't get 100% coverage at some level in your test suites, you're shipping bugs for your customer's to discover for you, that you could easily have avoided. And 100% coverage is also an excellent aid for refactoring, as well the occasional cause of it.Bieber
@Bieber A good point, but I still think filtering it is better in this case. Measure coverage over what makes sense to test. In this case the OP was asking about testing the IEnumerable.GetEnumerator() method only. I was suggesting that since it really just delegates you should try to exclude it from coverage since it's just testing the delegated method's code and therefore essentially covering the same method twice. It's certainly debatable.Harbert
From my perspective, it really depends on the class to be tested. If you are implementing a custom collection (and not using an existing collection type in the background) it might make sense to test it.Schwitzer
A
4

You would have to use use IEnumerable (non-generic); I posted a reply using Cast<T>, but that will still cheat (it checked for the desired type as a special case) - you might need something like:

public static int CountUntyped(this IEnumerable source) {
    int count = 0;
    foreach(object obj in source) { count++; }
    return count;
}

IEnumerable<T> source = ...
Assert.AreEqual(typed.Count(), source.CountUntyped());
Ardelia answered 2/10, 2009 at 14:56 Comment(2)
But that would just compare the count of items in the enumerables though, wouldn't it? And that would work kind of badly in my case, since the FibonacciSequence never really ends :pBraud
Lol! Yes, I take your point there. But the thing I'm trying to highlight is working with IEnumerable rather than IEnumerable<T> - and I don't think you can simply use Cast<T>, 'cos it looks...Ardelia
S
-1

For my case, I just added a simple helper method to assert the returned IEnumerator<T>:

private void AssertEnumerator<T>(IEnumerator<T> expected, IEnumerator<T> actual)
    {
        while (true)
        {
            var expectedMoveNext = expected.MoveNext();
            Assert.AreEqual(expectedMoveNext, actual.MoveNext());

            if (!expectedMoveNext)
                break;

            var expectedCurrentItem = expected.Current;
            var actualCurrentItem = actual.Current;
            Assert.AreEqual(expectedCurrentItem, actualCurrentItem);
        }
    }
Schwitzer answered 11/10, 2023 at 7:29 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.