В программировании проверка исходного кода программы с целью обнаружения ошибок
Цель функционального тестирования - обнаружение несоответствий между реальным поведением реализованных функций и ожидаемым поведением в соответствии со спецификацией и исходными требованиями. Функциональные тесты должны охватывать все реализованные функции с учетом наиболее вероятных типов ошибок. Тестовые сценарии, объединяющие отдельные тесты, ориентированы на проверку качества решения функциональных задач.
Функциональные тесты создаются по внешним спецификациям функций, проектной информации и по тексту на ЯП, относятся к функциональным его характеристикам и применяются на этапе комплексного тестирования и испытаний для определения полноты реализации функциональных задач и их соответствия исходным требованиям.
В задачи функционального тестирования входят:
- идентификация множества функциональных требований;
- идентификация внешних функций и построение последовательностей функций в соответствии с их использованием в ПС;- идентификация множества входных данных каждой функции и определение областей их изменения;
- построение тестовых наборов и сценариев тестирования функций;
- выявление и представление всех функциональных требований с помощью тестовых наборов и проведение тестирования ошибок в программе и при взаимодействии со средой.
Тесты, создаваемые по проектной информации, связаны со структурами данных, алгоритмами, интерфейсами между отдельными компонентами и применяются для тестирования компонентов и их интерфейсов. Основная цель - обеспечение полноты и согласованности реализованных функций и интерфейсов между ними.
Комбинированный метод "черного ящика" и "прозрачного ящика" основан на разбиении входной области функции на подобласти обнаружения ошибок. Подобласть содержит однородные элементы, которые все обрабатываются корректно либо некорректно. Для тестирования подобласти производится выполнение программы на одном из элементов этой области.
Предпосылки функционального тестирования :
- корректное оформление требований и ограничений к качеству ПО;
- корректное описание модели функционирования ПО в среде эксплуатации у заказчика;
- адекватность модели ПО заданному классу.
7.3. Инфраструктура процесса тестирования ПС
Под инфраструктурой процесса тестирования понимается:
- выделение объектов тестирования;
- проведение классификации ошибок для рассматриваемого класса тестируемых программ;
- подготовка тестов, их выполнение и поиск разного рода ошибок и отказов в компонентах и в системе в целом;
- служба проведения и управление процессом тестирования;
- анализ результатов тестирования.
Объекты тестирования - компоненты, группы компонентов, подсистемы и система. Для каждого из них формируется стратегия проведения тестирования. Если объект тестирования относится к "белому ящику" или "черному ящику", состав компонентов которого неизвестный, то тестирование проводится посредством ввода внего входных тестовых данных для получения выходных данных. Стратегическая цель тестирования состоит в том, чтобы убедиться, что каждый рассматриваемый входной набор данных соответствует ожидаемым выходным выходных данным. При таком подходе к тестированию не требуется знания внутренней структуры и логики объекта тестирования.
Проектировщик тестов должен заглянуть внутрь "черного ящика" и исследовать детали процессов обработки данных, вопросы обеспечения защиты и восстановления данных, а также интерфейсы с другими программами и системами. Это способствует подготовке тестовых данных для проведения тестирования.
Для некоторых типов объектов группа тестирования не может сгенерировать представительное множество тестовых наборов, которые демонстрировали бы функциональную правильность работы компоненты при всех возможных наборах тестов.
Поэтому предпочтительным является метод "белого ящика", при котором можно использовать структуру объекта для организации тестирования по различным ветвям. Например, можно выполнить тестовые наборы, которые проходят через все операторы или все контрольные точки компонента для того, чтобы убедиться в правильности их работы.
7.3.1. Методы поиска ошибок в программах
Международный стандарт ANSI/IEEE-729-83 разделяет все ошибки в разработке программ на следующие типы.
Ошибка (error) - состояние программы, при котором выдаются неправильные результаты, причиной которых являются изъяны ( flaw ) в операторах программы или в технологическом процессе ее разработки, что приводит к неправильной интерпретации исходной информации, следовательно, и к неверному решению.
Дефект (fault) в программе - следствие ошибок разработчика на любом из этапов разработки, которая может содержаться в исходных или проектных спецификациях, текстах кодов программ, эксплуатационной документация и т.п. В процессе выполнения программы может быть обнаружен дефект или сбой.
Отказ (failure) - это отклонение программы от функционирования или невозможность программы выполнять функции, определенные требованиями и ограничениями, что рассматривается как событие, способствующее переходу программы в неработоспособное состояние из-за ошибок, скрытых в ней дефектов или сбоев в среде функционирования [7.6, 7.11]. Отказ может быть результатом следующих причин:
- ошибочная спецификация или пропущенное требование, означающее, что спецификация точно не отражает того, что предполагал пользователь;
- спецификация может содержать требование, которое невозможно выполнить на данной аппаратуре и программном обеспечении;
- проект программы может содержать ошибки (например, база данных спроектирована без средств защиты от несанкционированного доступа пользователя, а требуется защита);
- программа может быть неправильной, т.е. она выполняет несвойственный алгоритм или он реализован не полностью.
Таким образом, отказы, как правило, являются результатами одной или более ошибок в программе, а также наличия разного рода дефектов.
Ошибки на этапах процесса тестирования.Приведенные типы ошибок распределяются по этапам ЖЦ и им соответствуют такие источники их возникновения [7.12]:
- непреднамеренное отклонение разработчиков от рабочих стандартов или планов реализации;
- спецификации функциональных и интерфейсных требований выполнены без соблюдения стандартов разработки, что приводит к нарушению функционирования программ;
- организации процесса разработки - несовершенная или недостаточное управление руководителем проекта ресурсами (человеческими, техническими, программными и т.д.) и вопросами тестирования и интеграции элементов проекта.
Рассмотрим процесс тестирования, исходя из рекомендаций стандарта ISO/IEC 12207, и приведем типы ошибок, которые обнаруживаются на каждом процессе ЖЦ.
Процесс разработки требований. При определении исходной концепции системы и исходных требований к системе возникают ошибки аналитиков при спецификации верхнего уровня системы и построении концептуальной модели предметной области.
Характерными ошибками этого процесса являются:
- неадекватность спецификации требований конечным пользователям;- некорректность спецификации взаимодействия ПО со средой функционирования или с пользователями;
- несоответствие требований заказчика к отдельным и общим свойствам ПО;
- некорректность описания функциональных характеристик;
- необеспеченность инструментальными средствами всех аспектов реализации требований заказчика и др.
Процесс проектирования.Ошибки при проектировании компонентов могут возникать при описании алгоритмов, логики управления, структур данных, интерфейсов, логики моделирования потоков данных, форматов ввода-вывода и др. В основе этих ошибок лежат дефекты спецификаций аналитиков и недоработки проектировщиков. К ним относятся ошибки, связанные:
- с определением интерфейса пользователя со средой;
- с описанием функций (неадекватность целей и задач компонентов, которые обнаруживаются при проверке комплекса компонентов);
- с определением процесса обработки информации и взаимодействия между процессами (результат некорректного определения взаимосвязей компонентов и процессов);
- с некорректным заданием данных и их структур при описании отдельных компонентов и ПС в целом;
- с некорректным описанием алгоритмов модулей;
- с определением условий возникновения возможных ошибок в программе;
- с нарушением принятых для проекта стандартов и технологий.
Этап кодирования.На данном этапе возникают ошибки, которые являются результатом дефектов проектирования, ошибок программистов и менеджеров в процессе разработки и отладки системы. Причиной ошибок являются:
- бесконтрольность значений входных параметров, индексов массивов, параметров циклов, выходных результатов, деления на 0 и др.;
- неправильная обработка нерегулярных ситуаций при анализе кодов возврата от вызываемых подпрограмм, функций и др.;
- нарушение стандартов кодирования (плохие комментарии, нерациональное выделение модулей и компонент и др.);
- использование одного имени для обозначения разных объектов или разных имен одного объекта, плохая мнемоника имен;- несогласованное внесение изменений в программу разными разработчиками и др.
Процесс тестирования.На этом процессе ошибки допускаются программистами и тестировщиками при выполнении технологии сборки и тестирования, выбора тестовых наборов и сценариев тестирования и др. Отказы в программном обеспечении, вызванные такого рода ошибками, должны выявляться, устраняться и не отражаться на статистике ошибок компонент и программного обеспечения в целом.
Процесс сопровождения.На процессе сопровождения обнаруживаются ошибки, причиной которых являются недоработки и дефекты эксплуатационной документации, недостаточные показатели модифицируемости и удобочитаемости, а также некомпетентность лиц, ответственных за сопровождение и/или усовершенствование ПО. В зависимости от сущности вносимых изменений на этом этапе могут возникать практически любые ошибки, аналогичные ранее перечисленным ошибкам на предыдущих этапах.
Все ошибки, которые возникают в программах, принято подразделять на следующие классы [7.12]:
- логические и функциональные ошибки;
- ошибки вычислений и времени выполнения;
- ошибки вводавывода и манипулирования данными;
- ошибки интерфейсов;
- ошибки объема данных и др.
Логические ошибки являются причиной нарушения логики алгоритма, внутренней несогласованности переменных и операторов, а также правил программирования. Функциональные ошибки - следствие неправильно определенных функций, нарушения порядка их применения или отсутствия полноты их реализации и т.д.
Ошибки вычислений возникают по причине неточности исходных данных и реализованных формул, погрешностей методов, неправильного применения операций вычислений или операндов. Ошибки времени выполнения связаны с необеспечением требуемой скорости обработки запросов или времени восстановления программы.
Ошибки ввода-вывода и манипулирования данными являются следствием некачественной подготовки данных для выполнения программы, сбоев при занесении их в базы данных или при выборке из нее.
Ошибки интерфейса относятся к ошибкам взаимосвязи отдельных элементов друг с другом, что проявляется при передаче данных между ними, а также при взаимодействии со средой функционирования.
Ошибки объема относятся к данным и являются следствием того, что реализованные методы доступа и размеры баз данных не удовлетворяют реальным объемам информации системы или интенсивности их обработки.
Приведенные основные классы ошибок свойственны разным типам компонентов ПО и проявляются они в программах по разному. Так, при работе с БД возникают ошибки представления и манипулирования данными, логические ошибки в задании прикладных процедур обработки данных и др. В программах вычислительного характера преобладают ошибки вычислений, а в программах управления и обработки - логические и функциональные ошибки. В ПО, которое состоит из множества разноплановых программ, реализующих разные функции, могут содержаться ошибки разных типов. Ошибки интерфейсов и нарушение объема характерны для любого типа систем.
Анализ типов ошибок в программах является необходимым условием создания планов тестирования и методов тестирования для обеспечения правильности ПО.
На современном этапе развития средств поддержки разработки ПО ( CASE-технологии , объектно-ориентированные методы и средства проектирования моделей и программ) проводится такое проектирование, при котором ПО защищается от наиболее типичных ошибок и тем самым предотвращается появление программных дефектов.
Связь ошибки с отказом.Наличие ошибки в программе, как правило, приводит к отказу ПО при его функционировании. Для анализа причинно-следственных связей "ошибкаотказ" выполняются следующие действия:
- идентификация изъянов в технологиях проектирования и программирования;
- взаимосвязь изъянов процесса проектирования и допускаемых человеком ошибок;
- классификация отказов, изъянов и возможных ошибок, а также дефектов на каждом этапе разработки;- сопоставление ошибок человека, допускаемых на определенном процессе разработки, и дефектов в объекте, как следствий ошибок спецификации проекта, моделей программ;
- проверка и защита от ошибок на всех этапах ЖЦ, а также обнаружение дефектов на каждом этапе разработки;
- сопоставление дефектов и отказов в ПО для разработки системы взаимосвязей и методики локализации, сбора и анализа информации об отказах и дефектах;
- разработка подходов к процессам документирования и испытания ПО.
Конечная цель причинно-следственных связей "ошибкаотказ" заключается в определении методов и средств тестирования и обнаружения ошибок определенных классов, а также критериев завершения тестирования на множестве наборов данных; в определении путей совершенствования организации процесса разработки, тестирования и сопровождения ПО.
Приведем следующую классификацию типов отказов:
- аппаратный, при котором общесистемное ПО не работоспособно;
- информационный, вызванный ошибками во входных данных и передаче данных по каналам связи, а также при сбое устройств ввода (следствие аппаратных отказов);
- эргономический, вызванный ошибками оператора при его взаимодействии с машиной (этот отказ - вторичный отказ, может привести к информационному или функциональному отказам);
- программный, при наличии ошибок в компонентах и др.
Некоторые ошибки могут быть следствием недоработок при определении требований, проекта, генерации выходного кода или документации. С другой стороны, они порождаются в процессе разработки программы или при разработке интерфейсов отдельных элементов программы (нарушение порядка параметров, меньше или больше параметров и т.п.).
Источники ошибок.Ошибки могут быть порождены в процессе разработки проекта, компонентов, кода и документации. Как правило, они обнаруживаются при выполнении или сопровождении программного обеспечения в самых неожиданных и разных ее точках.
Некоторые ошибки в программе могут быть следствием недоработок при определении требований, проекта, генерации кода или документации. С другой стороны, ошибки порождаются в процессе разработки программы или интерфейсов ее элементов (например, при нарушении порядка задания параметров связи - меньше или больше, чем требуется и т.п.).
Причиной появления ошибок - непонимание требований заказчика; неточная спецификация требований в документах проекта и др. Это приводит к тому, что реализуются некоторые функции системы, которые будут работать не так, как предлагает заказчик. В связи с этим проводится совместное обсуждение заказчиком и разработчиком некоторых деталей требований для их уточнения.
Команда разработчиков системы может также изменить синтаксис и семантику описания системы. Однако некоторые ошибки могут быть не обнаружены (например, неправильно заданы индексы или значения переменных этих операторов).
Тестирование и отладка – два противоположных, но взаимосвязанных процесса, выполняются многократно. В этой статье дается только общее представление об этих процессах (скорее на примере структурного программирования). Понятно, что требования к тестированию игр или управлению воздушными полетами принципиально разные.
Основные термины
Тестирование (testing) – процесс выполнения программы с целью найти ошибки. Может выполняться как с компьютером, так и без него (общий термин).
Доказательство (proof) – попытка найти в программе ошибки путем доказательств на основе математических теорем о правильности программы безотносительно к внешней программной среде (вид тестирования).
Контроль (verification) – попытка найти ошибки, выполняя программу в тестовой или моделируемой среде (вид тестирования).
Испытание (validation) – попытка найти ошибки, выполняя программу в заданной программной среде (вид тестирования).
Аттестация (certification) – авторитетное подтверждение правильности программы (итоговое тестирование, для критичного ПО).
Отладка (debugging) – средство установления точной природы ошибок, процесс, противоположный тестированию, ведет к устранению ошибок.
Виды тестирования
Автономное тестирование, тестирование модуля (module testing) – контроль отдельного модуля в изолированной среде (например, с помощью ведущей программы), инспекция текста модуля на сессии программистов, которая иногда дополняется математическим доказательством правильности модуля.
Тестирование сопряжений (integration testing) – контроль сопряжений между частями системы, как между компонентами в комплексе, так и между модулями отдельного компонента (например, у заглушки).
Комплексное тестирование (system testing) – контроль и/или испытание системы по отношению к исходным целям. Является процессом контроля, если оно выполняется в моделируемой среде, и процессом испытания в реальной среде.
ПРИНЦИПЫ (аксиомы) тестирования
Мои примечания к аксиомам тестирования:
- Не гордитесь тестом, который не обнаружил ни одной ошибки.
- При планировании разработки выделяйте не менее 50% времени на тестирование и отладку, тогда у вас появится шанс закончить работу в срок.
- Другой человек не будет испытывать уважения к вашим способностям писать правильный код. Поэтому, когда нет возможности передать тестирование другому разработчику, следует отстраниться от своей разработки и отнестись к ней как чужой (такой психологический настрой).
- Если вы не описали заранее ожидаемые результаты, то даже неумышленно можно истолковать фактические результаты как верные.
- Не воспроизводимые тесты – тесты, которые нельзя повторить нужное количество раз.
- Обязательно в наборе тестов должны быть входные данные, которые могут не соответствовать смыслу задачи (смотри пример с формулой Герона), либо формату вводимых данных.
- Тестирование «с лету» подразумевает, что тесты не следует придумывать после написания кода, это следует делать заранее.
- Группируйте ошибки тестирования, постарайтесь понять их природу.
- Рекомендация относится к коллективной разработке, когда менее опытный программист делает много ошибок в своем коде программы.
- Тестирование – творческий процесс, автор тестов должен понимать технологию тестирования и иметь собственный опыт программирования.
- Например, методология структурного программирования обеспечивает обозримость ПО за счет деления его на модули и стандартизации их взаимодействия. Полезными свойствами программного кода является осмысленный выбор идентификаторов объектов наличие комментариев.
- Вы можете изменить программу в процессе отладки. Важно – не забыть восстановить полезный код.
- Для каждого процесса тестирования начинайте с определения цели.
Связь процесса тестирования с процессом проектирования ПО
В процессе проектирования ПО обычно принимаются следующие решения:
- Требования к ПО (область применения, назначение, условия эксплуатации);
- Цели создания ПО (решение определенных задач);
- Внешние спецификации «что должно делать ПО» (функции и требования к ним)
- Архитектура системы;
- Структура программы (разбиение на программные модули);
- Внешние спецификации модуля (функции модуля);
- Логика модуля (алгоритмы);
Как вы видите, процесс непосредственного кодирования не включен в проектирование.
Процессы тестирования (в скобках указаны номера связанных с ними процессов проектирования):
1. Автономный тест (6, 7)
2. Тест сопряжений (4, 5)
3. Тест функций (3)
4. Комплексный тест (2)
5. Тест приемлемости (1)
Тестирование ПО включает:
1) постановку задачи;
2) проектирование тестов;
3) написание тестов;
4) тестирование тестов;
5) выполнение тестов;
6) изучение результатов тестирования.
Два крайних подхода к проектированию тестов:
- Каждая команда – хотя бы один раз;
Каждый условный переход – в каждом направлении хотя бы раз;
Цикл – ни разу, один раз, максимальное число раз.
Две стратегии тестирования: восходящее и нисходящее
Восходящее тестирование – от отдельных модулей по мере их готовности (в том числе отдельных функций) к тестированию форм и ПО в целом.
Нисходящее тестирование начинается с тестирования структуры ПО и с последующим тестированием модулей.
Реально используются комбинации обеих стратегий в зависимости от сложности ПО.
Критерии выбора стратегии тестирования
Время до полной сборки программы
Время реализации скелета программы
Имеющийся инструментарий проектирования
Мера параллелизма ранних этапов реализации
Возможность проверки в реальных условиях эксплуатации
Отладка
После цикла тестирования начинается цикл отладки программы.
Самое сложное – понять по результатам тестирования причину ошибки. Ошибка в модуле (например, в методе обработки некоторого события) может быть связана как с логикой обработки данных, так и с ошибкой сопряжений (при передаче параметров). Ошибки вызова форм приложения могут быть связаны с неверно выбранным способом их взаимодействия.
Далее выдвигается гипотеза, которая проверяется после изменения программного кода.
Важно, что после этого следует снова выполнить (полное или частичное) тестирование, так как любое изменение кода может внести новые ошибки.
Тестирование – процесс выполнения программы с целью обнаружения ошибок путем реального выполнения специально подобранных контрольных примеров. Шаги процесса задаются тестами. Каждый тест определяет:
• свой набор исходных данных и условий для запуска программы;
• набор ожидаемых результатов работы программы.
Другое название теста – тестовый вариант. Полную проверку программы гарантирует исчерпывающее тестирование. Оно требует проверить все наборы исходных данных, все варианты их обработки и включает большое количество тестовых вариантов. Увы, но исчерпывающее тестирование во многих случаях остается только мечтой – срабатывают ресурсные ограничения (прежде всего, ограничения времени).
Хорошим считают тестовый вариант с высокой вероятностью обнаружения еще не раскрытой ошибки. Успешным называют тест, который обнаруживает до сих пор не раскрытую ошибку.
• демонстрацию соответствия функций программы ее назначению;
• демонстрацию реализации требований к характеристикам программы;
• отображение надежности как индикатора качества программы.
Тестирование не может показать отсутствия дефектов (оно может показывать только присутствие дефектов).
Рассмотрим информационные потоки процесса тестирования. Они показаны на рис. 1.7.
Рис. 1.7. Информационные потоки процесса тестирования
На входе процесса тестирования три потока:
• исходные данные для запуска программы;
Выполняются тесты, все полученные результаты оцениваются. Это значит, что реальные результаты тестов сравниваются с ожидаемыми результатами. Когда обнаруживается несовпадение, фиксируется ошибка – начинается отладка. Процесс отладки непредсказуем по времени. Неопределенность в отладке приводит к большим трудностям в планировании действий.
После сбора и оценивания результатов тестирования начинается отображение качества и надежности программного обеспечения. Если регулярно встречаются серьезные ошибки, требующие проектных изменений, то качество и надежность программного обеспечения подозрительны, констатируется необходимость усиления тестирования. С другой стороны, если функции программного обеспечения реализованы правильно, а обнаруженные ошибки легко исправляются, может быть сделан один из двух выводов:
• качество и надежность программного обеспечения удовлетворительны;
• тесты не способны обнаруживать серьезные ошибки.
В конечном счете, если тесты не обнаруживают ошибок, то обнаруживаются пользователями и корректируются разработчиком на этапе сопровождения. Результаты, накопленные в ходе тестирования, могут оцениваться и более формальным способом. Для этого используют модели надежности программного обеспечения, выполняющие прогноз надежности по реальным данным об интенсивности ошибок. Существуют два принципа тестирования программы:
• функциональное тестирование (тестирование «черного ящика»);
• структурное тестирование (тестирование «белого ящика»).
Тестирование «черного ящика»
Как показано на рис. 1.8, основное место приложения тестов «черного ящика» – интерфейс.
Рис. 1.8. Тестирование «черного ящика»
Эти тесты демонстрируют:
• как выполняются функции программ;
• как принимаются исходные данные;
• как вырабатываются результаты;
• как сохраняется целостность внешней информации.
При тестировании «черного ящика» рассматриваются системные характеристики программ, игнорируется их внутренняя логическая структура. Исчерпывающее тестирование, как правило, невозможно. Например, если в программе 10 входных величин и каждая принимает по 10 значений, то потребуется 1010 тестовых вариантов. Отметим также, что тестирование «черного ящика» не реагирует на многие особенности программных ошибок.
Тестирование «белого ящика»
Объектом тестирования здесь является не внешнее, а внутреннее поведение программы. Проверяется корректность построения всех элементов программы и правильность их взаимодействия друг с другом. Обычно анализируются управляющие связи элементов, реже – информационные связи. Тестирование по принципу «белого ящика» характеризуется степенью, в какой тесты выполняют или покрывают логику (исходный текст) программы. Исчерпывающее тестирование также затруднительно. Особенности этого принципа тестирования рассмотрим отдельно.
Обычно тестирование «белого ящика» (рис. 1.9) основано на анализе управляющей структуры программы.
Рис. 1.9. Тестирование «белого ящика»
Программа считается полностью проверенной, если проведено исчерпывающее тестирование маршрутов (путей) ее графа управления.
В этом случае формируются тестовые варианты, в которых:
• гарантируется проверка всех независимых маршрутов программы;
• находятся ветви True, False для всех логических решений;
• выполняются все циклы (в пределах их границ и диапазонов);
• анализируется правильность внутренних структур данных.
Недостатки тестирования «белого ящика»:
• количество независимых маршрутов может быть очень велико;
• исчерпывающее тестирование маршрутов не гарантирует соответствия программы исходным требованиям к ней;
• в программе могут быть пропущены некоторые маршруты;
• нельзя обнаружить ошибки, появление которых зависит от обрабатываемых данных (это ошибки, обусловленные выражениями типа if abs (a-b) < eps. if (a+b+c)/3«a. ).
Достоинстватестирования «белого ящика» связаны с тем, что принцип «белого ящика» позволяет учесть особенности программных ошибок:
1. Количество ошибок минимально в «центре» и максимально на «периферии» программы.
2. Предварительные предположения о вероятности потока управления или данных в программе часто бывают некорректны. В результате типовым может стать маршрут, модель вычислений по которому проработана слабо.
3. При записи алгоритма программы в виде текста на языке программирования возможно внесение новых ошибок трансляции (синтаксических и семантических).
4. Некоторые результаты в программе зависят не от исходных данных, а от внутренних состояний программы.
Каждая из этих причин является аргументом для проведения тестирования по принципу «белого ящика». Тесты «черного ящика» не смогут реагировать на ошибки таких типов.
Способ тестирования базового пути
Тестирование базового пути – это способ, который основан на принципе «белого ящика».
Способ тестирования базового пути дает возможность:
• получить оценку комплексной сложности программы;
• использовать эту оценку для определения необходимого количества тестовых вариантов.
Тестовые варианты разрабатываются для проверки базового множества путей (маршрутов) в программе. Они гарантируют однократное выполнение каждого оператора программы при тестировании.
Способы тестирования условий
Цель этого семейства способов тестирования – строить тестовые варианты для проверки логических условий программы. При этом желательно обеспечить охват операторов из всех ветвей программы.
Рассмотрим используемую здесь терминологию.
Простое условие – булева переменная или выражение отношения.
Выражение отношения имеет вид:
Е1 <оператор отношения> Е2,
где El, Е2 – арифметические выражения, а в качестве оператора отношения используется один из следующих операторов: <, >, =, ¹, <, >.
Составное условие состоит из нескольких простых условий, булевых операторов и круглых скобок. Будем применять булевы операторы OR, AND (&), NOT. Условия, не содержащие выражений отношения, называют булевыми выражениями.
Таким образом, элементами условия являются: булев оператор, булева переменная, пара скобок (заключающая простое или составное условие), оператор отношения, арифметическое выражение. Эти элементы определяют типы ошибок в условиях.
Если условие некорректно, то некорректен по меньшей мере один из элементов условия. Следовательно, в условии возможны следующие типы ошибок:
• ошибка булева оператора (наличие некорректных/ отсутствующих/ избыточных булевых операторов);
• ошибка булевой переменной;
• ошибка булевой скобки;
• ошибка оператора отношения;
• ошибка арифметического выражения.
Способ тестирования условий ориентирован на тестирование каждого условия в программе. Методики тестирования условий имеют два достоинства. Во-первых, достаточно просто выполнить измерение тестового покрытия условия. Во-вторых, тестовое покрытие условий в программе – это фундамент для генерации дополнительных тестов программы.
Целью тестирования условий является определение не только ошибок в условиях, но и других ошибок в программах. Если набор тестов для программы А эффективен для обнаружения ошибок в условиях, содержащихся в А, то вероятно, что этот набор также эффективен для обнаружения других ошибок в А. Кроме того, если методика тестирования эффективна для обнаружения ошибок в условии, то вероятно, что эта методика будет эффективна для обнаружения ошибок в программе.
Существует несколько методик тестирования условий.
Простейшая методика – тестирование ветвей. Здесь для составного условия С проверяется:
• каждое простое условие (входящее в него);
Другая методика – тестирование области определения. В ней для выражения отношения требуется генерация 3-4 тестов. Выражение вида
Е1 <оператор отношения> Е2
проверяется тремя тестами, которые формируют значение Е1 большим, чем Е2, равным Е2 и меньшим, чем Е2.
Если оператор отношения неправилен, а Е1 и Е2 корректны, то эти три теста гарантируют обнаружение ошибки оператора отношения.
Для определения ошибок в Е1 и Е2 тест должен сформировать значение Е1 большим или меньшим, чем Е2, причем обеспечить как можно меньшую разницу между этими значениями.
Для булевых выражений с п переменными требуется набор из 2n тестов. Этот набор позволяет обнаружить ошибки булевых операторов, переменных и скобок, но практичен только при малом п. Впрочем, если в булево выражение каждая булева переменная входит только один раз, то количество тестов легко уменьшается.
Тестирование ветвей и операторов отношений
Способ тестирования ветвей и операторов отношений обнаруживает ошибки ветвления и операторов отношения в условии, для которого выполняются следующие ограничения:
• все булевы переменные и операторы отношения входят в условие только по одному разу;
• в условии нет общих переменных.
В данном способе используются естественные ограничения условий (ограничения на результат). Для составного условия С, включающего п простых условий, формируется ограничение условия:
где di – ограничение на результат i-го простого условия.
Рассмотрим шаги способа тестирования ветвей и операторов отношений.
Для каждого условия в программе выполняются следующие действия:
• строится ограничение условий;
• выявляются ограничения результата по каждому простому условию;
• строится ограничивающее множество. Построение выполняется путем подстановки в константные формулы ОМ& или ОМor выявленных ограничений результата;
• для каждого элемента разрабатывается тестовый вариант.
Способ тестирования потоков данных
В предыдущих способах тесты строились на основе анализа управляющей структуры программы. В данном способе анализу подвергается информационная структура программы.
Работу любой программы можно рассматривать как обработку потока данных, передаваемых от входа в программу к ее выходу.
Под определением данных понимают действия, изменяющие элемент данных. Признак определения – имя элемента стоит в левой части оператора присваивания:
Использование данных – это применение элемента в выражении, где происходит обращение к элементу данных, но не изменение элемента. Признак использования – имя элемента стоит в правой части оператора присваивания:
Здесь место подстановки другого имени отмечено прямоугольником (прямоугольник играет роль метки-заполнителя).
Тестирование циклов
Цикл – наиболее распространенная конструкция алгоритмов, реализуемых в программном обеспечении. Тестирование циклов производится по принципу «белого ящика», при проверке циклов основное внимание обращается на правильность конструкций циклов.
Различают 4 типа циклов:
Простые циклы
Для проверки простых циклов (рис. 1.10) с количеством повторений п может использоваться один из следующих наборов тестов:
• прогон всего цикла;
• только один проход цикла;
• два прохода цикла;
• т проходов цикла, где т < п;
• п - 1, п, п + 1 проходов цикла.
Рис. 1.10. Простые циклы
Вложенные циклы
С увеличением уровня вложенности циклов количество возможных путей резко возрастает. Это приводит к нереализуемому количеству тестов. Для сокращения количества тестов применяется специальная методика, в которой используются такие понятия, как объемлющий и вложенный циклы (рис. 1.11).
Рис. 1.11. Вложенные циклы
• Выбирается самый внутренний цикл. Устанавливаются минимальные значения параметров всех остальных циклов.
• Для внутреннего цикла проводятся тесты простого цикла. Добавляются тесты для исключенных значений и значений, выходящих за пределы рабочего диапазона.
• Переходят в следующий по порядку объемлющий цикл. Выполняют его тестирование. При этом сохраняются минимальные значения параметров для всех внешних (объемлющих) циклов и типовые значения для всех вложенных циклов.
Работа продолжается до тех пор, пока не будут протестированы все циклы.
Объединенные циклы
Если каждый из циклов независим от других, то используется техника тестирования простых циклов. При наличии зависимости (например, конечное значение счетчика первого цикла используется как начальное значение счетчика второго цикла) используется методика для вложенных циклов (рис. 1.12).
Рис. 1.12. Объединенные циклы
Неструктурированные циклы
Неструктурированные циклы (рис. 1.13) тестированию не подлежат. Этот тип циклов должен быть переделан с помощью структурированных программных конструкций.
Несколькими тысячами строк кода позже, тот же самый проект может оказаться отягощенным ошибками, из-за которых добавление новых функций становится головной болью, и падает энтузиазм программистов. Лучшие разработчики знают, как найти и устранить ошибки, и придерживаются лучших практик в разработке программного обеспечения, чтобы свести к минимуму, в первую очередь, возникновение ошибок.
Ни один программист никогда не напишет абсолютно верный код, но с некоторой практикой и решимостью вполне возможно писать чистый код , сдерживать ошибки и разрабатывать надежные программные системы.
Ваш набор инструментов для борьбы с ошибками
1. Оператор печати
Инструмент номер один для отладки кода – это опробованный и верный способ вставки операторов печати. В качестве равнозначной замены, для случаев, когда операторов печати много, и ими трудно управлять, может быть использована система протоколирования вместо операторов печати. Во многих языках программирования для этого есть в свободном доступе специальные библиотеки, как, например, библиотека logging , встроенная в Python.
Операторы печати – это самый быстрый, простой и непосредственный для программиста способ инспектирования значений данных и типов переменных. Правильно размещенные операторы печати позволяют программисту отслеживать поток данных на участке кода и быстро определять источник ошибки.
Не имеет значения, сколько передовых технологий используется, скромный оператор печати должен быть первым инструментом, к которому обращается программист, когда пытается отладить участок кода.
2. Отладчик
Отладчики исходного кода доводят метод отладки с помощью операторов печати до его логического завершения. Они позволяют программисту отследить по шагам выполнение кода строка за строкой и инспектировать все, что угодно, начиная от значений переменных и заканчивая состоянием виртуальной машины.
Большинство языков программирования имеют множество доступных отладчиков, которые предлагают различные возможности, включая графические интерфейсы, настройки точек останова для приостановки выполнения программы, и выполнение произвольного кода внутри среды исполнения.
Применение отладчика может быть излишним во многих ситуациях, но при надлежащем использовании, отладчик может стать мощным и эффективным инструментом. Для лучшего понимания возможностей отладчика, познакомьтесь с отладчиком Python pdb .
3. Система отслеживания ошибок
Когда такое случается, некоторые ошибки неизбежно остаются неисправленными, и что более важно, их труднее обнаружить и устранить другие, связанные с ними ошибки.
Простой текстовый файл может служить начальной системой отслеживания ошибок для проекта. С ростом объема кода количество ошибок выйдет за рамки текстового файла.
Существует большой выбор систем отслеживания ошибок в программном обеспечении, как коммерческих, так и с открытым исходным кодом. Самым важным критерием в выборе такой системы является доступность для сотрудников-непрограммистов, которым нужно работать с файлом ошибок.
4. Верификация программ
В некоторых языках программирования верификатор может проводить статический анализ кода для обнаружения проблемных мест до того, как код будет откомпилирован или выполнен, а в других языках верификатор полезен для проверки синтаксиса и стиля написания.
Исполнение программы верификации внутри редактора во время написания кода или прогон кода через верификатор до компиляции и выполнения помогает программистам находить и исправлять неисправности до того, как они переросли в ошибки в исполняемом программном обеспечении.
Использование верификации позволяет значительно сэкономить время по отслеживанию источника неисправности, вызванных синтаксическими ошибками, опечатками, и некорректными типами данных. Чтобы получить более полное представление о возможностях верификатора, посмотрите Pyflakes , верификатор для Python.
5. Контроль версий
Также как и использование системы отслеживания ошибок, применение системы контроля версии – это самая лучшая практика в разработке программного обеспечения, которая не может быть игнорирована при разработке любого проекта значительного размера.
Системы контроля версии, такие как Git , Mercurial и SVN , позволяют разным версиям базы кода быть разделенными, основываясь на том, над чем работают или кто разрабатывает код. Разные версии могут быть объединены вместе, поэтому несколько программистов могут работать с базой кода в одно то же время, не создавая ошибки, которые могли бы повлиять на ход работы остальных разработчиков.
Системы контроля версий играют ключевую роль еще и потому, что позволяют программистам откатить изменения до более ранней версии кода, просто возвратившись в состояние базы до появления ошибок, не допуская при этом других ошибок, за исправление которых пришлось бы дорого поплатиться.
6. Модульность
Плохо спроектированный код – это главный источник трудно исправляемых ошибок. Если код легко понять, и он может быть « выполнен » в уме или на бумаге, есть большая вероятность, что программисты смогут быстро находить и исправлять ошибки.
Самый лучший способ добиться этого – писать функции, выполняющие что-то одно. А вот участок кода с большим количеством функций имеет большую склонность к возникновению ошибок, которые сложно отслеживать.
Проектирование компонентов программного обеспечения, которые осуществляет только одну функцию, часто называется модульным дизайном. Модульность помогает программистам рассматривать системы программного обеспечения в двух измерениях. Во-первых, модульность создает уровень абстракции, позволяющий думать о модуле системы без понимания всех деталей его работы.
Например, программист, разрабатывающий систему электронной коммерции, мог бы, рассматривая модуль обработки кредитной карты, видеть, как он связан с остальным кодом, не вдаваясь в детали самой обработки кредитной карты. С другой стороны, детали модуля (в нашем примере того, который занимается обработкой кредитной карты) могут быть рассмотрены и поняты без обращения к не имеющему отношение к этому модулю коду.
7. Автоматизированные тесты
Модульные тесты и другие типы автоматизированных тестов идут рука об руку с модульным программированием.
Автоматизированный код – это участок кода, который выполняет программу с определенными входными параметрами и проверяет, соответствует ли поведение программы ожидаемому.
Модульные тесты проверяют функционирование отдельных функций или методов класса, в то время как функциональные тесты проверяют специфичное поведение всей программы, а интеграционные тесты проверяют большие части системы или всю систему в целом.
Существует много фреймворков для тестирования, которые делают написание тестов легче. Многие из известных фреймворков, используемых сегодня, были получены из библиотеки JUnit, написанной Кентом Бентом (Kent Bent), одним из первых сторонников идеи разработки через тестирование. Стандартная библиотека Python включает свою версию JUnit под именем PyUni или просто unittest .
8. Метод «Плюшевый мишка» (или отладка «Резиновая уточка»)
Если верить легендам программирования Брайану Кернигану и Робу Пайку (Brain Kernighan и Rob Pike), отладка по типу «Резиновая уточка» возникла в университетском компьютерном центре, где студенты должны были садиться напротив плюшевого мишки и объяснять ему их ошибки, прежде чем обращаться за помощью к живому человеку.
Этот метод отладки оказался настолько эффективным, что быстро распространился во всем мире разработки программного обеспечения, и также как простой оператор печати, продолжает существовать по сей день, несмотря на то, что есть, казалось бы, более сложные инструменты. Практически все может заменить плюшевого мишку: резиновые уточки, как терпеливые слушатели, тоже пользуются спросом.
Важной частью этого метода является то, что нужно объяснять код и проблему вслух в простых и понятных терминах. Есть подобная методика, которая также полезна – вести журнал программирования, в который нужно записывать мысли о коде до и после его реализации.
9. Пишите комментарии к коду
Комментарии должны объяснять цель кода на низком уровне. Должна существовать возможность легко ответить на вопросы о том, что строка кода делает и как она это делает, прочитав сам код. Это достигается путем написания читаемого кода, который разработан настолько просто, насколько это возможно, и использует осмысленные имена для функций и переменных.
Комментарии к коду должны заполнять пробелы информации в максимально возможной степени, отвечая на такие вопросы, как: почему используется конкретная реализация, или как данный участок кода взаимодействует с остальной частью программы.
Написание хороших комментариев – это отличная практика разработки программного обеспечения даже в свободном от ошибок коде, но когда ошибки появляются, комментарии помогут сэкономить массу времени, затрачиваемого на понимание кода, написанного несколько дней, недель или даже месяцев назад.
10. Пишите документацию
В то время как комментарии описывают код на низком уровне, с точки зрения программиста, программная документация описывает функционирование всей системы в доступной для пользователей форме. В зависимости от типа разрабатываемого программного обеспечения, документация может описывать интерфейсы программирования, графические интерфейсы или рабочие процессы.
Написание документации демонстрирует понимание программной системы, и часто указывает на те части системы, которые не до конца понятны и являются вероятным источником ошибок.
На пути к мастерству: избавляемся от ошибок
Программирование – это, прежде всего, искусство. И также как для любого другого вида искусства, путь к мастерству в нем вымощен трудолюбием и стремлением учиться. Работа по изучению программирования никогда не заканчивается. Всегда есть что-то новое для изучения и новые способы по улучшению.
Какими из этих 10 средств отладки вы пользуетесь сейчас? Какими вы могли бы начать пользоваться с сегодняшнего дня? Какие из этих инструментов требуют времени на практику и освоения новых навыков?
Программисты пользуются преимуществом, которым только некоторые другие мастера могут когда-либо воспользоваться: самые лучшие инструменты и знания о программировании свободно и бесплатно доступны для всех, кто заинтересован в этом вопросе. Вы можете стать профи в отладке кода: все, что вы должны сделать для этого – просто взять инструменты по отладке и приступить к работе.
Читайте также: