Подтвердить что ты не робот

Странные результаты тестирования покрытия для блока итератора, почему эти утверждения не выполняются?

Я использую dotCover для анализа охвата кода моих модульных тестов, и я получаю некоторые странные результаты... У меня есть метод итератора, для которого охват не завершен, но заявления, которые не охвачены, - это просто закрывающие фигурные скобки в конце метода.

Здесь метод, который я тестирую:

    public static IEnumerable<T> CommonPrefix<T>(
        this IEnumerable<T> source,
        IEnumerable<T> other,
        IEqualityComparer<T> comparer)
    {
        source.CheckArgumentNull("source");
        other.CheckArgumentNull("other");

        return source.CommonPrefixImpl(other, comparer);
    }

    private static IEnumerable<T> CommonPrefixImpl<T>(
        this IEnumerable<T> source,
        IEnumerable<T> other,
        IEqualityComparer<T> comparer)
    {
        comparer = comparer ?? EqualityComparer<T>.Default;

        using (IEnumerator<T> en1 = source.GetEnumerator(),
                              en2 = other.GetEnumerator())
        {
            while (en1.MoveNext() && en2.MoveNext())
            {
                if (comparer.Equals(en1.Current, en2.Current))
                    yield return en1.Current;
                else
                    yield break;
            }
        } // not covered
    } // not covered

unit test:

    [Test]
    public void Test_CommonPrefix_SpecificComparer()
    {
        var first = new[] { "Foo", "Bar", "Baz", "Titi", "Tata", "Toto" };
        var second = new[] { "FOO", "bAR", "baz", "tata", "Toto" };

        var expected = new[] { "Foo", "Bar", "Baz" };
        var actual = first.CommonPrefix(second, StringComparer.CurrentCultureIgnoreCase);
        Assert.That(actual, Is.EquivalentTo(expected));
    }

И результаты покрытия:

coverage results

Я предполагаю, что закрывающая фигурная скобка блока using на самом деле является вызовом Dispose на счетчиках; но тогда, почему это не выполнено? Я сначала подозревал, что NUnit не утилизировал счетчики, но я получаю тот же результат, если я делаю foreach на actual.

Что касается второй непокрытой закрывающей скобки, я не знаю, что она означает... Я предполагаю, что это связано с тем, как компилятор преобразует блок итератора.

Может ли кто-нибудь пролить свет на то, что эти два "заявления", и почему они не выполняются?


EDIT: Питер поставил очень хороший вопрос: результаты, показанные выше, были получены при запуске тестов в отладочной версии. Если я запускаю тесты в выпуске, охват метода CommonPrefixImpl равен 100%, поэтому он, вероятно, связан с оптимизацией компилятора.

4b9b3361

Ответ 1

Одна из проблем с методами итератора заключается в том, что компилятор создает довольно большой и сложный конечный автомат для управления отложенным выполнением кода в методе итератора. Обычно это генерирует класс или два. Эти классы предназначены для решения общего случая, а не вашего конкретного случая, поэтому там, по крайней мере, есть немного кода, который никогда не используется. Вы можете посмотреть, что генерируется, глядя на вашу сборку с помощью таких инструментов, как ILSpy, JustDecompile или Reflector. Он покажет классы в вашей сборке, сгенерированные компилятором С# (обычно имена классов, содержащие "<" и т.д.).

То, что профайлер знает о том, как PDB связывается с вашим кодом, и, несмотря на то, что весь написанный вами код, возможно, выполняется, все же существует вероятность того, что не весь код, сгенерированный компилятором, будет выполнен. Профилировщик, вероятно, этого не знает и просто говорит, что был выполнен определенный процент (менее 100) конкретного метода итератора.

Одной из вероятных попыток получения кода является обработка исключений. Поскольку компилятор не знает, что ваш код не будет или, возможно, не сможет генерировать исключение, он все равно будет генерировать код, чтобы компенсировать исключение, - он должен держать его в состоянии от коррупции. Бьюсь об заклад, если вы включили способ выбросить исключение в разных местах вашего метода итератора на основе некоторого флага и дважды запустить метод (один раз без исключений и один раз с исключениями в одном и том же прогоне), что проценты будут разными - вероятнее всего потому что тогда будет создан обработанный код обработки исключений.

Тот факт, что конец метода "кажется" не выполняется, вероятен, потому что этот код является частью другого метода в конечной машине, которая запускается, и компилятор никогда не генерирует связь с этим сгенерированным кодом с кодом в вашем классе.

UPDATE:, чтобы лучше понять, что делает компилятор, и посмотреть пример типа генерируемого кода, см. раздел 10.14. Итераторы в спецификации С# (http://www.microsoft.com/en-us/download/details.aspx?id=7029)

Ответ 2

В дополнение к вашему вопросу и подробному ответу у меня было следующее поведение.

    // less than 100% coverage
    public static IEnumerable<T> ForEachYieldDo<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var x in source)
        {
            action(x);
            yield return x;
        }
    }

    // 100% code coverage
    public static IEnumerable<T> ForEachSelectDo<T>(this IEnumerable<T> source, Action<T> action)
    {
        return source.Select(x =>
        {
            action(x);
            return x;
        });
    }

Обе функции имеют одинаковое поведение. Действие выполняется, только если элемент обработан. Если поиск элементов остановлен, действие не выполняется.