Для получения триального ключа
заполните форму ниже
Team License (базовая версия)
Enterprise License (расширенная версия)
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
GBP
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

** На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
Как статический анализ дополняет TDD

Как статический анализ дополняет TDD

12 Дек 2012

Одной из популярных техник разработки программного обеспечения является TDD. В целом мне нравится эта технология и отчасти мы её используем. Главное при её использовании не вдаваться в крайности. Не стоит полагаться только на неё и забывать другие методики повышения качества программ. В этой статье я хочу показать, как методика статического анализа кода может подстраховать программистов, использующих TDD.

TDD это замечательно

Разработка через тестирование (test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки. Сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам. Подробнее останавливать на том, что такое TDD я не буду. На эту тематику имеется большое количество статей, которые легко найти в интернете.

При использовании TDD мне кажется, очень важным не увлечься бесчисленным количеством тестов. Очень легко создавать видимость бурной деятельности и писать огромное количество строк кода в день. Но при этом функциональность продукта будет наращивать очень медленно. Можно почти все усилия и время тратить на код тестов. Вдобавок тесты иногда трудоёмко поддерживать при изменении функциональности.

Именно по этому, при разработке PVS-Studio мы не используем TDD в чистом виде. Если писать тесты для отдельных функций, то время разработки увеличится в десятки раз. Дело в следующем. Чтобы вызвать функцию раскрывающую тип в typedef или выполнить какой-то анализ кода, необходимо подготовить очень много входных данных. Нужно построить в памяти корректный фрагмент дерева разбора и заполнить множество структур. Это отнимет слишком много времени.

У нас есть другой способ. Наши TDD тесты представляют собой специально размеченные маленькие фрагменты кода на Си/Си++. Вначале мы пишем различные ситуации, где должны показываться какие-то предупреждения. Потом начинаем реализовывать код для их выявления. В грубой форме, тесты могут выглядеть так:

int A() {
  int x;
  return x; //Err
}

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

int B() {
  static int x;
  return x; //Ok
}

Здесь всё хорошо, так как переменная является статической.

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

Не везде TDD может быть применён в чистом виде. Например, как в нашем случае. Если вы хотите использовать эту методологию, но она вам не удобна, попробуйте взглянуть на неё с более высокого уровня абстракции. У нас, как нам кажется, это получилось.

TDD это замечательно, но не надо терять голову

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

Изучая исходные коды многих открытых приложений, я постоянно обращаю внимания на два недостатка при использовании юнит-тестов. У TDD конечно есть и другие недостатки, но о них я сейчас говорить не буду. По крайней мере, они не так сильно бросаются лично мне в глаза.

Итак, вот две типовые проблемы при создании тестов:

1) Сами тесты никто не тестирует.

2) Тесты не тестируют редкие критические ситуации.

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

Что с этим делать? Как минимум использовать дополнительный инструментарий для контроля качества кода. Это могут быть динамические или статические анализаторы кода. Конечно, они тоже не гарантируют выявление всех ошибок в тестах. Однако комплексное использование различных инструментов даёт очень хороший результат.

Например, запуская PVS-Studio для проверки очередного проекта, я очень часто встречаю ошибки в коде тестов. Приведу пример, взятый из проекта Chromium.

TEST(SharedMemoryTest, MultipleThreads) {
  ....
  int threadcounts[] = { 1, kNumThreads };
  for (size_t i = 0;
       i < sizeof(threadcounts) / sizeof(threadcounts); i++) {
  ....
}

Какой-то тест должен запускаться в одном потоке, а потом в нескольких. Из-за опечатки, не тестируется параллельная работа алгоритма. Ошибка вот здесь: sizeof(threadcounts) / sizeof(threadcounts).

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

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

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

bool Test()
{
  char *buf = new char[10];
  FooFoo(buf);
  bool ok = memcmp(buf, "1234567890", sizeof(buf)) == 0;
  delete [] buf;
  return ok;
}

Такой тест работает "наполовину". Он сравнивает только первые 4 или 8 байт. Количество сравниваемых байт зависит от размера указателя. Такой тест может казаться очень хорошим и работающим, но на самом деле доверять ему нельзя.

Другое слабое место TDD это отсутствие тестов для критических ситуаций. Конечно, такие тесты можно сделать. Но это неоправданно трудоемко. Например, сделать так, чтобы malloc() в нужный момент вернул NULL, требует много сил. А пользы от этого весьма мало. В программе вероятность такого события может быть меньше 0.0001%. Приходится находить компромисс между полнотой тестов и трудоёмкостью их создания.

Поиграем с числами. Пусть в программе функция malloc() используется 1000 раз. Пусть вероятность нехватки памяти при вызове каждой из них равна 0.0001%. Посчитаем, чему равна вероятность, что при работе программы возникнет ошибка выделения памяти:

(1 - 0.999999^1000) * 100% = 0.09995%

Приблизительно вероятность нехватки памяти составляет 0.1%. Не экономно писать 1000 тестов для тестирования таких ситуаций. Однако, 0.1% это не так уж и мало. У кого-то из пользователей точно будут возникать такие ситуации. Как быть уверенным, что они будут корректно обработаны?

Сложный вопрос. Написание юнит тестов слишком дорого. Инструменты динамического анализа плохо подходят по тем же причинам. Для них нужно создавать ситуацию, чтобы программе не хватало памяти в определённые моменты. Про ручное тестирование и говорить нечего.

Есть два пути. Можно использовать специальные инструменты, возвращающие код ошибки при вызове определённых системных функций. С такими системами я на практике не работал, поэтому не могу сказать насколько это просто, эффективно и надёжно.

Другим помощником может стать статический анализатор кода. Этому инструменту всё равно, как часто выполняется та или иная ветвь программы. Он проверяет почти весь код. Слово "почти" написано из-за того, что в программах на Си/Си++ есть "#ifdef" и явно отключенные ветки с помощью "if(0)" про содержимое которых лучше молчать.

Вот пример недочёта, который может обнаружить статический анализ в обработчиках ошибок:

VTK_THREAD_RETURN_TYPE vtkTestCondVarThread( void* arg )
{
  ....
  if ( td )                  // <=
  {
    ....
  }
  else
  {
    cout << "No thread data!\n";
    cout << "  Thread " << ( threadId + 1 ) 
         << " of " << threadCount << " exiting.\n";

    -- td->NumberOfWorkers;  // <=

    cout.flush();
  }
  ...
}

В случае возникновения ошибки выводится сообщение и модифицируется переменная "td->NumberOfWorkers". Этого делать нельзя, так как указатель 'td' равен нулю.

Выводы

Резюмирую содержание статьи:

1. TDD – замечательная технология. Стоит уделить время знакомству с ней и начать использовать в своей работе. Если классический вариант TDD неудобен, не стоит сразу отказываться от этой методологии. Возможно, её удастся использовать, взглянув на неё под другим углом или с другого уровня абстракции.

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

Популярные статьи по теме
PVS-Studio для Java

Дата: 17 Янв 2019

Автор: Андрей Карпов

В седьмой версии статического анализатора PVS-Studio мы добавили поддержку языка Java. Пришло время немного рассказать, как мы начинали делать поддержку языка Java, что у нас получилось и какие дальн…
Любите статический анализ кода!

Дата: 16 Окт 2017

Автор: Андрей Карпов

Я в шоке от возможностей статического анализа кода, хотя сам участвую в разработке инструмента PVS-Studio. На днях я был искренне удивлён тому, что анализатор оказался умнее и внимательнее меня.
Эффект последней строки

Дата: 31 Май 2014

Автор: Андрей Карпов

Я изучил множество ошибок, возникающих в результате копирования кода. И утверждаю, что чаще всего ошибки допускают в последнем фрагменте однотипного кода. Ранее я не встречал в книгах описания этого …
Бесплатный PVS-Studio для тех, кто развивает открытые проекты

Дата: 22 Дек 2018

Автор: Андрей Карпов

В канун празднования нового 2019 года команда PVS-Studio решила сделать приятный подарок всем контрибьюторам open-source проектов, хостящихся на GitHub, GitLab или Bitbucket. Им предоставляется возмо…
Статический анализ как часть процесса разработки Unreal Engine

Дата: 27 Июн 2017

Автор: Андрей Карпов

Проект Unreal Engine развивается - добавляется новый код и изменятся уже написанный. Неизбежное следствие развития проекта - появление в коде новых ошибок, которые желательно выявлять как можно раньш…
Характеристики анализатора PVS-Studio на примере EFL Core Libraries, 10-15% ложных срабатываний

Дата: 31 Июл 2017

Автор: Андрей Карпов

После большой статьи про проверку операционной системы Tizen мне было задано много вопросов о проценте ложных срабатываний и о плотности ошибок (сколько ошибок PVS-Studio выявляет на 1000 строк кода)…
Зло живёт в функциях сравнения

Дата: 19 Май 2017

Автор: Андрей Карпов

Возможно, читатели помнят мою статью под названием "Эффект последней строки". В ней идёт речь о замеченной мной закономерности: ошибка чаще всего допускается в последней строке однотипных блоков текс…
PVS-Studio ROI

Дата: 30 Янв 2019

Автор: Андрей Карпов

Время от времени нам задают вопрос, какую пользу в денежном эквиваленте получит компания от использования анализатора PVS-Studio. Мы решили оформить ответ в виде статьи и привести таблицы, которые по…
Технологии, используемые в анализаторе кода PVS-Studio для поиска ошибок и потенциальных уязвимостей

Дата: 21 Ноя 2018

Автор: Андрей Карпов

Краткое описание технологий, используемых в инструменте PVS-Studio, которые позволяют эффективно обнаруживать большое количество паттернов ошибок и потенциальных уязвимостей. Статья описывает реализа…
Как PVS-Studio оказался внимательнее, чем три с половиной программиста

Дата: 22 Окт 2018

Автор: Андрей Карпов

PVS-Studio, как и другие статические анализаторы кода, часто выдаёт ложные срабатывания. Но не стоит спешить считать странные срабатывания ложными. Это короткая история о том, как PVS-Studio вновь ок…

Комментарии (0)

Следующие комментарии

На сайте установлена reCAPTCHA и применяются
Политика конфиденциальности и Условия использования Google.
Этот сайт использует куки и другие технологии, чтобы предоставить вам более персонализированный опыт. Продолжая просмотр страниц нашего веб-сайта, вы принимаете условия использования этих файлов. Если вы не хотите, чтобы ваши данные обрабатывались, пожалуйста, покиньте данный сайт. Подробнее →
Принять