Code coverage для интеграционного тестирования
Итак, ситуация следующая. Есть у нас Integration Test Framework, написанный специально для нашего приложения. Но начался он писаться задолго после старта работы над приложением, поэтому накопилось большое количество кода, который тестируется в лучшем случае unit тестами. Как это всё вместе работает не совсем понятно :) Для это писался этот ITF, но он ещё в разработке и тесты пишутся достаточно медленно. Вот в процессе обсуждение этого факта, мне подумалось, а почему бы не считать каково покрытие тестами у нас. Вообще, было замечено, что многие программисты очень плохо относятся ко всяким таким характеристикам как сode coverage. Вот для unit тестов оно у нас не считается, но локально иногда запускаешь посмотреть, сейчас у нас больше 80%. Но, как мне сказали, о чём это говорит? Из за неправильного ответа на этот вопрос и возникает такая не любовь к этой величине. То, что сode coverage 100% это не значит, что вы достигли просветление, ваше приложение идеально и вы на пути к нирване. Но это говорит о том, что весь написанный код, действительно вызывается, т.е. это немного спасает от простых ошибок.
Но вернёмся к ITF. Почему я считаю, что для интеграционных тестов сode coverage более полезен.
- Меньше возможностей для искусственной манипуляции результатами. Как иногда бывает с простыми численными характеристиками, за что их и не любят, люди увлекаются попыткой их достижения. И если мы говорим о unit тестах, то достаточно просто написать много, по своей сути бесполезных тестов, которые дадут требуемую цифру. Это ещё одна из причин, почему test first :) Но для интеграционных тестов это не так. Они не работают напрямую с методами или классами. Они используют исключительно пользовательское api, создание файла, нажатие на кнопку и т.п. Поэтому пытаться искусственно делать 100% покрытие очень трудоёмкая задача. Кроме того, даже для ручного тестирования, многие все равно прикручивают сode coverage, чтобы увидеть это заветное число, после того как тестировщики погоняют приложение. Это даёт много интересной информации для размышления.
- Мы оказались в ситуации, что интеграционные тесты приходится писать вдогонку. Уже есть большая часть приложения, команда разрослась почти до 10 человек и всего 2 тестировщика. Таким образом, отслеживая величину покрытия, можно делать выводы о том, мы догоняем или всё таки уже опоздали. Т.е. если на предыдущей недели было 20%, а на этой стало 23%, то мы на верном пути, но если стало 10%, то слишком увлеклись генерированием не проверенного кода и наверно стоит поднапрячь программистов, чтобы они побольше внимания уделяли написанию интеграционных тестов в помощь тестировщикам.
В результате всё таки создал такой таск и решил на досуге посмотреть, что может предложить open source java community поэтому поводу. Надо была попроще библиотека, которая позволяла посчитать покрытие кода, по результатам работы приложения. начал конечно же с
Open Source Software in Java. В принципе, предложения есть. Я выбрал последнее с "оригинальным" названием
CodeCover. Последняя версия можно считать 1.0.* оформленная ажно в 2009 году, но плагин под эклипс обновлялся в 2010, да и остальные тулы, тоже не сильно новее. Маленький проектик hoto можно увидеть под катом, если заинтересовало.
Итак, качаем архив. И папку lib из него копируем к себе в проект. Работать можно двумя способами с консолью или антом. Я выбрал конечно же второй. Теперь об интересном, посмотрев опции, можно заметить, что данная библиотека умеет работать с несколькими языками, как оказалось это java и cobol. Собственно интересный выбор :) Ну мне то первый нужен, так что ок.
Второй интересный факт. Обычно code coverage утилиты используют
Instrumentation, чтобы модифицировать bytecode, добавив в него свои счётчики. Но тут разработчики пошли другим путём, возможно из за того, что они решили сделать поддержку нескольких языков. Модифицируется исходный код, точнее генерируется новый исходный код, на базе существующего, с добавленными счётчиками.
Так что первой задачей для анта, будет сгенерировать такой модифицированный код.
- <project name="CodeCover" default="create.report">
-
- <!-- тут располагаются все библиотеки CodeCover, причём именно в такой структуре, как были в архиве, т.е. с подкатологом plugins -->
- <property name="codecover.dir" value="lib" />
-
- <!-- тут будут наши исходные коды
- <property name="source.dir" value="src" />
-
- <!-- в эту директорию будет помещён сгенерированные исходные коды -->
- <property name="instrumented.source.dir" value="instrumented" />
-
- <property name="resources.dir" value="resources" />
-
- <property name="target.dir" value="bin" />
-
- <!-- подключаем специальную ant таску, которая нам и позволит работать с библиотекой -->
- <taskdef name="codecover" classname="org.codecover.ant.CodecoverTask" classpath="${codecover.dir}/codecover-ant.jar" />
-
- <fileset dir="${codecover.dir}" id="libs.fileset">
- <include name="org.codecover.instrumentation.java15-0.1.jar" />
- </fileset>
-
- <path id="compile.sources.classpath">
- <fileset refid="libs.fileset" />
- </path>
-
- <path id="run.classpath">
- <fileset refid="libs.fileset" />
- <pathelement location="${target.dir}" />
- </path>
-
- <target name="clean">
- <delete>
- <fileset dir="." includes="*.clf" />
- </delete>
- <delete file="codecover.xml" />
- <delete file="report.html" />
- <delete dir="report.html-files" />
- </target>
-
- <!-- собственно вот главный нужный на таргет, который и производит генерацию модифицированных исходных кодов -->
- <target name="instrument.sources" depends="clean">
- <codecover>
- <instrument containerId="c" language="java" destination="${instrumented.source.dir}" charset="utf-8" copyUninstrumented="yes">
- <source dir="${source.dir}">
- <include name="**/*.java" />
- </source>
- </instrument>
- <save containerId="c" filename="codecover.xml" />
- </codecover>
- </target>
Запустив instrument.sources, в папке instrumented мы получим наши же исходники, только с кучей непонятного кода, приводить не буду, потому что даже простой класс в 30 строчек, разрастается более чем в 3 раза. Но, не суть важно. На нашем реальном приложении с около 200 классами, такая генерация у меня заняла где-то секунд десять. В принципе не критично. Теперь нам необходимо их скомпилировать. Тут совершенно ничего сложного нет, используем обычную javac таску, только в classpath необходимо добавить библиотеку org.codecover.instrumentation.java15-0.1.jar.
- <target name="compile.instrumented" depends="instrument.sources">
- <javac srcdir="${instrumented.source.dir}" destdir="${target.dir}" encoding="utf-8" target="1.5" debug="true" includeAntRuntime="false">
- <classpath refid="compile.sources.classpath" />
- </javac>
- </target>
Собственно, дальше поступаем с class файлами как обычно, например заворачиваем в jar и его используем, я поступил проще, сделал ещё одну ant таску, которая запускает приложение. Тут важен ещё один момент, лучше при запуске приложения указать системное свойство org.codecover.coverage-log-file, это имя файла в который будут записана информация о вызовах методов и инструкций, это имя будет полезно при дальнейшем формировании отчёта. Если не укажите, ничего страшного, библиотека сама сгенерирует уникальное имя.
- <target name="run.instrumented" >
- <java fork="true" failonerror="true" classname="me.drobushevich.blog.codecover.Main">
- <jvmarg value="-Dorg.codecover.coverage-log-file=codecover.clf" />
- <classpath refid="run.classpath" />
- </java>
- </target>
Идём дальше, лог файл, это простой текстовый файл, можно конечно его посмотреть, но хотелось бы получить отчёт. CodeCover умеет делать отчёты в двух форматах, html для просмотра и csv, для использование в других приложения, например выбрать только топ информацию. В архиве на сайте, который был скачен, есть три темплейта, один для csv, и два для html. HTML_Report_SingleFile.xml - вот этот не рекомендую, для нашего приложения, достаточно небольшого, получился html файл в 14 мегобайт. Браузеры не очень хорошо работают с такими файлами :) Если нужен одиночный html, то лучше использовать csv формат, а потом самостоятельно формировать попроще и меньше html файл, только с ключевыми данными. Я воспользовался шаблонам HTML_Report_hierarchic.xml. ant target для формирования отчёта так же не представляет трудностей.
- <target name="create.report" depends="run.instrumented">
- <codecover>
- <load containerId="c" filename="codecover.xml" />
- <analyze containerId="c" coverageLog="codecover.clf" name="Run session" />
- <save containerId="c" filename="codecover.xml" />
-
- <report containerId="c" destination="report.html" template="${resources.dir}/report-templates/HTML_Report_hierarchic.xml">
- <testCases>
- <testSession pattern=".*">
- <testCase pattern=".*" />
- </testSession>
- </testCases>
- </report>
- </codecover>
- </target>
Для нашего проекта, генерация отчёта заняла чуть больше минуты, не так много, но и не мало.
А вот как это выглядит главная страница отчёта.
А так же очень понравились страницы для методов. Так как там, красиво разукрашены куски методов в соответствии с их покрытием.
Правда, я так и не понял, что не так с этим for'ом :)/
Ну а с большего, на реальном проекте, показала более менее интересное покрытие.
Вдруг кого заинтересовал,
маленький проектик с ant скриптом расшарил.
Комментариев нет:
Отправить комментарий