Автоматический анализ покрытия кода с использованием OpenCover + плюшки

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

    Уже сложно найти проект, в котором отсутствуют юнит-тесты. Их использование многим кажется избыточным, ведь это трата времени, которое с тем же успехом можно потратить на написание другого кода и “не, ну я точно знаю, что там все правильно”. Но, как мы убеждаемся, в долгосрочной перспективе тесты экономят больше времени, чем отнимают. Облегчается сопровождение кода, рефакторинг становится безопасным, отслеживается правильность любых изменений. Причем, чем выше покрытие — тем сильнее чувствуется полезность тестов.

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

    Проводить подробный анализ покрытия нам помогает инструмент OpenCover. Он работает с кодом на C#. Это замечательное опенсорсное решение, исходники доступны на гитхабе. Документации не особо много, но вполне хватает.

    Итак, чтобы начать пользоваться OpenCover, достаточно скачать исходники и собрать, используя Visual Studio. OpenCover являет собой консольное приложение, все необходимые опции задаются параметрами командной строки, так что прикрутить к любому сборщику, будь то MsBuild, Nant, Rake или что либо другое, не проблема.

    Интересен механизм работы — OpenCover запускается вместе с прогоном юнит-тестов. Если быть точным, команда на запуск тестов передается ему в качестве нескольких параметров.(Если в аргументе есть пробелы, то он берется в кавычки полностью, например “-target:%application%”):

    • -target:%application% — приложение, которое нужно запустить (В нашем случае — Gallio.Echo, как более распространенный пример — nunit-console). Важное замечание — путь к приложению должен быть абсолютным, так как мы передаем его в качестве параметра, и в переменной Path он искаться не будет.
    • -targetdir:%path% — папка, в которой нужно запустить %application%
    • -targetargs:%args% — параметры, передаваемые %application%

    Кроме того, у OpenCover есть еще аргументы, регулирующие непосредственно его работу. Приведу только те, которые использую сам, остальные можно найти тут

    • -output:%path% — указывает, куда поместить отчет
    • -filter:%filters% — Определяет, что учитывать при анализе. Фильтры имеют формат +[%assembly%]%type% для включаемых сборок/типов и то же самое, но со знаком минус в начале для исключаемых. Фильтров может быть множество, разделяются пробелами, в таком случае аргумент заключается в кавычки. Исключающие фильтры имеют приоритет над включающими.
    • -register — Динамическая регистрация сборки OpenCover.Profiler, необходимо для работы приложения. Можно опустить, если предварительно зарегестрировать вручную с помощью regsvr32.exe. Если у текущего пользователя нет прав админа, нужно использовать -register:user
    • -returntargetcode — Указывает, что нужно возвращать errorlevel, полученный приложением, указаным в параметре -target. Если опустить этот параметр и, например, у вас упадет какой-то тест, то приложение не остановит работу. Необходим, если вы используете анализ покрытия в контексте Continuous Integration, так как можно без проблем совместить анализ с обычным прогоном юнит-тестов и избежать их повторного запуска.

    Итак, нам удалось настроить и запустить OpenCover и мы даже получили отчет в формате xml на много тысяч строк, который содержит очень подробные сведения о покрытии нашего кода. Однако есть проблема — он абсолютно нечитабелен. А хочется наглядно увидеть, что и как покрыто.

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

    • -reports:%reports% — Исходные файлы отчетов, если их несколько, разделяем точкой с запятой
    • -targetdir:%path% — Указывает, куда поместить сгенерированые отчеты
    • -reporttypes:%types% — Типы генерируемых отчетов. Мы используем -reporttypes:Html, нам хватает.

    Получаем вот-такие отчеты:imageimage

    Начало положено, покрытие кода анализируется. Дальше — на ваше усмотрение. Я, например, настроил нашу систему Continuous Integration таким образом, чтобы при покрытии ниже требуемого билд падал. Учитывая строгое отношение к завалившимся билдам, неплохо обеспечивает стабильное написание юнит-тестов :)

    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 5 184 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 14

      0
      Два занятных пункта.

      Во-первых, Code coverage есть в студии, и тоже неплохо интегрируется в билд (а особенно в TFS Build).
      Во-вторых, опыт показал, что покрытие строчки кода совершенно не означает, что покрывающий ее тест что-то проверяет. То есть представьте, что в вашем примере с подсветкой кода есть еще переменная j=0, и внутри if используется она. Покрытие не будет меняться вне зависимости от того, проверяет тест, какая переменная используется, или нет.
        0
        Спасибо.

        1 — я знаком с code coverage, но особенности нашего CI таковы, что нам неудобно его использовать. Да и не особо он понравился, если честно. OpenCover + ReportGenerator — удобно и информативно, а также универсально.

        2 — да, полностью с вами согласен. Но мы говорим об анализе именно покрытия тестами, а не их качества
          0
          А смысл анализировать покрытие кода, если вы не знаете, насколько в реальности это покрытие полезно?

          Я как раз предостерегаю от этой ловушки — думать, что если в проекте 100% покрытие тестами, то там все хорошо. И, соответственно, от (бездумного) использования метрик на code coverage в билде, потому что как раз они приводят к написанию тестов, которые формально код покрывают, а по факту ничего не делают. Это личный только что пережитый опыт.
            0
            Я вас прекрасно понимаю, мы сами наступали на эти грабли. Сейчас приходится переделывать многие старые тесты, написаные «для галочки». Поэтому, уровень покрытия используется совсем не бездумно, но служит лишь одной из метрик, позволяющей оценивать уровень тестирования продукта. Код не пропускается в основной репозиторий без ревью, так что качество все таки проверяется вручную. В сочетании с автоматическим анализом количества покрытого кода, складывается наглядная картина.
              0
              В общем, я бы рекомендовал делать метрику не на некую величину уровня покрытия, а на неуменьшение этой величины. Это позитивнее.
                0
                По сути, у мы так и делаем. Есть минимально допустимый уровень покрытия, например 70%, и эта цифра не должна уменьшаться. В свою очередь, увеличение не является обязательным, но приветствуется :)
        0
        Тот же ReSharper + dotCover позволяет проанализировать покрытие кода, куда лучше ИМХО
          0
          (1) решарпер для этого не нужен, нужен только dotCover
          (2) dotCover, все-таки, платный
            0
            (1) собственно именно для измерения покрытия — нет, но сам процесс тестирования и рефакторинга эта связка делает значительно более приятным.
            (2) да, но смотря для каких проектов использовать, иногда проще заплатить
              0
              Теперь попробуйте прикрутить эту связку на билд-сервер. Раз уж она делает процесс тестирования более приятным.
                0
                Эта связка отлично используется на рабочей машине, но на билд-сервере, увы, бесполезна.
            0
            Боб Мартин рекомендует стремиться к 100% покрытию, хотя сам и говорит, что эта цифра нереальна. Такую рекомендацию он даёт в Clean Code Episode, посвящённому TDD.

            Покрытие нужно для того, чтобы просто понимать, на что у тебя тесты есть, а на что нет.
            Если программист начинает писать тесты, смысл которых равен 0, то его надо вычислять и наказывать за непрофессиональное поведение.
              0
              Покрытие нужно для того, чтобы просто понимать, на что у тебя тесты есть, а на что нет.

              Как уже было сказано выше, покрытие «по исполнению» показывает те места, где тестов точно нет. А вот если строчка исполнена — то еще не факт, что она протестирована.
                0
                Да, совершенно верно. Я не уточнил. Это одна из причин почему 100% покрытие не гарантирует того, что в рантайме не будет багов.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое