Вебинар: C# разработка и статический анализ: в чем практическая польза? - 18.11
В статье описаны технологии тестирования, используемые при разработке статического анализатора кода PVS-Studio. Разработчики инструмента для программистов делятся принципами тестирования собственного программного продукта, которые могут быть интересны разработчикам аналогичных пакетов обработки текстовых данных или исходных кодов.
Занимаясь разработкой и продвижением программного продукта PVS-Studio, мы огромное внимание уделяем вопросам качества программного обеспечения, процессам разработки и принципам организации труда программистов. При этом до сих пор закрытым оставался вопрос о том, как же, собственно, мы сами разрабатываем свой программный продукт? Используем ли те технологии, рекомендации и практики, о которых пишем в статьях? Наконец, относится ли к нам фраза "сапожник без сапог".
В этой статье мы решили рассказать о том, как мы тестируем программный продукт PVS-Studio. С одной стороны, это делается для того, чтобы убедить наших (потенциальных) пользователей в качестве нашего инструмента. С другой стороны - рассказать об успешном опыте применения некоторых практик.
PVS-Studio - это статический анализатор кода, предназначенный для разработчиков современных ресурсоемких приложений на языках Си и Си++. Под современными мы понимаем 64-битные и/или параллельные приложения. Разработка таких программ имеет ряд трудностей, отличных от проблем традиционных программ. Ведь помимо обычных и всем известных ошибок вроде неинициализированных указателей, которые обнаруживаются любым компилятором, есть и специфичные виды проблем.
Речь идет об ошибках в программах, которые проявляются при миграции 32-битных приложений на 64-битные платформы. Или при распараллеливании кода для поддержки многопроцессорности или многоядерности. Разрабатывать такие приложения довольно сложно из-за недостатка инструментов, облегчающих создание 64-битных и параллельных программ. Анализатор PVS-Studio является именно таким инструментом.
Разрабатывая PVS-Studio, мы используем шесть основных методики тестирования:
Кратко опишем здесь эти методики.
Поскольку PVS-Studio - это статический анализатор кода, то при его разработке мы также используем методику статического анализа кода для поиска проблем в анализаторе. Во-первых, это нужно для того, чтобы нас не упрекнули в том, что мы не пользуемся своим продуктом. А во-вторых, это реально позволяет находить ошибки до того, как их найдут пользователи.
Юнит-тесты уровня классов, методов функций позволяют нам быть уверенными в том, что добавление новой функциональности не портит имеющийся код. На этом уровне проверяются отдельные программные сущности в виде простеньких маленьких тестов. В общем, самые обычные юнит-тесты.
Функциональные тесты уровня самостоятельно написанных файлов позволяют быстро проверять, что всё, что должно диагностироваться - диагностируется как и раньше. Все обнаруживаемые потенциальные проблемы в коде должны обнаруживаться.
Функциональные тесты уровня отдельных файлов позволяют убедиться, что различные отдельные файлы с кодом полностью и без проблем проверяются анализатором. Под проблемами здесь понимаются срабатывающие ASSERT, неожиданные сообщения об ошибках, а также падения. Ведь анализатор кода - это такая же программа, как и другие, и падения в ее работе, увы, не редки.
Функциональные тесты уровня отдельных сторонних проектов и решений позволяют убедиться в том, что анализатор все еще умеет проверять проекты, а количество диагностических сообщений от версии к версии изменяется контролируемо, а не хаотично.
Функциональные тесты пользовательского интерфейса позволяют проверить работоспособность плагина – надстройки, с помощью которого анализатор интегрируется в среду Visual Studio. При этом проверке подвергаются как доступность и адекватная работа отдельных элементов интерфейса, так и ожидаемая взаимосвязь этих элементов друг с другом и с работой самого анализатора кода.
Конечно же, помимо этих методик, есть и другие стандартные подходы вроде "зоркий глаз программиста" или "у нас завтра релиз, проверьте ЭТО", но они показали свои недостатки и мы стараемся их не применять.
Теперь же расскажем об используемых приемах более подробно.
Заголовок раздела вызывает недоумение? Кто же еще может выполнять статический анализ кроме статического анализатора? Однако при разработке инструментов для программистов всегда есть нюансы.
Как вы наверняка знаете, первые версии компиляторов языков программирования редко пишутся сразу же на этих языках. Как правило, для разработки компилятора нового языка используется совсем другой язык, являющийся стандартом на тот момент. Это сегодня все компиляторы Си++, пишутся на Си++, а первая версия была написана на Си.
Точно так же, разрабатывая первую версию статического анализатора кода, мы не могли ее проверить. Именно поэтому первая версия нашего продукта (тогда он назывался еще Viva64) вовсе не была 64-битной! Зато уже версия 1.10, которая появилась 16 января 2007 года, то есть через 17 дней после выпуска первой версии, среди нововведений содержала строку:
Таким образом, как только у нас появился статический анализатор, выявляющий проблемы 64-битного кода, так мы сразу стали проверять им наш же код.
Помогает ли нам статический анализ нашего же продукта? Конечно же, да. Но опять-таки из-за нашей специфики есть нюансы. Поскольку мы сами пишем статьи про то, как надо делать 64-битный код, то 64-битных ошибок в новом коде мы все-таки не допускаем. Однако от применения статического анализа мы извлекаем пользу в плане улучшения диагностики. Например, мы можем посмотреть, какие типы синтаксических конструкций дают явно ложное срабатывание и исключить их диагностирование.
Таким образом, применение статического анализа для разработки статического анализатора оказывается полезным для нас.
Юнит-тесты уровня классов, методов, функций представляют собой набор тестов для проверки отдельных логических элементов программы. Вот некоторые примеры функциональности, на которую у нас есть юнит-тесты:
Естественно, этими областями наши юнит-тесты не ограничиваются, но являются показательными с точки зрения примеров.
Как мы пользуемся этими юнит-тестами? При исправлении ошибки в анализаторе или при добавлении новой функциональности юнит-тесты запускаются в режиме release-сборки. Если тесты проходят без проблем, то тесты запускаются в режиме debug-сборки под отладчиком. Это делается для того, чтобы проверить, не срабатывают ли ASSERT, которых в коде предостаточно. Позднее будет понятно, почему сразу нельзя запускать debug-версию.
Хотя такие тесты позволяют выявить ошибки, они не являются полноценным решением. Дело в том, что у нас в PVS-Studio очень мало юнит-тестов по сравнению с тем, сколько юнит-тестов нужно для проверки "почти компилятора". Поэтому достичь юнит-тестами большого покрытия в нашем случае очень сложно. Это огромная по трудозатратам задача, которую в настоящее время реализовать нет возможности.
Тем не менее, юнит-тесты уровня классов, методов, функций - это первый уровень "обороны" в нашей системе тестирования.
При разработке анализатора кода важно не "потерять" диагностику тех потенциальных ошибок, которые уже давно обнаруживаются. Это достигается за счет функциональных тестов уровня самостоятельно написанных файлов. Абсолютно все обнаруживаемые потенциальные проблемы в виде кода собраны в отдельные файлы. Эти файлы размечены определенным образом. В тех строках, где анализатор кода должен обнаружить ошибки, стоят специальные маркерные символы. Причем в этих маркерах указано, сколько ошибок в данной строке должно быть: одна, две и т.д. Как только из-за ошибки разработчиков что-то перестает диагностироваться, мы сразу же это видим. Раньше в строке номер 17 выдавалось сообщение о двух ошибках, а теперь только об одной. Или наоборот, появились лишние сообщения.
Такой подход очень похож по принципу работы на функциональные тесты уровня отдельных проектов (о которых будет сказано ниже), но отличается очень высокой скоростью работы и тем, что проверяемые файлы написаны (и размечены) самостоятельно.
Кроме того, эту систему можно использовать, разумеется, и при разработке новых диагностических сообщений. Сначала надо в файлах вручную написать код, в котором будет диагностироваться новая ошибка, затем разметить его и можно приступать к реализации собственно диагностики ошибки. Пока реализации нет, система тестирования будет сообщать, что в этом месте должна быть диагностируема ошибка, но ее нет. Как только диагностика появится, тест будет проходить нормально.
Проверка не отдельных классов/методов или вручную сделанных файлов, а файлов из реальных проектов позволяет нам добиться большего покрытия кода. Четвертым уровнем в нашей системе тестирования является именно такая проверка. В наши тесты включены отдельные полностью препроцессированные файлы из различных проектов: wxWidgets, fox-toolit, CImg, Lame, Boost, CxImage, FreeType и многих других. Также сюда входят препроцессированные файлы, построенные на основе стандартных системных заголовочных файлов (CRT, MFC и так далее).
После добавления новой или исправления старой функциональности программист запускает сначала release-версию тестов, а потом debug-версию. Почему сразу не запускать debug-версию? Очень просто, Debug-версию не запускают сразу потому, что release-версия тестов работает одну минуту, а debug-версия - пять минут.
Тестирование на уровне отдельных файлов - очень мощная вещь. Она позволяет мгновенно выявить ошибку, если в результате развития анализатора какая-то функциональность "отвалилась". Огромное количество ошибок было не допущено благодаря этим тестам.
Самая мощная часть нашей системы тестирования - это ее пятый уровень, который представляет собой проверку отдельных проектов и решений (projects and solutions). Именно благодаря этой системе каждая новая версия PVS-Studio как минимум не хуже предыдущей.
Выглядит эта система следующим образом. Имеется несколько десятков проектов и решений различных доступных в интернете программ. Например: Lame, Emule, Pixie, Loki и другие. Каждый из этих проектов проверен с помощью PVS-Studio, результаты проверки (в виде log-файла) сохранены. После установки новой (разрабатываемой версии) запускается специальная разработанная нами система, которая по очереди открывает каждый проект, проверяет его с помощью PVS-Studio, сохраняет результаты, а затем сравнивает их с эталонными. Если есть отличия, то она их записывает в отдельный файл (аналог стандартного diff), который легко можно посмотреть с помощью PVS-Studio и разобраться в причине появления этих отличий.
Например, в новой версии PVS-Studio появилось новое диагностическое сообщение с кодом V118. Мы запускаем систему тестирования, и она должна сообщить, что в некоторых проектах появились сообщения V118. Затем мы вручную просматриваем все изменения в результатах и решаем, правильно ли выдано сообщение V118.
Если же при этом помимо появления сообщения V118 мы видим в результатах, что пропали некоторые сообщения V115, то это означает, что тесты показали недостаток текущей версии программы, и она отправляется обратно на доработку. В случае признания всех изменений справедливыми новые файлы отчетов признаются эталонными, и сравнение выполняется уже с ними.
Эта система имеет и другое назначение. Поскольку продукт PVS-Studio предназначен для работы как в Visual Studio 2005, так и в Visual Studio 2008 и 2010, то мы всегда проверяем, чтобы диагностические сообщения всегда совпадали в разных версиях Visual Studio. То есть если, к примеру, мы получили во всех проектах 10 000 диагностических сообщений в Visual Studio 2005, то и в Visual Studio 2008 и 2010 мы должны получить ровно столько же сообщений.
Сколько времени занимает такая проверка? На этот вопрос нет однозначного ответа, покольку база этих тестов постоянно растет за счет новых проектов. И, естественно, время работы постоянно увеличивается. Год назад, когда анализатор работал только с использованием одного ядра, тесты выполнялись около часа. Затем мы сделали возможным работу в несколько потоков, и для двухъядерной машины время работы сократилось почти вдвое. Со временем мы добавляли все новые проекты и в тесты. Теперь на той же двухъядерной машине эти тесты отрабатывают чуть больше, чем за час. Все это происходит, естественно, в release-версии.
На шестом уровне находится система автоматизированного функционального тестирования пользовательского интерфейса. В связи с тем, что сам анализатор PVS-Studio представляет собой консольное приложение, он не имеет собственного пользовательского интерфейса. Однако для его работы и проверки файлов с исходным кодом требуется сбор данных о параметрах компиляции проверяемых им файлов и создание множества временных конфигурационных файлов, что, безусловно, неудобно делать вручную. Поэтому, по аналогии с компилятором Visual C++, всё это скрыто от конечного пользователя и автоматизировано с помощью плагина-надстройки для среды разработки Visual Studio. Тестирование GUI данной надстройки и происходит на 6-ом уровне нашей системы, т.е. фактически проверяется та дополнительная функциональность интерфейса среды Visual Studio, которую обеспечивает интегрированная в неё PVS-Studio.
Для покрытия функциональных возможностей PVS-Studio система использует 3 тестовых набора (по одному для каждой из поддерживаемых версий Visual Studio), каждый из которых состоит из отдельных тестовых сценариев (вариантов использования). Каждый такой сценарий содержит верификацию порядка 3-5 вариантов тестирования (набор условий, определяющих, удовлетворяется ли заранее определённое требование). Каждый тестовый набор содержит одинаковый набор сценариев и одинаковые требования для каждого варианта тестирования, т.к. предполагается, что анализатор PVS-Studio должен функционировать одинаково во всех версиях Visual Studio.
Система тестирования интерфейса реализована на базе встроенной системы модульного тестирования Visual Studio Team Test. (расширение для тестирования графического интерфейса появилось в Visual Studio начиная с версии 2010). Стоит отметить, что для данной системы интерфейсы Visual Studio версий 2005 и 2008 идентичны (т.е. имеют одинаковый UI Mapping и покрываются одной реализацией тестовых сценариев). Visual Studio 2010 же имеет новый интерфейс, основанный в основном на WPF элементах, и поэтому требует отдельной реализации тестов.
Известно, что нет предела совершенству. Мы продолжаем развивать нашу систему тестов по всем направлениям. Естественно, мы постоянно увеличиваем базу всех тестов.
Из данной статьи вы узнали, как мы тестируем наш статический анализатор кода PVS-Studio. Возможно, наш опыт поможет вам внедрить подобные практики тестирования в своих рабочих проектах. А мы надеемся, что прочитав о том, как мы тестируем PVS-Studio, у вас возникнет желание подробнее познакомиться с нашим инструментом.
0