4612

Объектно-ориентированного программирования и их реализация в системе разработки программ Delphi

Реферат

Информатика, кибернетика и программирование

Введение Сложность современного программного обеспечения требует от разработчиков владения наиболее перспективными технологиями его создания. Одной из таких технологий на настоящий момент является объектно-ориентированное программирование (ООП), при...

Русский

2012-11-23

1.67 MB

36 чел.

Введение

Сложность современного программного обеспечения требует от разработчиков владения наиболее перспективными технологиями его создания. Одной из таких технологий на настоящий момент является объектно-ориентированное программирование (ООП), применение которого позволяет разрабатывать программное обеспечение повышенной сложности за счет улучшения его технологичности (лучших механизмов разделения данных, увеличения повторяемости кодов, использования стандартизованных интерфейсов пользователя и т.д.). Чтобы технологически грамотно использовать ООП, необходимо хорошо понимать его основные концепции и научиться мыслить при разработке программы в понятиях ООП.


1. ТЕСТИРОВАНИЕ ПРОГРАММНЫХ ПРОДУКТОВ

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

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

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

1.1. Виды контроля качества разрабатываемого программного обеспечения

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

Рис. 1. Зависимость вероятности правильного исправления ошибок (а) и стоимости исправления ошибок от этапа разработки

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

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

Процесс разработки программного обеспечения, в том виде, как он определяется в современной модели жизненного цикла программного обеспечения, предполагает три стадии тестирования:

  •  автономное тестирование компонентов программного обеспечения;
  •  комплексное тестирование разрабатываемого программного обеспечения;
  •  системное или оценочное тестирование на соответствие основным критериям качества.

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

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

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

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

Формирование набора тестов имеет большое значение, поскольку тестирование является одним из наиболее трудоемких этапов (от 30 до 60% общей трудоемкости) создания программного продукта. Причем доля стоимости тестирования в общей стоимости разработки имеет тенденцию возрастать при увеличении сложности программного обеспечения и повышении требований к их качеству.

Существуют два принципиально различных подхода к формированию тестовых наборов: структурный и функциональный.

Структурный подход базируется на том, что известна структура тестируемого программного обеспечения, в том числе его алгоритмы («стеклянный ящик»). В этом случае тесты строят так, чтобы проверить правильность реализации заданной логики в коде программы.

Функциональный подход основывается на том, что структура программного обеспечения не известна («черный ящик»). В этом случае тесты строят, опираясь на функциональные спецификации. Этот подход называют также подходом, управляемым данными, так как при его использовании тесты строят на базе различных способов декомпозиции множества данных.

Наборы тестов, полученные в соответствии с методами этих подходов, обычно объединяют, обеспечивая всестороннее тестирование программного обеспечения.

1.2. Ручной контроль программного обеспечения

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

Различают статический и динамический подходы к ручному контролю. При статическом подходе анализируют структуру, управляющие и информационные связи программы, ее входные и выходные данные. При динамическом – выполняют ручное тестирование, т.е. вручную моделируют процесс выполнения программы на заданных исходных данных.

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

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

Основными методами ручного контроля являются:

  •  инспекции исходного текста,
  •  сквозные просмотры,
  •  проверка за столом,
  •  оценки программ.

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

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

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

1. Контроль обращений к данным

  •  Все ли переменные инициализированы?
  •  Не превышены ли максимальные (или реальные) размеры массивов и строк?
  •  Не перепутаны ли строки со столбцами при работе с матрицами?
  •  Присутствуют ли переменные со сходными именами?
  •  Используются ли файлы? Если да, то при вводе из файла проверяется ли завершение файла?
  •  Соответствуют ли типы записываемых и читаемых значений?
  •  Использованы ли нетипизированные переменные, открытые массивы, динамическая память? Если да, то соответствуют ли типы переменных при «наложении» формата? Не выходят ли индексы за границы массивов?

2. Контроль вычислений

  •  Правильно ли записаны выражения (порядок следования операторов)?
  •  Корректно ли выполнены вычисления над неарифметическими переменными?
  •  Корректно ли выполнены вычисления с переменными различных типов (в том числе с использованием целочисленной арифметики)?
  •  Возможно ли переполнение разрядной сетки или ситуация машинного нуля?
  •  Соответствуют ли вычисления заданным требованиям точности?
  •  Присутствуют ли сравнения переменных различных типов?

3. Контроль передачи управления

  •  Будут ли корректно завершены циклы?
  •  Будет ли завершена программа?
  •  Существуют ли циклы, которые не будут выполняться из-за нарушения условия входа? Корректно ли продолжатся вычисления?
  •  Существуют ли поисковые циклы? Корректно ли отрабатываются ситуации «элемент найден» и «элемент не найден»?

4. Контроль межмодульных интерфейсов

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

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

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

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

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

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

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

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

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

1.3. Структурное тестирование

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

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

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

Структурный подход к тестированию имеет ряд недостатков. Так тестовые наборы, построенные по данной стратегии:

  •  не обнаруживают пропущенных маршрутов;
  •  не обнаруживают ошибок, зависящих от обрабатываемых данных, например, в операторе if (a-b) < eps – пропуск функции абсолютного значения abs проявится только, если а < b;
  •  не дают гарантии, что программа правильна, например, если вместо сортировки по убыванию реализована сортировка по возрастанию.

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

Procedure m (а, b: real; var x: real);

begin

 if (a>1) and (b=0) then x:=x/a;

 if (a=2) or (x>1) then x:=x+1;

end;

Формирование тестовых наборов для тестирования маршрутов может осуществляться по нескольким критериям:

  •  покрытие операторов;
  •  покрытие решений (переходов);
  •  покрытие условий;
  •  покрытие решений/условий;
  •  комбинаторное покрытие условий.

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

Для фрагмента, алгоритм и граф которого представлены на рис. 2, можно было бы выполнить каждый оператор один раз, задав в качестве входных данных а = 2, b = 0, х = 3. Но при этом из второго условия следует, что переменная х может принимать любое значение, и в некоторых версиях языка Pascal это значение проверяться не будет (!).

Кроме того:

  •  если при написании программы в первом условии указано: (a>1) or (b=0), то ошибка обнаружена не будет;
  •  если во втором условии вместо х > 1 записано х > 0, то эта ошибка тоже не будет обнаружена;
  •  существует путь 1-2-4-6 (см. рис. 2, б), в котором х вообще не меняется и, если здесь есть ошибка, она не будет обнаружена.

Рис. 2. Схема алгоритма процедуры примера (а) и ее граф передач управления (б)

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

Покрытие решений (переходов). Для реализации этого критерия необходимо такое количество и состав тестов, чтобы результат проверки каждого условия (т.е. решение) принимал значения «истина» или «ложь», по крайней мере, один раз.

Нетрудно видеть, что критерий покрытия решений удовлетворяет критерию покрытия операторов, но является более «сильным».

Программу, алгоритм которой представлен на рис. 2, а, можно протестировать по методу покрытия решений двумя тестами, покрывающими либо пути: 1-2-4-6, 1-2-3-4-5-6, либо пути: 1-2-3-4-6, 1-2-4-5-6, например:

а = 3, b = 0, х = 3 — путь 1-2-3-4-5-6;

а = 2, b = 1, х = 1 — путь 1-2-4-6.

Однако путь, где х не меняется, будет проверен с вероятностью 50%: если во втором условии вместо условия х > 1 записано х < 1, то этими двумя тестами ошибка обнаружена не будет.

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

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

Программа, алгоритм которой представлен на рис. 2, а, проверяет четыре условия:

1) а > 1; 2) b = 0; 3) а = 2; 4) х > 1.

Необходимо реализовать все возможные ситуации:

а > 1, а ≥ 1, b = 0, b ≠ 0, а = 2, а ≠ 2, х > 1, х ≤ 1.

Тесты, удовлетворяющие этому условию:

а = 2, b = 0, х = 4 — путь 1-2-3-4-5-6, условия: 1 - да, 2 - да, 3 - да, 4 - да;

а = 1, b = 1, х = 1 — путь 1-2-4-6, условия: 1 - нет, 2 - нет, 3 - нет, 4 - нет.

Критерий покрытия условий часто удовлетворяет критерию покрытия решений, но не всегда. Тесты критерия покрытия условий для ранее рассмотренных примеров покрывают результаты всех решений, но это случайное совпадение. Например, тесты:

а = 1, b = 0, х = 3 — путь 1-2-3-6, условия: 1 - нет, 2 - да, 3 - нет, 4 - да;

а = 2, b = 1, х = 1 — путь 1-2-3-4-5-6, условия: 1 - да, 2 - нет, 3 - да, 4 - нет

покрывают результаты всех условий, но только два из четырех результатов решений: не выполняется результат «истина» первого решения и результат «ложь» второго.

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

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

Анализ, проведенный выше, показывает, что этому критерию удовлетворяют тесты:

а = 2, b = 0, х = 4 — путь 1-2-3-4-5-6, условия: 1 - да, 2 - да, 3 - да, 4 - да;

а =1, b = 1, х = 1 — путь 1-2-4-6, условия: 1 - нет, 2 - нет, 3 - нет, 4 - нет.

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

Для программы, алгоритм которой представлен на рис. 2, необходимо покрыть тестами восемь комбинаций:

1) а > 1, b = 0;  5) а = 2, х > 1;

2) а > 1, b ≠ 0;  6) а = 2, х ≤ 1;

3) а ≤ 1, b = 0;  7) а ≠ 2, х > 1;

4) а ≤ 1, b ≠ 0;  8) а ≠ 2, х ≤ 1.

Эти комбинации можно проверить четырьмя тестами:

а = 2, b = 0, х = 4 — проверяет комбинации (1), (5);

а = 2, b = 1, х = 1 — проверяет комбинации (2), (6);

а = 1, b = 0, х = 2 — проверяет комбинации (3), (7);

а = 1, b = 1, х = 1 — проверяет комбинации (4), (8).

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

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

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

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

Термин «возможных» употреблен здесь потому, что некоторые комбинации условий могут быть нереализуемы. Например, для комбинации k < 0 и k > 40 задать k невозможно.

1.4. Функциональное тестирование

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

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

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

При функциональном тестировании различают следующие методы формирования тестовых наборов:

  •  эквивалентное разбиение;
  •  анализ граничных значений;
  •  анализ причинно-следственных связей;
  •  предположение об ошибке.

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

Разработку тестов методом эквивалентного разбиения осуществляют в два этапа: на первом выделяют классы эквивалентности, а на втором – формируют тесты.

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

  •  если некоторый параметр х может принимать значения в интервале [1, 999], то выделяют один правильный класс 1 < х < 999 и два неправильных: х < 1 и х > 999;
  •  если входное условие определяет диапазон значений порядкового типа, например, «в автомобиле могут ехать от одного до шести человек», то определяется один правильный класс эквивалентности и два неправильных: ни одного и более шести человек;
  •  если входное условие описывает множество входных значений и есть основания полагать, что каждое значение программист трактует особо, например, «типы графических файлов: bmp, jpeg, vsd», то определяют правильный класс эквивалентности для каждого значения и один неправильный класс, например, txt;
  •  если входное условие описывает ситуацию «должно быть», например, «первым символом идентификатора должна быть буква», то определяется один правильный класс эквивалентности (первый символ – буква) и один неправильный (первый символ - не буква);
  •  если есть основание считать, что различные элементы класса эквивалентности трактуются программой неодинаково, то данный класс разбивается на меньшие классы эквивалентности.

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

Ограничение на значение параметра

Правильные классы эквивалентности

Неправильные классы эквивалентности

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

Анализ граничных значений. Граничные значения – это значения на границах классов эквивалентности входных значений или около них. Анализ показывает, что в этих местах резко увеличивается возможность обнаружения ошибок. Например, если в программе анализа вида треугольника было записано А + В ≥ С вместо А + В > С, то задание граничных значений приведет к ошибке: линия будет отнесена к одному из видов треугольника.

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

  •  если входное условие описывает область значений, то следует построить тесты для границ области и тесты с неправильными входными данными для ситуаций незначительного выхода за границы области, например, если описана область [–1.0, +1.0], то должны быть сгенерированы тесты: –1.0, +1.0, –1.001 и +1.001;
  •  если входное условие удовлетворяет дискретному ряду значений, то следует построить тесты для минимального и максимального значений и тесты, содержащие значения большие и меньшие этих двух значений, например, если входной файл может содержать от 1 до 255 записей, то следует проверить 0, 1, 255 и 256 записей;
  •  если существуют ограничения выходных значений, то целесообразно аналогично тестировать и их: конечно не всегда можно получить результат вне выходной области, но, тем не менее, стоит рассмотреть эту возможность;
  •  если некоторое входное или выходное значение программы является упорядоченным множеством, например, это последовательный файл, линейный список или таблица, то следует сосредоточить внимание на первом и последнем элементах этого множества.

Помимо указанных граничных значений, целесообразно поискать другие.

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

Оба описанных метода основаны на исследовании входных данных. Они не позволяют проверять результаты, получаемые при различных сочетаниях данных. Для построения тестов, проверяющих сочетания данных, применяют методы, использующие булеву алгебру.

Анализ причинно-следственных связей. Позволяет системно выбирать высокорезультативные тесты. Метод использует алгебру логики и оперирует понятиями «причина» и «следствие». Причиной в данном случае называют отдельное входное условие или класс эквивалентности. Следствием – выходное условие или преобразование системы. Идея метода заключается в отнесении всех следствий к причинам, т.е. в уточнении причинно-следственных связей. Данный метод дает полезный побочный эффект, позволяя обнаруживать неполноту и неоднозначность исходных спецификаций.

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

Далее на основе анализа семантического (смыслового) содержания спецификации строят таблицу истинности, в которой каждой возможной комбинации причин ставится в соответствие следствие. При этом целесообразно истину обозначать «1», ложь - «0», а для обозначения безразличных состояний условий применять обозначение «X», которое предполагает произвольное значение условия (0 или 1). Таблицу сопровождают примечаниями, задающими ограничения и описывающими комбинации причин и/или следствий. которые являются невозможными из-за синтаксических или внешних ограничений. При необходимости аналогично строится таблица истинности для класса эквивалентности.

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

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

Предположение об ошибке. Часто программист с большим опытом находит ошибки, «не применяя никаких методов». На самом деле он подсознательно использует метод «предположение об ошибке».

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

Проиллюстрируем применение всех рассмотренных выше методов на примере.

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

В основе программы лежит решение системы линейных уравнений:

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

1) все коэффициенты - вещественные числа (1 тест);

2-7) поочередно каждый из коэффициентов – не вещественное число (6 тестов).

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

8) результат – единственное решение ( ≠ 0);

9) результат – множество решений (  = 0 и x = y = 0);

10) результат – отсутствие решений (  = 0, но x ≠ 0 или y ≠ 0);

и с результатами на границе:

11) = 0,01;

12) = –0,01;

13) = 0, x = 0,01, y = 0;

14) = 0, y = –0,01, x = 0.

По методу анализа причинно-следственных связей определяем множество условий:

а) для определения типа прямой:

– для определения типа и существования первой прямой;

– для определения типа и существования второй прямой;

б) для определения точки пересечения:

= 0,

x = 0,

y = 0.

Выделяем три группы причинно-следственных связей (определение типа и существования первой линии, определение типа и существования второй линии, определение точки пересечения) и строим таблицы истинности для определения типа первой прямой (табл. 1) и для определения результата (табл. 2). В обеих таблицах X означает неопределенное значение. Для второй прямой таблица истинности будет выглядеть аналогично табл. 1.

Каждая строка этих таблиц преобразуется в тест. При возможности (с учетом независимости групп) берутся данные, соответствующие строкам сразу двух или всех трех таблиц.

Таблица 1

А = 0

B = 0

C = 0

Результат

0

0

X

прямая общего положения

0

1

0

прямая, параллельная оси ОХ

0

1

1

ось ОХ

1

0

0

прямая, параллельная оси ОY

1

0

1

ось ОY

1

1

X

множество точек плоскости

Таблица 2

= 0

x = 0

y = 0

Единственное решение

Множество решений

Решения нет

0

X

X

1

0

0

1

0

X

0

0

1

1

X

0

0

0

1

1

1

1

0

1

0

В результате к уже имеющимся тестам добавляются:

15-21) проверки всех случаев расположения обеих прямых – 6 тестов по первой прямой совмещают с 6-ю тестами по второй прямой так, чтобы варианты не совпадали (6 тестов);

22) проверка несовпадения условия x = 0 или y = 0 (в зависимости от того, какой тест был выбран по методу граничных условий) — тест также можно совместить с предыдущими 6-ю тестами.

По методу предположения об ошибке добавим тест:

23) все коэффициенты – нули.

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

1.5. Тестирования модулей и комплексное тестирование

При тестировании модулей программного обеспечения, так же, как при проектировании и кодировании возможно применение как восходящего, так и нисходящего подходов.

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

                                    а                                                                                  б

Рис. 3 Тестирование программного обеспечения при восходящем подходе:
(а) – автономное тестирование модулей нижнего уровня; (б) – тестирование следующего уровня

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

Нисходящее тестирование. Нисходящее тестирование органически связано с нисходящим проектированием и разработкой; как только проектирование какого-либо модуля заканчивается, его кодируют и передают на тестирование.

В этом случае автономно тестируется только основной модуль. При его тестировании все вызываемые им модули заменяют модулями, которые в той или иной степени имитируют поведение вызываемых модулей (рис. 4). Такие модули принято называть «заглушками». В отличие от тестирующих программ заглушки очень просты, например, они могут просто фиксировать, что им передано управление. Часто заглушки просто возвращают какие-либо фиксированные данные.

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

а

б

Рис. 4. Начальные этапы тестирования: (а) - основного модуля; (б) - двух модулей

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

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

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

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

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

Каждое отклонение от спецификации обязательно документируют, заполняя специальный протокол (рис. 5). Наиболее интересными полями протокола являются тип проблемы и ее описание.

В поле тип проблемы указывают один из вариантов:

1 – ошибка кодирования - программа ведет себя не так, как следует из общепринятых представлений, например, 2 + 2 = 5 – на что разработчик может выдать резолюцию «соответствует проекту»;

2 – ошибка проектирования – программа ведет себя в соответствии с проектом, но специалист по тестированию не согласен с данным решением в проекте – на что разработчик может отреагировать, наложив резолюцию «не согласен с предложением»;

3 – предложение – предложение по улучшению проекта;

4 – расхождение с документацией – обнаружено, что программа ведет себя не так, как указано в документации;

5 – взаимодействие с аппаратурой – обнаружены проблемы при использовании определенного вида аппаратуры;

6 – вопрос – программа делает что-то не совсем понятное.

Рис. 5. Бланк отчета об обнаруженном несоответствии

Описание проблемы должно быть коротким и понятным, например: «Система не запоминает настройки принтера, выполняемые в окне настройки».

Если программист исправляет ошибку, то тестирование повторяют сначала, так как при исправлении ошибки программист может внести в программу новые ошибки. Такое тестирование называют «регрессионным».

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

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

Предложено очень много критериев. Все критерии можно разделить на три группы:

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

Рис. 6. Пример графика обнаружения ошибок

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

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

Часть ошибок при этом остаются неисправленными «отложенными» до выпуска следующей версии.

1.6. Оценочное тестирование

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

Оценочное тестирование, которое также называют «тестированием системы в целом», включает следующие виды:

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

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

2. ОТЛАДКА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ

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

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

2.1. Классификация ошибок

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

В целом сложность отладки обусловлена следующими причинами:

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

В соответствии с этапом обработки, на котором проявляются ошибки, различают (рис. 7):

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

Рис. 7. Классификация ошибок по этапу обработки программы

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

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

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

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

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

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

  •  неверное определение исходных данных,
  •  логические ошибки,
  •  накопление погрешностей результатов вычислений (рис. 8).

Рис. 8. Классификация ошибок этапа выполнения по возможным причинам

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

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

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

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

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

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

2.2. Методы отладки программного обеспечения

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

  •  ручного тестирования;
  •  индукции;
  •  дедукции;
  •  обратного прослеживания.

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

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

Данный метод часто используют как составную часть других методов отладки.

Рис. 9. Схема процесса отладки методом индукции

Рис. 10. Схема процесса отладки методом дедукции

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

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

В процессе доказательства пытаются выяснить, все ли проявления ошибки объясняет данная гипотеза, если не все, то либо гипотеза не верна, либо ошибок несколько.

Метод дедукции. По методу дедукции вначале формируют множество причин, которые могли бы вызвать данное проявление ошибки. Затем, анализируя причины, исключают те, которые противоречат имеющимся данным. Если все причины исключены, то следует выполнить дополнительное тестирование исследуемого фрагмента. В противном случае наиболее вероятную гипотезу пытаются доказать. Если гипотеза объясняет полученные признаки ошибки, то ошибка найдена, иначе – проверяют следующую причину (рис. 10).

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

2.3. Методы и средства получения дополнительной информации

Для получения дополнительной информации об ошибке можно выполнить добавочные тесты или использовать специальные методы и средства:

  •  отладочный вывод;
  •  интегрированные средства отладки;
  •  независимые отладчики.

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

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

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

Интегрированные средства отладки. Большинство современных сред программирования (Delphi, Builder C++, Visual Studio и т.д.) включают средства отладки, которые обеспечивают максимально эффективную отладку. Они позволяют:

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

На рис. 11 показан вид программы в момент перехода в режим пошагового выполнения по достижении точки останова в Delphi. В этот момент программист имеет возможность посмотреть значения интересующих его переменных.

Рис. 11. Переход в режим пошагового выполнения по достижении точки останова в Delphi (в рамке указано значение переменной «под курсором»

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

Аналогично поступают при «зависании» компьютера.

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

Для уточнения природы ошибки возможен анализ машинных кодов, флагов и представления программы и значений памяти в 16-ричном виде (рис. 12).

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

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

2.4. Общая методика отладки программного обеспечения

Суммируя все сказанное выше, можно предложить следующую методику отладки программного обеспечения, написанного на универсальных языках программирования для выполнения в операционных системах MS DOS и Win32:

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

Если ошибка не найдена или система просто «зависла», переходят ко второму этапу.

Рис. 12. Вид экрана при отладке программы в 16-ричном коде (режим CPU)

2 этап – локализация ошибки – определение конкретного фрагмента, при выполнении которого произошло отклонение от предполагаемого вычислительного процесса. Локализация может выполняться:

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

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

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

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

4 этап – исправление ошибки – внесение соответствующих изменений во все операторы, совместное выполнение которых привело к ошибке.

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

Следует иметь в виду, что процесс отладки можно существенно упростить, если следовать основным рекомендациям структурного подхода к программированию:

  •  программу наращивать «сверху-вниз», от интерфейса к обрабатывающим подпрограммам, тестируя ее по ходу добавления подпрограмм;
  •  выводить пользователю вводимые им данные для контроля и проверять их на допустимость сразу после ввода;
  •  предусматривать вывод основных данных во всех узловых точках алгоритма (ветвлениях, вызовах подпрограмм).

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

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

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

Выводы

  1.  Тестирование – это процесс выполнения программы, целью которого является выявление ошибок. Процесс разработки программного обеспечения предполагает три стадии тестирования: автономное тестирование компонентов программного обеспечения; комплексное тестирование разрабатываемого программного обеспечения; системное или оценочное тестирование на соответствие основным критериям качества.
  2.  Существуют два принципиально различных подхода к формированию тестовых наборов: структурный – базируется на том, что известна структура тестируемого программного обеспечения и тесты строят так, чтобы проверить правильность реализации заданной логики в коде программы, и функциональный – основывается на том, что структура программного обеспечения не известна («черный ящик») и тесты строят, опираясь на функциональные спецификации.
  3.  Основными методами ручного контроля являются: инспекции исходного текста, сквозные просмотры, проверка за столом, оценки программ.
  4.  Формирование тестовых наборов для тестирования маршрутов может осуществляться по нескольким критериям: покрытие операторов, покрытие решений (переходов), покрытие условий, покрытие решений/условий, комбинаторное покрытие условий.
  5.  При функциональном тестировании различают следующие методы формирования тестовых наборов: эквивалентное разбиение, анализ граничных значений, анализ причинно-следственных связей, предположение об ошибке.
  6.  Отладка – это процесс локализации и исправления ошибок, обнаруженных при тестировании программного обеспечения. Локализацией называют процесс определения оператора программы, выполнение которого вызвало нарушение нормального вычислительного процесса. Для исправления ошибки необходимо определить ее причину, т.е. определить оператор или фрагмент, содержащие ошибку.
  7.  В соответствии с этапом обработки программы, на котором проявляются ошибки, различают: синтаксические ошибки (ошибки компиляции) – ошибки, фиксируемые компилятором (транслятором, интерпретатором) при выполнении синтаксического и частично семантического анализа программы; ошибки компоновки – ошибки, обнаруженные компоновщиком (редактором связей) при объединении модулей программы; ошибки выполнения – ошибки, обнаруженные операционной системой, аппаратными средствами или пользователем при выполнении программы.
  8.  Отладка программы предполагает обдумывание и логическое осмысление всей имеющейся информации об ошибке. Большинство ошибок можно обнаружить по косвенным признакам посредством тщательного анализа текстов программ и результатов тестирования без получения дополнительной информации. При этом используют различные методы: ручного тестирования, индукции, дедукции, обратного прослеживания.

Контрольные вопросы и задания

  1.  Что является целью тестирования программ? Почему?
  2.  Перечислите известные вам виды контроля качества программного обеспечения. На каких этапах применяют каждый их них?
  3.  Какие подходы к тестированию вы знаете? В чем они заключаются?
  4.  Почему функциональное тестирование называют «тестированием по методу черного ящика»? Перечислите методы функционального тестирования и определите, в каких случаях следует использовать каждый из них.
  5.  Почему структурное тестирование называют «тестированием по методу белого или прозрачного ящика»? Перечислите методы структурного тестирования и определите возможности каждого из них. Какой метод структурного тестирования обеспечивает наибольшую вероятность обнаружения ошибок?
  6.  Чем нисходящее тестирование отличается от восходящего? Что понимают под комплексным тестированием и чем оно отличается от тестирования компонент? Когда можно прекращать тестирование компонентов?
  7.  Перечислите виды тестирования системы в целом. В каких случаях применяют каждый из них?
  8.  Какой процесс называют отладкой? В чем его сложность?
  9.  Назовите основные типы ошибок. Как они проявляются при выполнении программы?
  10.  Перечислите основные методы отладки. В чем заключается различие между ними?
  11.  Какие средства получения дополнительной информации об ошибках вы знаете? В каких случаях дополнительная информация позволяет найти ошибку?


 

А также другие работы, которые могут Вас заинтересовать

6653. Миотонии - гетерогенная группа нервно-мышечных заболеваний 25.78 KB
  Миотонии Миотонии -гетерогенная группа нервно-мышечных заболеваний, объединенная общим характерным клиническим комплексом нарушений мышечного тонуса, проявляющимся затруднением расслабления мышц после активного сокращения. Причины развития мио...
6654. Наследственные атаксии 20.32 KB
  Наследственные атаксии Нарушение статики и целенаправленных движений в связи с расстройством согласованности работы мышц агонистов и антагонистов и проявляющееся дисметрией и несоразмерностью движений называется атаксией. Для поддержания ходьбы и ра...
6655. Аутосомно-доминантные атаксии 24.76 KB
  Аутосомно-доминантные атаксии К этой группе относятся разнообразные атактические синдромы, наследующиеся по аутосомно-доминантному типу и манифестирующие обычно в зрелом возрасте (как правило, после 20-30 лет). В абсолютном большинстве случаев эта г...
6656. Врожденные наследственные мозжечковые атаксии 20.64 KB
  Врожденные наследственные мозжечковые атаксии Данная группа наследственных атактических заболеваний характеризуется генетически обусловленным нарушением нормального развития и дифференцировки различных частей мозжечка и, в частности, отдельных клето...
6657. Болезнь Фридрейха 26.2 KB
  Болезнь Фридрейха В 1862 году N. Friedreich описал болезнь, впоследствии получившую его имя. Болезнь Фридрейха (или атаксия Фридрейха) - заболевание с аутосомно-рецессивным типом наследования, является частой формой наследственных атаксий: расп...
6658. Атаксия вследствие дефицита витамина Е 18.67 KB
  Атаксия вследствие дефицита витамина Е Это - редкое заболевание, которое наблюдается главным образом в странах Средиземноморского региона. Ее развитие обусловлено генетическим дефектом, расположенным на длинном плече 8ой хромосомы (локус 8q13). Клин...
6659. Х-сцепленные рецессивные атаксии 22.03 KB
  Х-сцепленные рецессивные атаксии В соответствии с Х сцепленным рецессивным наследованием заболевание развивается только у лиц мужского пола - носителей единственной копии Х хромосомы, у женщин - гетерозиготных носительниц мутации заболеван...
6660. Генетика рассеянного склероза 20.73 KB
  Генетика рассеянного склероза Участие генетических факторов в предрасположенности к развитию рассеянного склероза (РС) и формированию особенностей клинической картины не вызывает сомнения. Этиология рассеянного склероза продолжает является областью ...
6661. Основные методы исследований генетики РС, как заболевания с мультигенной предрасположенностью 24.3 KB
  Основные методы исследований генетики РС, как заболевания с мультигенной предрасположенностью. Для идентификации генов, определяющих генетическую предрасположенность к РС как к мультигенному заболеванию, применяют два основных типа анализа - популяц...