лекция. Очная 2016 г Содержание
Скачать 1.01 Mb.
|
Методы реализуемых путей. Данная методика заключается в выделении из множества путей подмножества всех реализуемых путей. После этого покрывающее множество путей строится из полученного подмножества реализуемых путей. Достоинство статических методов состоит в сравнительно небольшом количестве необходимых ресурсов как при использовании, так и при разработке. Однако их реализация может содержать непредсказуемый процент брака (нереализуемых путей). Кроме того, в этих системах переход от покрывающего множества путей к полной системе тестов пользователь должен осуществить вручную, а эта работа достаточно трудоемкая. Динамические методы требуют значительно больших ресурсов как при разработке, так и при эксплуатации, однако увеличение затрат происходит в основном за счет разработки и эксплуатации аппарата определения реализуемости пути (символический интерпретатор, решатель неравенств). Достоинство этих методов заключается в том, что их продукция имеет некоторый качественный уровень – реализуемость путей. Методы реализуемых путей дают самый лучший результат [33]. Интеграционное тестирование Интеграционное тестирование – это тестирование части системы, состоящей из двух и более модулей. Основная задача интеграционного тестирования – поиск дефектов, связанных с ошибками в реализации и интерпретации интерфейсного взаимодействия между модулями. С технологической точки зрения интеграционное тестирование является количественным развитием модульного, поскольку так же, как и модальное тестирование, оперирует интерфейсами модулей и подсистем и требует создания тестового окружения, включая заглушки (Stub) на месте отсутствующих модулей. Основная разница между модульным и интеграционным тестированием состоит в целях, т. е. в типах обнаруживаемых дефектов, которые, в свою очередь, определяют стратегию выбора входных данных и методов анализа. В частности, на уровне интеграционного тестирования часто применяются методы, связанные с покрытием интерфейсов, например, вызовов функций или методов, или анализ использования интерфейсных объектов, таких как глобальные ресурсы, средства коммуникаций, предоставляемых операционной системой. На рисунке 4.2 приведена структура комплекса программ К, состоящего из оттестированных на этапе модульного тестирования модулей М1, М2 М11 М12, М21, М22. Задача, решаемая методом интеграционного тестирования, — тестирование межмодульных связей, реализующихся при исполнении программного обеспечения комплекса К. Интеграционное тестирование использует модель «белого ящика» на модульном уровне. Поскольку тестировщику текст программы известен с детальностью до вызова всех модулей, входящих в тестируемый комплекс, применение структурных критериев на данном этапе возможно и оправданно. Рисунок 4.2 – Пример структуры комплекса программ Интеграционное тестирование применяется на этапе сборки модульно оттестированных модулей в единый комплекс. Известны два метода сборки модулей; монолитный, характеризующийся одновременным объединением всех модулей в тестируемый комплекс; инкрементальный, характеризующийся пошаговым (помодульным) наращиванием комплекса программ с пошаговым тестированием собираемого комплекса. В инкрементальном методе выделяют две стратегии добавления модулей: «сверху вниз» и соответствующее ему восходящее тестирование; «снизу вверх» и соответственно нисходящее тестирование. Особенности монолитного тестировании заключаются в следующем: для замены не разработанных к моменту тестирования модулей, кроме самого верхнего (К на рис. 5.2), необходимо дополнительно разрабатывать драйверы (test driver) и/или заглушки (stub) [9), замещающие отсутствующие на момент сеанса тестирования модули нижних уровней. Сравнение монолитного и интегрального подходов дает следующие результаты. Монолитное тестирование требует больших трудозатрат, связанных с дополнительной разработкой драйверов и заглушек и со сложностью идентификации ошибок, проявляющихся в пространстве собранного кода. Пошаговое тестирование связано с меньшей трудоемкостью идентификации ошибок за счет постепенного наращивания объема тестируемого кода и соответственно локализации добавленной области тестируемого кода. Монолитное тестирование предоставляет большие возможности распараллеливания работ, особенно на начальной фазе тестирования. Особенности нисходящего тестирования заключаются в следующем: организация среды для исполняемой очередности вызовов оттестированными модулями тестируемых модулей, постоянная разработка и использование заглушек, организация приоритетного тестирования модулей, содержащих операции обмена с окружением, или модулей, критичных для тестируемого алгоритма. Недостатки нисходящего тестирования: проблема разработки достаточно «интеллектуальных» заглушек, т. е. заглушек, способных к использованию при моделировании различных режимов работы комплекса, необходимых для тестирования; сложность организации и разработки среды для реализации исполнения модулей и нужной последовательности; параллельная разработка модулей верхних и нижних уровней приводит к не всегда эффективной реализации модулей из-за подстройки (специализации) еще не тестированных модулей нижних уровней к уже оттестированным модулям верхних уровней. Недостатки восходящего тестирования: запаздывание проверки концептуальных особенностей тес тируемого комплекса; необходимость в разработке и использовании драйверов. Системное тестирование Системное тестирование качественно отличается от интеграционного и модульного уровней. Системное тестирование рассматривает тестируемую систему в целом и оперирует на уровне пользовательских интерфейсов, в отличие от последних фаз интеграционного тестирования, которое оперирует на уровне интерфейсов модулей. Различны и цели этих уровней тестирования. На уровне системы часто сложно и малоэффективно анализировать прохождение тестовых траекторий внутри программы или отслеживать правильность работы конкретных функций. Основная задача системного тестирования – выявления дефектов, связанных с работой системы в целом, таких как неверное использование ресурсов системы, непредусмотренные комбинации данных пользовательского уровня, несовместимость с окружением, непредусмотренные сценарии использования, отсутствующая или неверная функциональность, неудобство в применении Системное тестирование производится над проектом в целом с помощью метода «черного ящика». Структура программы не имеет никакого значения, для проверки доступны только входы и выходы, видимые пользователю. Тестированию подлежат коды и пользовательская документация. Категории тестов системного тестирования: Полнота решения функциональных задач. Стрессовое тестирование – на предельных объемах нагрузки входного потока. Корректность использования ресурсов (утечка памяти, возврат ресурсов). Оценка производительности. Эффективность защиты от искажения данных и некорректных действий. Проверка инсталляции и конфигурации на разных платформах, Корректность документации. Поскольку системное тестирование проводится на пользовательских интерфейсах, создается иллюзия того, что построение специальной системы автоматизации тестирования не всегда необходимо. Однако объемы данных на этом уровне таковы, что обычно более эффективным подходом является полная или частичная автоматизация тестирования, что приводит к созданию тестовой системы, гораздо более сложной, чем система тестирования, применяемая на уровне тестирования модулей или их комбинаций. Эффективность и оптимизация программ Эффективными считаются программы, требующие минимального времени выполнения и/или минимального объема оперативной памяти. Особые требования к эффективности программного обеспечения предъявляют при наличии ограничений (на время реакции системы, на объем оперативной памяти и т. п.). В случаях, когда обеспечение эффективности не требует серьезных временных и трудовых затрат, а также не приводит к существенному ухудшение технологических свойств, необходимо это требование иметь в виду. Разумный подход к обеспечению эффективности разрабатываемого программного обеспечения состоит в том, чтобы в первую очередь оптимизировать те фрагменты программы, которые существенно влияют на характеристики эффективности. Для уменьшения времени выполнения некоторой программы в первую очередь следует проанализировать циклические фрагменты с большим количеством повторений: экономия времени выполнения одной итерации цикла будет умножена на количество итераций. Не следует забывать и о том, что многие способы снижения временных затрат приводят к увеличению емкостных и, наоборот, уменьшение объема памяти может потребовать дополнительного времени на обработку. И тем более не следует «платить» за увеличение эффективности снижением технологичности разрабатываемого программного обеспечения. Исключения возможны лишь при очень жестких требованиях и наличии соответствующего контроля за качеством. Частично проблему эффективности программ решают за программиста компиляторы. Средства оптимизации, используемые компиляторами, делят на две группы: машинно-зависимые, т. е. ориентированные на конкретный машинный язык, выполняют оптимизацию кодов на уровне машинных команд, например, исключение лишних пересылок, использование более эффективных команд и т. п. машинно-независимые выполняют оптимизацию на уровне входного языка, например, вынесение вычислений константных (независящих от индекса цикла) выражений из циклов и т.п. Естественно, нельзя вмешаться в работу компилятора, но существует много возможностей оптимизации программы на уровне команд. Способы экономии памяти. Принятие мер по экономии памяти предполагает, что в каких-то случаях эта память неэкономно использовалась. Учитываю, что анализировать имеет смысл только операции размещения данных, существенно влияющие па характеристику эффективности, следует обращать особое внимание на выделение памяти под данные структурных типов (массивов, записей, объектов и т. п.). Прежде всего при наличии ограничений на использование памяти следует выбирать алгоритмы обработки, не требующие дублирования исходных данных структурных типов в процессе обработки. Примером могут служить, алгоритмы сортировки массивов, выполняющие операцию в заданном массиве, например хорошо известная сортировка методом «пузырька». Если в программе необходимы большие массивы, используемые ограниченное время, то их можно размещать в динамической памяти и удалять при завершении обработки. Также следует помнить, что при передаче структурных данных в подпрограмму «по значению» копии этих данных размещаются в стеке. Избежать копирования иногда удается, если передать данные «по ссылке», но как неизменяемые (описанные const). Способы уменьшения времени выполнения. Как уже упоминалось выше, для уменьшения времени выполнения в первую очередь необходимо анализировать циклические участки программы с большим количеством повторений. При их написании необходимо по возможности: выносить вычисление константных, т. е. не зависящих от параметров цикла, выражений из циклов; избегать «длинных» операций умножения и деления, заменяя их сложением, вычитанием и сдвигами; минимизировать преобразования типов в выражениях; оптимизирован, запись условных выражений — исключать лишние проверки; исключать многократные обращения к элементам массивов по индексам (особенно многомерных, так как при вычислении адреса элемента используются операции умножения на значение индексов), первый раз прочитав из памяти элемент массива, следует запомнить его в скалярной переменной и использовать в нужных местах; избегать использования различных типов в выражении и т. п. Стиль программирования Стиль программирования, под которым понимают стиль оформления программ и их «структурность», также существенно влияет на читаемость программного кода и количество ошибок программирования. Основные принципы форматирования Основная теорема форматирования гласит, что хорошее визуальное форматирование показывает логическую структуру программы. Создание красивого кода – хорошо, а демонстрация структуры кода — лучше. Если одна методика лучше показывает структуру кода, а другая выглядит красивей, следует использовать ту, которая лучше демонстрирует структуру. Форматирование – это ключ к структуре программы. Компьютеру важна исключительно информация о скобках или операторах begin и end, а читатель-человек склонен делать выводы из визуального представления кода. Цели хорошего форматирования Многие решения о том, как должно выглядеть хорошее форматирование, представляют собой субъективные эстетические оценки; часто можно достичь одной и той же цели по-разному. Говоря объективно, хорошая схема форматирования должна делать следующее: точно представлять логическую структуру кода. Для демонстрации логической структуры программисты обычно применяют отступы и другие неотображаемые символы; единообразно показывать логическую структуру кода. Некоторые стили форматирования состоят из правил с таким количеством исключений, что последовательно их соблюдать практически невозможно. Действительно хороший стиль подходит в большинстве случаев; улучшать читабельность. Стратегия использования отступов, соответствующая логике, но усложняющая процесс чтения кода, бесполезна. Схема форматирования, использующая пробелы и разделители только там, где они требуются компилятору, логична, но читать такой код невозможно. Хорошая структура форматирования упрощает чтение кода; выдерживать процедуру исправления. Лучшие схемы форматирования хорошо переносят модификацию кода. Исправление одной строки не должно приводить к изменению нескольких других. В дополнение к этим критериям иногда во внимание принимается и задача минимизации количества строк кода, необходимых для реализации простого выражения или блока. Способы форматирования Можно получить хороший формат кода, по-разному используя несколько инструментов для форматирования. Не отображаемые символы Используйте неотображаемые символы для улучшения читаемости. Неотображаемые символы, к которым относятся пробелы, знаки табуляции, переводы строк и пустые строки, – это основное средство для демонстрации структуры программы. Вам вряд ли придет в голову писать книгу без пробелов между словами, разбиения на абзацы и деления на главы. Может, такую книгу и можно прочитать от начала до конца, но практически невозможно просматривать ее в поисках какой-то мысли или важного момента. Еще хуже, что такой формат книги не позволит показать читателю, как автор намеревался организовать информацию. Структура, предлагаемая автором, дает подсказку о логической организации темы. Разбиение книги на главы, абзацы и предложения показывает читателю, как следует мысленно организовывать тему. Если эта организация неочевидна, читателю приходится самому ее домысливать, что налагает на него более тяжкое бремя и увеличивает вероятность никогда не узнать, как на самом деле организована данная тема. Информация, содержащаяся в программе, сосредоточена еще плотней, чем информация в большинстве книг. Если страницу книги можно прочесть и понять за 1 или 2 минуты, то большинство программистов не могут читать и понимать листинг программы со скоростью, даже приблизительно сравнимой с этой. Программа должна давать гораздо больше подсказок о своей организации, чем книга. Группировка взаимосвязанных выражений– еще один способ применения неотображаемых символов. В литературе мысли группируются в абзацы. Хорошо написанный абзац содержит предложения, относящиеся только к определенной идее. Он не должен содержать посторонних предложений. Точно так же абзац кода должен содержать только взаимосвязанные операторы, выполняющие одно задание. Пустые строки. Кроме необходимости группировать взаимосвязанные операторы, очень важно отделять несвязанные выражения друг от друга. Начало нового абзаца в книге обозначается отступом или пустой строкой. Начало нового абзаца в коде нужно указывать с помощью пустой строки. Пустые строки позволяют продемонстрировать организацию программы. Вы можете использовать их для деления групп взаимосвязанных операторов на абзацы, отделения методов друг от друга и выделения комментариев. Хотя эту статистику тяжело применить на практике, но одно исследование показало, что оптимальное число пустых строк в программе составляет от 8 до 16%. Если оно больше 16%, то время, затрачиваемое на отладку, заметно увеличивается. Отступы. Применяйте отступы для демонстрации логической структуры программы. Как правило, операторы выделяются отступами, когда они следуют после некоторого выражения, от которого они логически зависят. Оптимальными являются отступы из 2—4 пробелов. Скобки Используйте скобки чаще, чем вам это кажется необходимым. Применяйте скобки для разъяснения выражений, состоящих из двух и более членов. Возможно, в скобках нет нужды, но они добавляют ясности и ничего вам не стоят. Например, скажите, как вычисляется следующее выражение? Вариант на C++: 12 + 4% 3 * 7 / 8. Пришлось ли вам задуматься о том, как эти выражения вычисляются, вот в чем вопрос? Можете ли вы быть уверенными в своем ответе без обращения к справочной информации? Даже опытные программисты не отвечают с полной уверенностью, и именно поэтому следует использовать скобки, если есть хоть малейшее сомнение в том, как вычисляется выражение. Надежность программного обеспечения Это комплексное свойство включает две составляющие: безошибочность (correctness) – соответствие спецификации; устойчивость (robustness) или отказоустойчивость (fault -tolerance) – способность продолжать правильно работать после отказов. Улучшение первого качества достигается хорошей технологией, предупреждающей ошибки (faulе-avoidance). Однако 100%-ное отсутствие ошибок недостижимо. Устойчивость должна быть относительно любых виден отказов, для ее полдержания создаются специальные программно-аппаратные средства. Отказ(failure) по ГОСТу — нарушение работоспособности изделия и его соответствия требованиям технической документации (рисунок 4.3). Применительно к программам (стандарт IEEE/ ANSI) отказ есть неспособность функциональной единицы системы, зависящей от программы, выполнять требуемую функцию в заданных пределах. Рисунок 4.3 – Диаграмма состояний и переходов при отказе Классификация причин отказов: Физические дефекты: внутренние (старение, износ); внешние воздействия (радиация, высокая температура). Ошибки людей: ошибки эксплуатации или взаимодействия; проектные ошибки. Повреждение(Fault) – неисправность уже появилась, но еще не проявилась вовне. Например, отказала ячейка памяти, но из нес еще не читали или в нее еще не писали. Отказ компонента – это повреждение системы. Время нахождения в неисправном, но работоспособном состоянии называется латентным (скрытым) периодом отказа. Восстановление(Recovery) — возврат в исправное состояние путем: ручного ремонта, замены, исправления; автоматически — задействованием резервов; самопроизвольно, обычно быстро. В случае с) отказ называется сбоем, т. е. сбой – это кратковременный самовосстанавливающийся отказ. Остальные отказы называются устойчивыми (по умолчанию отказ устойчивый). В электронной аппаратуре сбои происходят на порядок чаще устойчивых. Их причины — флюктуации питания, ситуации «гонок» сигналов, альфа-частицы и др. В программах аналогично сбоям ведут себя времязависимые ошибки – их иногда называют «мерцающими» (blinking bugs). Типичный пример восстановления – с помощью резервной (backup) копии данных. Если выполнить восстановление в латентном периоде отказа, то он никогда не проявится вовне – в этом состоит идеальная отказоустойчивость. Количественные характеристики надежности программ Надежность нужно оценивать, измерять, предсказывать — обеспечивать заданные требования к надежности в проекте и проверять их выполнение в продукте. «Внутренняя» характеристика надежности — количество оставшихся ошибок в программе — интересна больше разработчикам, чем потребителям. Для последних важны характеристики, традиционные для теории надежности, основанные на предположении о стохастическом (случайном во времени) процессе возникновения отказов: среднее время безотказной работы (MTBF — Mean Time Between Failures) и коэффициент готовности. Третья характеристика, взаимосвязанная с первой, — интенсивность отказов — среднее их количество в единицу времени. Таблица 4.3. Средние значения MTBF для устойчивых отказов Таким образом, программы вносят наибольший вклад а (не)надежность современных вычислительных систем. Между тем существуют столь ответственные (mission-critical) приложения, где требуется очень малая вероятность отказов. Например, для бортовой системы управления космическим зондом требуется λ= 10-9, чтобы вероятность устойчивого отказа в первые 10 лет работы была не более 10-4 (или вероятность безотказной работы 0,9999), что означает MTBF = 100 тысяч лет! Преимущества парного программирования Парное программирование экономически оправданно. При использовании в организации ХР штат программистов должен возрасти вдвое. Так как квалифицированные кадры являются достаточно дорогими, то, казалось бы, расходы на создание программного обеспечения должны значительно возрасти. И это действительно так – затраты возрастают на 15 %, но в то же время, как доказывают исследования, при парном программировании количество ошибок на 15% меньше, чем при одиночном. Если учесть стоимость исправления ошибок, обнаруженных на разных стадиях жизненного цикла продукта, включая внедрение, то сэкономленные на этом деньги значительно превосходят затраты на увеличение штата сотрудников. Быстрая обучаемость. При парном программировании специалисты перенимают приемы работы друг друга, значительно расширяют свои знания и передают свой опыт. Работая вместе, люди чувствуют себя уверенней, так как каждый из них знает, что партнер контролирует все его действия. При этом увеличивается качество его работы. В случае выбытия из проекта одного разработчика работавший с ним в паре сможет сразу же продолжить его деятельность, что приведет к экономии дорогого времени и сил второго разработчика. Работа в паре улучшает коммуникабельность в коллективе, помогает наладить понимание и взаимоотношения, что является крайне важным фактором с точки зрения психологии. |