1.1. Методы обеспечения качества ПО

При разработке ПО на практике используются такие способы контроля качества продукта:

1. Наладка качественного процесса. Для комплексного улучшения процессов компании разработчиками ПО используются стандарты CMM/CMMI, а также стандарты серии ISO 9000 (с последующей официальной сертификацией). Также применяются локальные стратегии, менее дорогостоящие и более направленные на решение отдельных проблем (подход organization pull).

2. Формальные методы (математизированные подходы к разработке ПО) – использование математических формализмов для доказательства корректности, спецификации, проверки формального соответствия, автоматической генерации и т.д.:

  • доказательство правильности работы программ;
  • проверка на моделях определенных свойств (model cheking);
  • статический анализ кода по дереву разбора программы (например, проверка корректности кода по определенным критериям – аккуратная работа с памятью, поиск мертвого кода и пр.);
  • модельно-ориентированное тестирование (model-based testing): автоматическая генерация тестов и тестового окружения по формальным спецификациям требований к системе;
  • и т.д.

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

3. Исследование и анализ динамических свойств ПО.

  • профилирование – исследование использования системой памяти, ее быстродействия и др. характеристик путем запуска и непосредственных наблюдений в виде графиков, отчетов и пр.; используется при распараллеливании программ, при поиске "узких" мест;
  • моделирование и анализ производительности (performance modeling and analysis) –моделируется нагрузочное окружение системы (число одновременных пользователей системы, сетевой трафик и пр.) и наблюдается поведение системы.

4. Обеспечение качества кода. Сюда относится целый комплекс различных мероприятий и методов. Некоторые, самые известные из них:

  • Разработка стандартов оформления кода в проекте и контроль за соблюдением этих стандартов – включает правила на создание идентификаторов переменных, методов и имен классов, на оформление комментариев, правила использования стандартных библиотек и т.д.
  • Регулярный рефакторинг для предотвращения образования из кода "винегрета". При разработке кода имеет место тенденция ухудшения его структуры (избыточность, неиспользуемые или слабо используемые фрагменты, запутанная и труднопонимаемая структура) при внесении в него новой функциональности, исправления ошибок и пр. Рефакторинг – это регулярная деятельность по переписыванию кода с целью улучшения его структуры.
  • Инспекция кода, например, техника peer code review – заключается в том, что код каждого участника проекта читается и обсуждается на специальных встречах (code review meetings), и делается это регулярно. Практика показывает, что в целом код улучшается.
  • Вычитка кода – используется при разработке критических систем реального времени. Ею занимаются также разработчики, но их роль в данном проекте – вычитка, а не разработка.

5. Тестирование. Самый распространенный способ контроля качества ПО, представленный, фактически, в каждом программном проекте. Рассмотрим его более подробно.


1.2. Понятие тестирования

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

Ожидаемое поведение программы. Исходной информацией для тестирования являются требования к ней или к ее отдельной части, то есть знание о том, как система должна себя вести. Самым распространенным способом тестирования является метод черного ящика. При этом реализация системы недоступна тестеровщикам, и тестируется только ее интерфейс. Часто это закрепляется и организацией коллектива – тестеровщики являются отдельными сотрудниками и в некоторых компаниях они даже принципиально не общаются с разработчиками, чтобы минимально знать реализационных деталей и максимально полно выступить в роли проверяющей инстанции.

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

Рис. 1.1.


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

Данный подход закрепляется также и в организации коллективов компаний: тестеровщики, как правило, отделены от разработчиков. Однако и те и другие должны исходить из общего видения системы – ее требований. Есть прецедент одной американской компании где разработчики и тестеровщики сидели на разных этажах, ходили в разной одежде (тестеровщики в костюмах, разработчики – в свитерах) и начальство не поощряло нерабочие отношения между этими группами. Это, конечно же, крайность, но она еще раз подчеркивает, как важно, чтобы точка зрения на систему у тестеров отличалась от точки зрения разработчиков.

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


1.3. Виды тестов

Тесты могут быть ручными и автоматическими.

Ручной тест – это последовательность действий тестеровщика, которую он (или разработчик) может воспроизвести и ошибка произойдет. Как правило, в средствах контроля ошибок такие последовательности действий содержатся в описании ошибки.

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

  • запуск теста на системе,
  • "прогон" целого пакета тестов,
  • анализ полученных результатов и их обработка.

Важной задачей инструментов тестирования является обеспечение доступа теста к системе через некоторый ее интерфейс.

Преимущества автоматических тестов:

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

Недостатки автоматического тестирования:

  • необходимость использования инструментов тестирования, которые также являются неотъемлемой частью специально заданных, искусственных условий;
  • затруднение доступа инструментов тестирования к тестируемой программе, например, в силу политических обстоятельств, когда сторонними разработчиками делается подсистема некоторой стратегической системы, и доступ к этой объемлющей системе у разработчиков сильно ограничен, или в силу аппаратных ограничений.
  • для сложных программ возникают трудности с полнотой тестирования;
  • высокая стоимость и трудоемкость, как настройки и развертки готовых (сторонних), так и своих собственных тестовых инструментов;
  • проблема ресурсов для автоматического тестирования, особенно при автоматической генерации тестов: часто есть возможность автоматически сгенерировать очень большое количество тестов, так что если их еще выполнять регулярно, в режиме непрерывной интеграции, то не хватит имеющихся системных ресурсов.

1.4. Критерии тестирования

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

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

При тестировании на некоторых (не на всех возможных!) входных данных применяют принцип факторизации – множество всех возможных входных значений разбивают на значимые с точки зрения тестирования классы и "прогоняют" тесты не на всех возможных входных значениях, а берут по одному набору значений из каждого класса. Например, тестируют некоторую функцию системы на ее граничные значения – очень большие значения параметров, очень маленькие и пр. Часто факторизацию удобно делать, исходя из требований к данной функции, также бывает полезно посмотреть на ее реализацию и "пройтись" тестами по разным ее логическим веткам (порождаемым, например, условными операторами).


1.5.Виды тестирования

Выделяют следующие виды тестирования.

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

Интеграционное тестирование – два и более компонента тестируются на совместимость. Это очень важный вид тестирования, поскольку разные компоненты могут создаваться разными людьми, в разное время, на разных технологиях. Этот вид тестирования должен применяться самими программистами, чтобы, по крайней мере, удостовериться, что все компоненты программы совместимы в первом приближении. Далее тонкости интеграции могут исследовать тестеровщики.

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

Системное тестирование – это тестирование всей системы в целом, как правило, через ее пользовательский интерфейс. При этом тестеровщики, менеджеры и разработчики акцентируются на том, как ПО выглядит и работает в целом, удобно ли оно, удовлетворяет ли она ожиданиям заказчика. При этом могут открываться различные дефекты, такие как неудобство в использовании тех или иных функций, забытые или "скудно" понятые требования.

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

Нагрузочное тестирование – тестирование системы на корректную работу с большими объемами данных. Например, проверка баз данных на корректную обработку большого (предельного) объема записей, исследование поведение серверного ПО при большом количестве клиентских соединений, эксперименты с предельным трафиком для сетевых и телекоммуникационных систем, одновременное открытие большого числа файлов, проектов и т.д.

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

Приемочное тестирование – тестирование, выполняемое при приемке системы заказчиком. Более того, различные стандарты часто включают в себя наборы приемочных тестов. Например, существует большой пакет тестов, поддерживаемых компанией Sun Microsystems, которые обязательны для прогона для всех новых реализаций Java-машины. Считается, что только после того, как все эти тесты успешно проходят, новая реализация вправе называться Java.


1.6. Шаблоны тестовых проектов MS Visual Studio

Для разработчика программного обеспечения в Visual Studio 2012 и выше имеется возможность создавать модульные и нагрузочные тесты, а также тесты пользовательского интерфейса. В частности, имеются следующие шаблоны тестовых проектов [33]:

  • проект модульного теста, который позволяет создавать модульные тесты в процессе разработки;
  • проект с веб-тестами производительности и нагрузочными тестами;
  • проект с закодированными тестами пользовательского интерфейса.

Инструментарием тестировщика в Visual Studio 2012 и выше является Microsoft Test Manager (MTM). MTM предназначен для управления жизненным циклом тестирования программного обеспечения, включая планирование, тестирование и мониторинг. MTM интегрирован с TFS. С помощью Microsoft Test Manager тестировщики подготавливают планы тестирования, управляют тестированием, отслеживают и анализируют результаты. При создании плана тестирования в него добавляются наборы тестов, тестовые случаи и конфигурации, необходимые для тестирования. Microsoft Test Manager позволяет выполнять ручные и автоматические, а также исследовательские тесты. Результаты тестирования сохраняются в базе данных, что позволяет подготавливать различные аналитические отчеты. Ошибки, выявленные в процессе тестирования, фиксируются, документируются и передаются разработчикам для их устранения. При внесении изменений в код программной системы MTM автоматически формирует план регрессионного тестирования, выявляя какие тесты должны быть повторно выполнены.

Для тестировщиков и разработчиков программного обеспечения Visual Studio 2012 и выше включает диспетчер виртуальной среды Lab Management, позволяющий создать инфраструктуру, которая максимально близко эмулирует реальную среду планируемого использования программного продукта. Такие среды могут использоваться для выполнения автоматических построений, автоматизации тестов и выполнения разработанного программного кода.


1.7. Рефакторинг

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

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

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

Некачественный дизайн кода определяется по ряду признаков [28]:

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

Для создания качественного дизайна кода целесообразно применять некоторые принципы проектирования ПО [31]:

  1. Принцип единственной обязанности определяет, что у класса должна быть только одна причина для изменения. Из этого принципа следует, что классу целесообразно поручать только одну обязанность.
  2. Принцип открытости/закрытости определяет, что программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации.
  3. Принцип подстановки определяет, что должна быть возможность вместо базового класса подставить любой его подтип.
  4. Принцип инверсии зависимостей определяет два положения:
    • модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те и другие должны зависеть от абстракций;
    • абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
  5. Принцип разделения интерфейсов определяет, что клиенты должны знать только об абстрактных интерфейсах, обладающих свойством сцепленности.

1.8. Работа с ошибками

Между программистами и тестеровщиками необходим специальный интерфейс общения. Ведь ошибок находится много, их исправление требует времени. Тестеровщики должны удостовериться, что ошибки, которые они обнаружили, задокументировали и отправили разработчикам, действительно исправлены. Кроме того, менеджерам нужна статистика по найденным и исправленным ошибкам – это хороший инструмент контроля проекта. Все это изображено на рис. 1.2. Чтобы справиться с этим потоком информации и обеспечить необходимые в работе, удобные сервисы, существует специальный класс программных средств – средства контроля ошибок (bug tracking systems).

Рис. 1.2.


Описание ошибки в системе контроля ошибок имеет следующие основные атрибуты:

  • ответственного за ее проверку – тестеровщика, который ее нашел и который проверяет, что исправления, сделанные разработчиком, действительно устраняют ошибку;
  • ответственного за ее исправление – разработчика, которому ошибка отправляется на исправление;
  • состояние, например, ошибка найдена, ошибка исправлена, ошибка закрыта, ошибка вновь проявилась и т.д.

Этот список существенно дополняется в различных программных средствах контроля ошибок, но это основные атрибуты.

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

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

Очень важным при работе с ошибками оказываются различные отчеты, о чем будет подробно рассказано при изучении VSTS.