Функциональное тестирование Технология разработки программного обеспечения Методические указания к выполнению лабораторной работы №6 «Методы тестирования» Факультет электроэнергетический Специальности: 220201 – информатика и управление в технических системах 230105 – программное обеспечение вычислительной техники и автоматизированных систем Вологда 2008 УДК 681.3.06 Технология разработки программного обеспечения: Методические указания к выполнению лабораторной работы №6 «Методы тестирования». - Вологда, ВоГТУ, 2008. - 31 с. Методические указания содержат описание и методику выполнения лабораторной работы по указанной дисциплине. Для лабораторной работы указывается цель работы, приводится необходимый теоретический материал, методика проведения экспериментов. Имеются также варианты заданий и контрольные вопросы по теме работы. Утверждено редакционно – издательским советом ВоГТУ. Составитель: Сергушичева А.П., доц. каф. АТПП. Рецензент: Водовозов А.М., зав.кафедрой УВС канд.техн. наук, доц. Лабораторная работа №6 Методы тестирования Введение Тестирование – важный и трудоемкий процесс в разработке программного обеспечения. Оно должно выявить подавляющее большинство ошибок, допущенных при составлении программ. Выделяют три стадии тестирования: автономное, комплексное и системное. Основными подходами к формированию тестов являются структурный и функциональный. Каждый из указанных подходов имеет свои особенности и области применения. Лабораторная работа предназначена для формирования у студентов навыков тестирования программных продуктов и направлена на изучение методов тестирования и способов формирования тестовых данных. В качестве практического задания студентам предлагается сформировать тестовые данные для конкретных программ, а также протестировать предложенные программные продукты различными методами. Для большей наглядности лабораторная работа снабжена примерами выполнения заданий. За помощь в подготовке данной лабораторной работы хочется выразить благодарность студентке группы ЭПО-51 2008г. выпуска Давыдовой Ольге. 1. Цель работы Цель работы: изучение методов тестирования и способов формирования тестовых наборов, приобретение практических навыков по тестированию программных продуктов. Продолжительность работы - 4 часа. 2. Основные теоретические положения 2.1. Тестирование. Задачи. Стратегии Тестирование программных средств (ПС) - это процесс выполнения программ или иная деятельность с программой и программными документами с целью обнаружения и исправления ошибок или аттестации ПС. При отладке ПС локализуются и устраняются, в основном, те ошибки, наличие которых в ПС устанавливается при тестировании. Исчерпывающее тестирование, как правило, невозможно, а тестированием ПС практически выполнимым набором тестов нельзя установить наличие всех имеющихся в ПС ошибок, т.е. тестирование не может доказать правильность ПС, в лучшем случае оно может продемонстрировать наличие или отсутствие определенных ошибок в программном обеспечении. Поэтому возникает задача подготовки такого набора тестов, чтобы при применении их к ПС обнаружить по возможности большее число ошибок. Тестом или тестовым набором называется набор данных, для которого заранее известен результат применения или известны правила поведения программ. В соответствии с определением тестирования, удачным следует считать тест, который обнаруживает хотя бы одну ошибку. Другой задачей, которая решается при организации тестирования является определение момента его окончания, т.к. чем дольше продолжается процесс тестирования (и отладки в целом), тем большей становится стоимость ПС. Признаком возможности завершения отладки является полнота охвата тестами, пропущенными через ПС, множества различных ситуаций, возникающих при выполнении программ ПС, и относительно редкое проявление ошибок в ПС на последнем отрезке процесса тестирования. Последнее определяется в соответствии с требуемой степенью надежности ПС, указанной в спецификации его качества. Проектирование тестов можно начинать сразу после завершения этапа внешнего описания ПС. Выделяют две стратегии проектирования тестов. Первый подход заключается в создании тестов на основании изучения спецификаций ПС (внешнего описания, описания архитектуры и спецификации модулей). Строение модулей при этом не учитывается, т.е. они рассматриваются как черные ящики. Данный подход называют стратегией "черного ящика" (функциональным тестированием, подходом, управляемым данными). При втором подходе тесты проектируются на основании анализа текстов программ с целью охвата ими всех путей выполнения программ ПС (стратегия "белого (прозрачного, стеклянного) ящика" или структурное тестирование). В этом случае проверяют правильность реализации заданной логики в коде программы. Если принять во внимание большое число возможных комбинаций входных данных и наличие в программах циклов с переменным числом повторений (большое число различных путей выполнения программ), то становится очевидной невозможность исчерпывающего тестирования ПС. Наборы тестов, полученные в соответствии с методами этих подходов, обычно объединяют, обеспечивая всестороннее тестирование программного обеспечения. 2.2. Методы ручного контроля программного обеспечения Ручной контроль обычно используют на ранних этапах разработки, т.к. возможность практической проверки подобных решений в этот период отсутствует, большое значение имеет их обсуждение, которое проводят в разных формах. Основными методами ручного контроля являются: инспекции исходного текста, сквозные просмотры, проверка за столом, оценка программ. Инспекции исходного текста представляют собой набор процедур и приемов обнаружения ошибок при изучении текста группой специалистов. В эту группу входят: автор программы, проектировщик, специалист по тестированию и координатор - компетентный программист, но не автор программы. Общая процедура инспекции предполагает следующие операции: • участникам группы заранее выдается листинг программы и спецификация на нее; • программист рассказывает о логике работы программы и отвечает на вопросы инспекторов; • программа анализируется по списку вопросов для выявления исторически сложившихся общих ошибок программирования. Список вопросов для инспекций исходного текста зависит, как от используемого языка программирования, так и от специфики разрабатываемого программного обеспечения. Пример списка вопросов, который можно использовать при анализе правильности программ, написанных на языке Pascal, приведен в приложении А. Кроме непосредственного обнаружения ошибок, результаты инспекции позволяют программисту увидеть другие сделанные им ошибки, получить возможность оценить свой стиль программирования, выбор алгоритмов и методов тестирования. Инспекция является способом раннего выявления частей программы, с большей вероятностью содержащих ошибки, что позволяет при тестировании уделить внимание именно этим частям. Сквозные просмотры осуществляются группой специалистов при изучении листинга программы и спецификации на нее на группе тестов. Участники заседания мысленно выполняют каждый тест в соответствии с логикой программы. При этом состояние программы (значения переменных) отслеживается на бумаге (доске). Проверка за столом осуществляется тестировщиком (не автором программы), который проверяет текст программы по списку часто встречающихся ошибок и "пропускает" через программу тестовые данные. Метод оценки программ направлен на повышение качества ПС и для поиска ошибок не применяется. Поэтому в данной лабораторной работе он не рассматривается. Пример: язык паскаль, текст программы: Program MenuDemo; Uses MenuUnit, Crt; Var Choice: Integer; M: BBMenu; BEGIN CLRSCR; M.Init( 25, 7, 18); M.AddPrompt('Open a New File') M.AddPrompt('Existing File'); M.AddPrompt('Close A File') M.AddPrompt('Quit'); Choice:= M.GetChoice; GotoXY(1, 24); ClrEol; Case Choice OF ‘0’: Writeln('No Choice Was Made'); ‘1’: Writeln('Procedure NewFile Should Be Called'); ‘2’: Writeln('Procedure ExistingFile Should Be Called'); ‘3’: Writeln('Procedure CloseFile Should Be Called'); ‘4’: Writeln('Quitting ...'); END; M.Done; END. Ошибка | Тип ошибки | Case Choice OF ‘0’: Writeln('No Choice Was Made'); ‘1’: Writeln('Procedure NewFile Should Be Called'); ‘2’: Writeln('Procedure ExistingFile Should Be Called'); ‘3’: Writeln('Procedure CloseFile Should Be Called'); ‘4’: Writeln('Quitting ...'); | 1) Некорректное сравнение переменных разных типов (integer и string) 2) Некорректное использование оператора case(не применяется к строкам). | M.AddPrompt('Close A File') | Отсутствует «;» после оператора | 2.3. Структурное тестирование Структурное тестирование называют также тестированием по «маршрутам», так как в этом случае тестовые наборы формируют путем анализа маршрутов, предусмотренных алгоритмом. Под маршрутами при этом понимают последовательности операторов программы, которые выполняются при конкретном варианте исходных данных. В основе структурного тестирования лежит концепция максимально полного тестирования всех маршрутов программы. Так, если алгоритм программы включает ветвление, то при одном наборе исходных данных может быть выполнена последовательность операторов, реализующая действия, которые предусматривает одна ветвь, а при втором - другая. Соответственно, для программы будут существовать маршруты, различающиеся выбранным при ветвлении вариантом. Считают, что программа проверена полностью, если с помощью тестов удается осуществить выполнение программы по всем возможным маршрутам передач управления. Однако нетрудно видеть, что даже в программе среднего уровня сложности число неповторяющихся маршрутов может быть очень велико, и, следовательно, полное или исчерпывающее тестирование маршрутов, как правило, невозможно. Формирование тестовых наборов для тестирования маршрутов может осуществляться по нескольким критериям: - покрытие операторов. Критерий покрытия операторов подразумевает такой подбор тестов, чтобы каждый оператор программы выполнялся, по крайней мере, один раз. Это необходимое, но недостаточное условие для приемлемого тестирования.; - покрытие решений (переходов); Для реализации этого критерия необходимо такое количество и состав тестов, чтобы результат проверки каждого условия (т.е. решение) принимал значения «истина» или «ложь», по крайней мере, один раз. Нетрудно видеть, что критерий покрытия решений удовлетворяет критерию покрытия операторов, но является более «сильным». - покрытие условий; Критерий покрытия условий является еще более «сильным» по сравнению с предыдущими. В этом случае формируют некоторое количество тестов, достаточное для того, чтобы все возможные результаты каждого условия в решении были выполнены, по крайней мере, один раз. Однако, как и в случае покрытия решений, этот критерий не всегда приводит к выполнению каждого оператора, по крайней мере, один раз. К критерию требуется дополнение, заключающееся в том, что каждой точке входа управление должно быть передано, по крайней мере, один раз. - покрытие решений/условий Согласно этому методу тесты должны составляться так, чтобы, по крайней мере, один раз выполнились все возможные результаты каждого условия и все результаты каждого решения, и каждому оператору управление передавалось, по крайней мере, один раз. - комбинаторное покрытие условий. Этот критерий требует создания такого множества тестов, чтобы все возможные комбинации результатов условий в каждом решении и все операторы выполнялись, по крайней мере, один раз. Пример. Требуется выполнить структурное тестирование текста программы, которая определяет значение х в зависимости от значений параметров процедуры. Procedure т (a, b:real; var x:real); begin if(a>l) and (b=0) then x:=x /a; if(a=2) or (x>l) then x:=x+1; end; Для формирования тестов программу представляют в виде графа, вершины которого соответствуют операторам программы, а дуги представляют возможные варианты передачи управления (рис.1).  Рис. 1 - Схема алгоритма процедуры (слева) и ее граф передач управления (6). Покрытие операторов будет реализовано при а = 2, b = 0, х = 3. Однако, хотя исходные данные заданы так, чтобы все операторы программы были выполнены хотя бы один раз, для проверки программы этого явно недостаточно. Например, из второго условия следует, что переменная х может принимать любое значение, и в некоторых версиях языка Pascal это значение проверяться не будет. Кроме того, если при написании программы в первом условии указано, что (а > 1) or (b = 0), или, если во втором условии вместо х > 1 записано х > 0, то эти ошибки обнаружены не будут. Также существует путь 1-2-4-6, в котором х вообще не меняется и, если здесь есть ошибка, она не будет обнаружена. По методу покрытия решений (переходов)рассматриваемую программуможно протестировать двумя тестами, покрывающими либо пути: 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-5-6 Однако путь, где х не меняется, будет проверен с вероятностью 50 %: если во втором условии вместо х > 1 записано х < 1, то этими двумя тестами ошибка обнаружена не будет. Покрытие условийпроверяет четыре условия: l)a>l; 2)b = 0; 3)а = 2; 4)х>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,Ь = 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, Ь = 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)а>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,Ь=1,х=1— проверяет комбинации (4), (8). В данном случае то, что четырем тестам соответствует четыре пути, является совпадением. Представленные тесты не покрывают всех путей, например, acd. Поэтому иногда необходима реализация восьми тестов. Таким образом, для программ, содержащих только одно условие на каждое решение, минимальным является набор тестов, который проверяет все результаты каждого решения и передает управление каждому оператору, по крайней мере, один раз. Для программ, содержащих вычисления, каждое из которых требует проверки более чем одного условия, минимальный набор тестов должен: • генерировать все возможные комбинации результатов проверок условий для каждого вычисления; • передавать управление каждому оператору, по крайней мере, один раз. Термин «возможных» употреблен здесь потому, что некоторые комбинации условий могут быть нереализуемы. Например, для комбинации k<0 и k>40 задать k невозможно. Функциональное тестирование Одним из способов проверки программ является тестирование с управлением по данным или по принципу «черного ящика». В этом случае программа рассматривается как «черный ящик», и целью тестирования является выяснение обстоятельств, в которых поведение программы не соответствует спецификации. Для обнаружения всех ошибок в программе, используя управление по данным, необходимо выполнить исчерпывающее тестирование, т. е. тестирование на всех возможных наборах данных. Для тех же программ, где исполнение команды зависит от предшествующих ей событий, необходимо проверить и все возможные последовательности. Очевидно, что проведение исчерпывающего тестирования для подавляющего большинства случаев невозможно. Поэтому обычно выполняют «разумное» или «приемлемое» тестирование, которое ограничивается прогонами программы на небольшом подмножестве всех возможных входных данных. Этот вариант не дает гарантии отсутствия отклонений от спецификаций. Правильно выбранный тест должен уменьшать, причем более чем на единицу, число других тестов, которые должны быть разработаны для обеспечения требуемого качества программного обеспечения. При функциональном тестировании различают следующие методы формирования тестовых наборов: • эквивалентное разбиение; • анализ граничных значений; • анализ причинно-следственных связей; • предположение об ошибке. Эквивалентное разбиение.Метод эквивалентного разбиения заключается в следующем. Область всех возможных наборов входных данных программы по каждому параметру разбивают на конечное число групп - классов эквивалентности. Наборы данных такого класса объединяют по принципу обнаружения одних и тех же ошибок: если набор какого-либо класса обнаруживает некоторую ошибку, то предполагается, что все другие тесты этого класса эквивалентности тоже обнаружат эту ошибку и наоборот. Разработку тестов методом эквивалентного разбиения осуществляют в два этапа: на первом выделяют классы эквивалентности, а на втором - формируют тесты. Выделение классов эквивалентности является эвристическим процессом, однако целесообразным считают выделять в отдельные классы эквивалентности наборы, содержащие допустимые и недопустимые значения некоторого параметра. При этом существует ряд правил: • если некоторый параметр х может принимать значения в интервале [1, 999], то выделяют один правильный класс 1 < х < 999 и два неправильных: х < 1 и х > 999; • если входное условие определяет диапазон значений порядкового типа, например, «в автомобиле могут ехать от одного до шести человек», то определяется один правильный класс эквивалентности и два неправильных: ни одного и более шести человек; • если входное условие описывает множество входных значений и есть основания полагать, что каждое значение программист трактует особо, на пример, «типы графических файлов: bmp, jpeg, vsd», то определяют правильный класс эквивалентности для каждого значения и один неправильный класс, например, txt; • если входное условие описывает ситуацию «должно быть», например, «первым символом идентификатора должна быть буква», то определяется один правильный класс эквивалентности (первый символ - буква) и один неправильный (первый символ - не буква); • если есть основание считать, что различные элементы класса эквивалентности трактуются программой неодинаково, то данный класс разбивается на меньшие классы эквивалентности. Таким образом, классы эквивалентности выделяют, перебирая ограничения, установленные для каждого входного значения в техническом задании или при уточнении спецификации. Каждое ограничение разбивают на две или более групп. При этом используют специальные бланки - таблицы классов эквивалентности: Ограничение на значение параметра | Правильные классы эквивалентности | Неправильные классы эквивалентности | | | | Правильные классы включают правильные данные, неправильные классы - неправильные данные. Для правильных и неправильных классов тесты проектируют отдельно. При построении тестов правильных классов учитывают, что каждый тест должен проверять по возможности максимальное количество различных входных условий. Такой подход позволяет минимизировать общее число необходимых тестов. Для каждого неправильного класса эквивалентности формируют свой тест. Последнее обусловлено тем, что определенные проверки с ошибочными входами скрывают или заменяют другие проверки с ошибочными входами. Анализ граничных значений.Граничные значения - это значения на границах классов эквивалентности входных значений или около них. Анализ показывает, что в этих местах резко увеличивается возможность обнаружения ошибок. Например, если в программе анализа вида треугольника было записано А + В > С вместо А + В > С, то задание граничных значений приведет к ошибке: линия будет отнесена к одному из видов треугольника. Применение метода анализа граничных значений требует определенной степени творчества и специализации в рассматриваемой проблеме. Тем не менее, существует несколько общих правил для применения этого метода: • если входное условие описывает область значений, то следует построить тесты для границ области и тесты с неправильными входными данными для ситуаций незначительного выхода за границы области, например, если описана область [-1.0, +1.0], то должны быть сгенерированы тесты: -1.0, +1.0,-1.001 и +1.001; • если входное условие удовлетворяет дискретному ряду значений, то следует построить тесты для минимального и максимального значений и тесты, содержащие значения большие и меньшие этих двух значений, напри мер, если входной файл может содержать от 1 до 255 записей, то следует проверить О, 1, 255 и 256 записей; • если существуют ограничения выходных значений, то целесообразно аналогично тестировать и их: конечно не всегда можно получить результат вне выходной области, но тем не менее стоит рассмотреть эту возможность; • если некоторое входное или выходное значение программы является упорядоченным множеством, например, это последовательный файл, линейный список или таблица, то следует сосредоточить внимание на первом и последнем элементах этого множества. Помимо указанных граничных значений, целесообразно поискать другие. Анализ граничных значений, если он применен правильно, является одним из наиболее полезных методов проектирования тестов. Однако следует помнить, что граничные значения могут быть едва уловимы и определение их связано с большими трудностями, что является недостатком этого метода. Оба описанных метода основаны на исследовании входных данных. Они не позволяют проверять результаты, получаемые при различных сочетаниях данных. Для построения тестов, проверяющих сочетания данных, применяют методы, использующие булеву алгебру. Анализ причинно-следствениых связей.Анализ причинно-следственных связей позволяет системно выбирать высокорезультативные тесты. Метод использует алгебру логики и оперирует понятиями «причина» и «следствие». Причиной в данном случае называют отдельное входное условие или класс эквивалентности. Следствием - выходное условие или преобразование системы. Идея метода заключается в отнесении всех следствий к причинам, т. е. в уточнении причинно-следственных связей. Данный метод дает полезный побочный эффект, позволяя обнаруживать неполноту и неоднозначность исходных спецификаций. Построение тестов осуществляют в несколько этапов. Сначала, поскольку таблицы причинно-следственных связей при применении метода к большим спецификациям становятся громоздкими, спецификации разбивают на «рабочие» участки, стараясь по возможности выделять в отдельные таблицы независимые группы причинно-следственных связей. Затем в спецификации определяют множество причин и следствий. Далее на основе анализа семантического (смыслового) содержания спецификации строят таблицу истинности, в которой каждой возможной комбинации причин ставится в соответствие следствие. При этом целесообразно истину обозначать «1», ложь - «О», а для обозначения безразличных состояний условий применять обозначение «X», которое предполагает произвольное значение условия (0 или 1). Таблицу сопровождают примечаниями, задающими ограничения и описывающими комбинации причин и/или следствий, которые являются невозможными из-за синтаксических или внешних ограничений. При необходимости аналогично строится таблица истинности для класса эквивалентности. И, наконец, каждую строку таблицы преобразуют в тест. При этом рекомендуется по возможности совмещать тесты из независимых таблиц. Данный метод позволяет строить высокорезультативные тесты и обнаруживать неполноту и неоднозначность исходных спецификаций. Его недостатком является неадекватное исследование граничных значений. Предположение об ошибке.Часто программист с большим опытом находит ошибки, «не применяя никаких методов». На самом деле он подсознательно использует метод «предположение об ошибке». Процедура метода предположения об ошибке в значительной степени основана на интуиции. Основная его идея заключается в том, чтобы перечислить в некотором списке возможные ошибки или ситуации, в которых они могут появиться, а затем на основе этого списка составить тесты. Другими словами, требуется перечислить те особые случаи, которые могут быть не учтены при проектировании. Проиллюстрируем применение всех рассмотренных выше методов на примере. Пример.Пусть необходимо выполнить тестирование программы, определяющей точку пересечения двух прямых на плоскости. При этом она должна определять параллельность прямой одной из осей координат. В основе программы лежит решение системы линейных уравнений: Ах + By = С, Dx + Еу = F. По методу эквивалентных разбиений формируем для каждого коэффициента один правильный класс эквивалентности (коэффициент-вещественное число) и один неправильный (коэффициент - не вещественное число). Откуда генерируем 7 тестов: 1) все коэффициенты - вещественные числа (1 тест); 2-7) поочередно каждый из коэффициентов - не вещественное число (6 тестов). По методу граничных значений можно считать, что для исходных данных граничные значения отсутствуют, т. е. коэффициенты - «любые» вещественные числа. Для результатов получаем, что возможны варианты: единственное решение, прямые сливаются - множество решений, прямые параллельны - отсутствие решений. Следовательно, целесообразно предложить тесты с результатами внутри областей возможных значений результатов: 8) результат - единственное решение (δ ≠0); 9) результат - множество решений (δ = 0 и δ Х = δ у = 0); 10) результат - отсутствие решений (δ = 0, но δ Х ≠ 0 или δ у ≠0); и с результатами на границе: 11) δ = 0,01; 12) δ = -0,01; 13) δ = 0, δ Х = 0,01, δ =0; 14) δ = 0, δ у =-0,01, δ Х = 0. По методу анализа причинно-следственных связей определяем множество условий: а) для определения типа прямой: - для определения типа и существования первой прямой; - для определения точки пересечения Выделяем три группы причинно-следственных связей (определение типа и существования первой линии, определение типа и существования второй линии, определение точки пересечения) и строим таблицы истинности для определения типа первой прямой (табл. 4.1) и для определения результата (табл. 4.2). В обеих таблицах X означает неопределенное значение. Для второй прямой таблица истинности будет выглядеть аналогично табл. 4.1. Каждая строка этих таблиц преобразуется в тест. При возможности (с учетом независимости групп) берутся данные, соответствующие строкам сразу двух или всех трех таблиц. Таблица 4.1 А=0 | в=о | с=о | Результат | | | X | прямая общего положения | | | | прямая, параллельная оси ОХ | | | | ось ОХ | | | | прямая, параллельная оси ОУ | | | | ось ОУ | | | X | множество точек плоскости | Таблица 4.2 δ = 0 | δ Х = 0 | δ у = 0 | Единственное решение | Множество решений | Решения нет | | X | Х 1 | | | | | | X | | | | | X | | | | | | | | | 1 0 | | | | | | | | | В результате к уже имеющимся тестам добавляются: 15-21) проверки всех случаев расположения обеих прямых - 6 тестов по первой прямой совмещают с 6-ю тестами по второй прямой так, чтобы варианты не совпадали (6 тестов); 22) проверка несовпадения условия 8Х = 0 или 8у = 0 (в зависимости от того, какой тест был выбран по методу граничных условий) - тест также можно совместить с предыдущими 6-ю тестами. По методу предположения об ошибке добавим тест: 23) все коэффициенты - нули. Всего получили 23 теста по всем четырем методам. Для каждого теста перед применением необходимо указать ожидаемый результат. Если попробовать вложить независимые проверки, то, возможно, число тестов можно еще сократить. Тестирование модулей При тестировании модулей программного обеспечения, так же, как при проектировании и кодировании возможно применение как восходящего, так и нисходящего подходов. Восходящее тестирование.Восходящий подход предполагает, что каждый модуль тестируют отдельно на соответствие имеющимся спецификациям на него, затем собирают оттестированные модули в модули более высокой степени интеграции и тестируют их. При этом проверяют межмодульные интерфейсы, используемые для подключения модулей более низкого уровня иерархии. И так далее, пока не будет собран весь программный продукт (рис. 5.1). Такой подход обеспечивает полностью автономное тестирование, для которого просто генерировать тестовые последовательности, которые передаются в модуль напрямую. Однако он имеет и существенные недостатки. Во-первых, при восходящем тестировании так же, как при восходящем проектировании, серьезные ошибки в спецификациях, алгоритмах и интерфейсе могут быть обнаружены только на завершающей стадии работы над проектом. Во-вторых, для того, чтобы тестировать модули нижних уровней, необходимо разработать специальные тестирующие программы, которые обеспечивают вызов интересующих нас модулей с необходимыми параметрами. Причем эти тестирующие программы также могут содержать ошибки. Рисунок 5.1. - Тестирование программного обеспечения при восходящем подходе: а - автономное тестирование модулей нижнего уровня; б - тестирование следующего уровня Нисходящее тестирование.Нисходящее тестирование органически связано с нисходящим проектированием и разработкой: как только проектирование какого-либо модуля заканчивается, его кодируют и передают на тестирование. В этом случае автономно тестируется только основной модуль. При его тестировании все вызываемые им модули заменяют модулями, которые в той или иной степени имитируют поведение вызываемых модулей (рис. 5.2). Такие модули принято называть «заглушками». В отличие от тестирующих программ заглушки очень просты, например, они могут просто фиксировать, что им передано управление. Часто заглушки просто возвращают какие-либо фиксированные данные. Как только тестирование основного модуля завершено, к нему подключают модули, непосредственно им вызываемые, и необходимые заглушки, а затем проводят их совместное тестирование. Далее последовательно подключают следующие модули, пока не будет собрана вся система. Основной недостаток нисходящего тестирования - отсутствие автономного тестирования модулей. Поскольку модуль получает данные не непосредственно, а через вызывающий модуль, то гораздо сложнее обеспечить его «достаточное» тестирование. Основным достоинством данного метода является ранняя проверка основных решений и качественное многократное тестирование сопряжения модулей в контексте программного обеспечения. При нисходящем тестировании есть возможность согласования с заказчиком внешнего вида (интерфейса) программного обеспечения.   Рисунок 5.2. -Начальные этапы тестирования: а - основного модуля; б - двух модулей Комбинированный подход.Чаще всего применяют комбинированный подход: модули верхних уровней тестируют нисходящим способом, а модули нижних уровней - восходящим. Этот способ позволяет с одной стороны начать с тестирования интерфейса, с другой - обеспечивает качественное автономное тестирование модулей низших уровней. 3. Порядок выполнения работы 1. Ознакомиться с теоретической частью. 2. Применение ручного контроля. Проанализируйте заданный фрагмент программы (приложение Б) по списку вопросов для выявления исторически сложившихся общих ошибок программирования (приложение А). Укажите, к какому типу относятся найденные вами ошибки. Вариант задания получите у преподавателя. Пример решения аналогичного задания найдете в теоретической части 3. Изучение структурного тестирования. Сформируйте тестовые наборы для тестирования маршрутов фрагментов (приложение В) по заданным критериям. Проанализируйте целесообразность каждого из критериев для своей программы, укажите их недостатки, достоинства и преимущества над другими критериями. Пример выполнения аналогичного задания найдете в теоретической части. 4. Изучение функционального тестирования. Получите свой вариант у преподавателя. Запустите программу "Геометрические фигуры" (файл geometry.exe), руководство пользователя на программу находится в приложении Г. Нажмите на кнопку с названием своего варианта. Для своей программы сформируйте наборы тестов методами: - эквивалентного разбиения; - анализа граничных значений; - анализа причинно-следственных связей. Протестируйте программу на полученных тестовых наборах. Результаты тестирования оформите в таблицу: Пример решения аналогичного задания смотрите в теоретической части. Таблица 3.1 - Результаты тестирования Тест | Ожидаемый результат | Полученный результат | | | | 5. Тестирование модулей. Для изучения нисходящего и восходящего тестирования предлагаются с программы: «Калькулятор» и «Обучающая программа», состаящие из нескольких модулей. Структуры этих программ представлены на рисунках 4.1 и 4.2. в приложении Д. Возьмите у преподавателя вариант задания на тестирование одной из веток программы. Протестируйте каждый модуль ветки и оформьте результаты тестирования в таблицу. Также сформулируйте предложения по улучшению работы модулей. Программа: «Калькулятор» и все составляющие ее модули находятся в папке «Калькулятор». «Обучающая программа» и все составляющие ее модули находятся в папке «Обучающая программа». 6. Составить отчет по выполненной работе. Приложение А Пример списка вопросов, который можно использовать при анализе правильности программ, написанных на языке Pascal, приведен в 1. Контроль обращений к данным • Все ли переменные инициализированы? • Не превышены ли максимальные (или реальные) размеры массивов и строк? • Не перепутаны ли строки со столбцами при работе с матрицами? • Присутствуют ли переменные со сходными именами? • Используются ли файлы? Если да, то при вводе из файла проверяется ли завершение файла? • Соответствуют ли типы записываемых и читаемых значений? • Использованы ли нетипизированные переменные, открытые массивы, динамическая память? Если да, то соответствуют ли типы переменных при «наложении» формата? Не выходят ли индексы за границы массивов? 2. Контроль вычислений • Правильно ли записаны выражения (порядок следования операторов)? • Корректно ли выполнены вычисления над неарифметическими переменными? • Корректно ли выполнены вычисления с переменными различных типов (в том числе с использованием целочисленной арифметики)? • Возможно ли переполнение разрядной сетки или ситуация машинного нуля? • Соответствуют ли вычисления заданным требованиям точности? • Присутствуют ли сравнения переменных различных типов? 3. Контроль передачи управления • Будут ли корректно завершены циклы? • Будет ли завершена программа? • Существуют ли циклы, которые не будут выполняться из-за нарушения условия входа? Корректно ли продолжатся вычисления? • Существуют ли поисковые циклы? Корректно ли отрабатываются ситуации «элемент найден» и «элемент не найден»? 4. Контроль межмодульных интерфейсов • Соответствуют ли списки параметров и аргументов по порядку, типу, единицам измерения? • Не изменяет ли подпрограмма аргументов, которые не должны изменяться? • Не происходит ли нарушения области действия глобальных и локальных переменных с одинаковыми именами? Приложение Б Варианты заданий для ручного тестирования Вариант | Язык | Текст программы | | Паскаль | unit NewParam; interface function ParamCount(): Word; function ParamStr (Index: Integer): string; function GetParamsStarting (Index: Integer): string; implementation uses Objects, TPString; var CommandLine: PString; Params: array [Byte] of record L, R: Byte; end; LocParamCount: Byte; function ParamCount; begin ParamCount := LocParamCount; end; function ParamStr; begin if (index = =0) then ParamStr := System.ParamStr (0) else with Params [Index-1] do ParamStr := Copy (CommandLine^, L, R-L); end; function GetParamsStarting; begin with Params [Index-1] do GetParamsStarting := Copy (CommandLine^, L, $FF); end; const ParamDelims = [' ', #9]; Quotes = ['"']; var WaitForQuote: Boolean; B: Byte; begin CommandLine := Ptr (PrefixSeg, $80); LocParamCount := 0; B := 0; while B <= Length (CommandLine^) do begin Inc (B); if CommandLine^ [B] in ParamDelims then Continue; with Params [LocParamCount] do begin WaitForQuote := CommandLine^ [B] in Quotes; if WaitForQuote then Inc (B) W:= B; while not ( (B > Length (CommandLine^)) or WaitForQuote and (CommandLine^ [B] in Quotes) or not WaitForQuote and (CommandLine^ [B] in ParamDelims)) do Inc (B); R := B; if WaitForQuote then Inc (B); end; Inc (LocParamCount); end; end. | | Паскаль | program var1; const x=12; var a : integer; b: byte; c: char; arr: array [0..50] of real; f: file of char; begin readln(a,b); if (c=x) begin a:=a+1; c:=c-“123”; end; for а:=0 to 100 do begin arr[b]=sqrt(b); b:=b*3; a:=-b/2; end; assign(f, “test.txt”); {$I-}; reset(f); {$I+}; if (IOResult<>0) then writeln (файл f не существует); else erase(f); if (a>0) then return 1 else return –a; end; | | Паскаль | program var[2]; const сon=”222”; var a : integer; b: byte; c: char; b: boolean; arr: array [-10..10] of real; procedure Srednee (var x1, x2, x3: integer) begin if (x1=x2=x3=0) then sred:=0 else if (x2=x3=0) then sred:=x1; else if (x3=0) then c:=(x1+x2)/2 else sred =(x1+x2+x3)/3; return sred; end; begin b:=false; writeln (‘Введите значения’); readln(a,b); if (d>0) begin b:= Srednee (1,2,6); end; while not (b) do begin arr[d]:= arr[d]+4; d:=d+1; end; Srednee (d, arr[1], a); a:=sred; end; | | Паскаль | unit inifile; interface type string127 = string[127]; procedure getinistring(var inifile : text; name : string127; var value : string127); procedure getconfig(configfile : string; var value:array of string127); implementation procedure getinistring; var buf : string127; i, j : byte; begin value := 0; reset(inifile); while not seekeof(inifile) do begin readln(inifile, buf); if ((buf[1] <> ';') and (buf[1] <> '%')) then begin i := -1; while (buf[i] = ' ') or (buf[i] = #9) do inc(i); if (pos(name, buf) = i) then begin i := i+length(name); while (buf[i] = ' ') or (buf[i] = #9) do inc(i); j := 1; while ((buf[i+j] <> ';') and buf[i+j] <> '%')) and (j < ord(buf[0])-i+1) do inc(j); value := copy(buf, i, j); exit(); end; end; end; end; procedure getconfig; var config : text; I : integer; buf : pointer; begin i := ioresult; assign(config, configfile); getmem(buf, 5120); settextbuf(config, buf^, 5120); reset(config); i := ioresult; if i <> 0 then begin writeln('Error reading file: ', configfile); writeln('Program aborted'); halt(20); end; for j := low(value) to high(value) do getinistring(config, value[j], value[j]); close(config); freemem(buf, 5120); end; end. | | С++ | #include "stdafx.h" #include "iostream.h" int m,n; int* ostatki; int* period; unsigned int len; bool IsInOstatki(int ost,int len) { for(int i=0;i<len-1;i++)//len-1 чтоб не проверять остаток, ввденый на текущем шаге { if(ostatki[i+j]==ost)return i; } return 0; } int main(int argc, char* argv[]) { cout<<"Vvedite chislitel drobi\r\n"; cin>>m; cout<<"\r\nvvedite znamenatel drobi\r\n"; cin>>n; ostatki=new int[n+1]; period=new int[n+1]; int d=m,q=n; int r,pos; len=1; do { r=d%q ostatki[len]=r; d/=q; period[len]=d; d=10*r; len++; } while((pos=IsInOstatki(r,len)=0); cout<<"drob ravna\r\n"; cout<<period[1]<<","; for(r=2;r<len;r++) { cout<<period[r]; } cout<<"\r\n period raven "; for(r=pos+1;r<len;r++) cout<<period[r]; cout>>"\r\n"; delete[] ostatki; delete[] period; return 0; } | | С++ | #include "string.h" #include "math.h" #include "stdafx.h" #include "calc.h" #include "ctype.h" class calc { public: double proceed(); void SetFunction(char* lpszCommand); calc(char* lpszCommand); calc(); struct SyntaxError { const char* p; SyntaxError(const int* q){p=q;} разные типы }; struct MathError { const char* p; MathError(const char* q){p=q;} }; virtual ~calc(); private: Tok_Type CurrTok; const bool InSet=true; protected: char* pCurrPos; char TokenValue[16]; double add_sub(); char Function[255]; virtual double prim(); }; calc::calc() { memset(Function,0,256) pCurrPos=Function; } calc::~calc() { } calc::calc(char* lpszCommand) { SetFunction(lpszCommand); } void calc::SetFunction(char* lpszCommand) { strncpy(Function,lpszCommand,255); Function[257]=0; pCurrPos=Function; InSet=false; } | Приложение В Варианты заданий для структурного тестирования. Критерии тестирования маршрутов: 1. покрытия операторов; 2. покрытия решений (переходов); 3. покрытия условий; 4. покрытия решений/условий; 5. комбинаторного покрытия условий. Вариант 1. procedure m(a,b: real; var x: real) begin if (a>0)and(b<0) then x:=x+1; if ((a=2)or(x>3))and(b>-10) then x:=x-1; end; Вариант 2. procedure m(a,b,с: real; var x: real) begin if (a>0)and(b<0)and(x>6) then x:=x+1; if (a=4)or(c<0) then x:=x11; end; Вариант 3. procedure m(a,b: real; var x: real) begin if (a<=6)and(b<0) then x:=x+1; if (a=7) then x:=x-1 else if (x>3) x=x*2; end; Вариант 4. procedure m(a,b: real; var x: real) begin if (a>0) if(b<0) then x:=x+1 else x=x*2; if (a>2)or(x=0) then x:=x+1; end; Приложение Г программа "Геометрические фигуры" (файл geometry.exe) Руководство пользователя |