Nano Hash - криптовалюты, майнинг, программирование

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

Я использую 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

Модульный тест:

    [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));
    }

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

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

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

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

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


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


  • Это с отладочной или выпускной сборкой? 15.08.2012
  • @PeterRitchie, хорошая мысль! Я провел тесты на отладочной сборке. Я только что попробовал еще раз с релизной сборкой, и теперь покрытие составляет 100%. Однако меня все еще интересует объяснение ... Я точно не знаю, что отличается от отладочной сборки. 15.08.2012
  • Я дал ответ о том, как правильно инструментировать код, чтобы вы не столкнулись с этой проблемой, отметив, что бинарным инструментариям довольно трудно сделать это правильно, и предоставил ссылку на инструмент, который правильно выполняет инструментирование. Модератор удалил этот ответ. 15.08.2012
  • @IraBaxter, да, я это вижу; не уверен, почему это было удалено. В любом случае, спасибо за ваш ответ, но я не ищу другого инструмента. Проблема, которую я описал, не является серьезной (тем более, что она исчезает с релизной сборкой), мне просто было интересно узнать, почему это произошло. 15.08.2012

Ответы:


1

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

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

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

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

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

15.08.2012
  • Спасибо за подробный ответ! Есть ли у вас какие-либо идеи, почему я получаю 100% покрытие, когда я запускаю тест на релизной сборке? 15.08.2012
  • Я не могу быть уверен, но компилятор не так много оптимизирует в режиме отладки, поэтому вполне возможно, что он оптимизирует код, который ему не нужен. Но это только одна вероятная возможность. 15.08.2012
  • Обычно для такого кода (т. е. пустых конструкторов и т. д.) вы можете добавить System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute. -- Этот автоматически сгенерированный код, созданный компилятором, есть ли способ заставить его иметь этот атрибут, чтобы он не влиял на результаты покрытия кода? 12.06.2015
  • Нет, нет никакого способа заставить этот атрибут. dotCover должен быть достаточно умным, чтобы не профилировать код, сгенерированный компилятором (хотя на нем есть атрибуты сгенерированного кода). Генерируется все, что содержит '‹' в имени, это легко проверить. 13.06.2015

  • 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;
            });
        }
    

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

    22.09.2019
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..