Методики нисходящего и восходящего тестирования (рисунок 7) отражают разные подходы к системной интеграции. При нисходящей интеграции компоненты высокого уровня интегрируются и тестируются еще до окончания их проектирования и реализации. При восходящей интеграции перед разработкой компонентов более высокого уровня сначала интегрируются и тестируются компоненты нижнего уровня [2].
Нисходящее тестирование является неотъемлемой частью процесса нисходящей разработки систем, при котором сначала разрабатываются компоненты верхнего уровня, а затем компоненты, находящиеся на нижних уровнях иерархии. Программу можно представить в виде одного абстрактного компонента с субкомпонентами, являющимися заглушками. Заглушки имеют такой же интерфейс, что и компонент, но с ограниченной функциональностью. После того как компонент верхнего уровня запрограммирован и протестирован, таким же образом реализуются и тестируются его субкомпоненты. Процесс продолжается до тех пор, пока не будут реализованы компоненты самого нижнего уровня. Затем вся система тестируется целиком [2-4].
Рисунок 7 – Нисходящее и восходящее тестирование
При восходящем тестировании, наоборот, сначала интегрируются и тестируются модули, расположенные на более низких уровнях иерархии. Затем выполняется сборка и тестирование модулей, расположенных на верхнем уровне иерархии, и так до тех пор, пока не будет протестирован последний модуль. При таком подходе не требуется наличие законченного архитектурного проекта системы, и поэтому он может начинаться на раннем этапе процесса разработки. Обычно такой подход применяется тогда, когда в системе есть повторно используемые компоненты или модифицированные компоненты из других систем[2,3].
Нисходящее и восходящее тестирование можно сравнить по четырем направлениям.
1. Верификация и аттестация системной архитектуры. При нисходящем тестировании больше возможностей выявить ошибки в архитектуре системы на раннем этапе процесса разработки. Обычно это структурные ошибки, раннее выявление которых предполагает их исправление без дополнительных затрат. При восходящем тестировании структура высокого уровня не утверждается вплоть до последнего этапа разработки системы.
2. Демонстрация системы. При нисходящей разработке незаконченная система вполне пригодна для работы уже на ранних этапах разработки. Этот факт является важным психологическим стимулом использования нисходящей модели разработки систем, поскольку демонстрирует осуществимость управления системой. Аттестация проводится в начале процесса тестирования путем создания демонстрационной версии системы. Но если система создается из повторно используемых компонентов, то и при восходящей разработке также можно создать ее демонстрационную версию.
3. Реализация тестов. Нисходящее тестирование сложно реализовать, так как необходимо моделировать программы-заглушки нижних уровней. Программы-заглушки могут быть упрощенными версиями представляемых компонентов. При восходящем тестировании для того, чтобы использовать компоненты нижних уровней, необходимо разработать тестовые драйверы, которые эмулируют окружение компонента в процессе тестирования.
4. Наблюдение за ходом испытаний. При нисходящем и восходящем тестировании могут возникать проблемы, связанные с наблюдениями за ходом тестирования. В большинстве систем, разрабатываемых сверху вниз, более верхние уровни системы, которые реализованы первыми, не генерируют выходные данные, однако для проверки этих уровней нужны какие-либо выходные результаты. Испытатель должен создать искусственную среду для генерации результатов теста. При восходящем тестировании также может возникнуть необходимость в создании искусственной среды (тестовых драйверов) для исследования компонентов нижних уровней [2-4].
На практике при разработке и тестировании систем чаще всего используется композиция восходящих и нисходящих методов. Разные сроки разработки для разных частей системы предполагают, что группа, проводящая тестирование и интеграцию, должна работать с каким-либо готовым компонентом. Поэтому во время процесса тестирования сборки в любом случае необходимо разрабатывать как заглушки, так и тестовые драйверы [3,4,5].
Тестирование интерфейсов
Как правило, тестирование интерфейса выполняется в тех случаях, когда модули или подсистемы интегрируются в большие системы. Каждый модуль или подсистема имеет заданный интерфейс, который вызывается другими компонентами системы. Цель тестирования интерфейса – выявить ошибки, возникающие в системе вследствие ошибок в интерфейсах или неправильных предположений об интерфейсах.
Схема тестирования интерфейса показана на рисунке 8. Стрелки в верхней части схемы означают, что контрольные тесты применяются не к отдельным компонентам, а к подсистемам, полученным в результате комбинирования этих компонентов.
Данный тип тестирования особенно важен в объектно-ориентированном проектировании, в частности при повторном использовании объектов и классов объектов. Объекты в значительной степени определяются с помощью интерфейсов и могут повторно использоваться в различных комбинациях с разными объектами и в разных системах. Во время тестирования отдельных объектов невозможно выявить ошибки интерфейса, так как они являются скорее результатом взаимодействия между объектами, чем изолированного поведения одного объекта [4].
Рисунок 8 – Тестирование интерфейсов
Между компонентами программы могут быть разные типы интерфейсов и соответственно разные типы ошибок интерфейсов [4,5].
1. Параметрические интерфейсы. Интерфейсы, в которых ссылки на данные и иногда функции передаются в виде параметров от одного компонента к другому.
2. Интерфейсы разделяемой памяти. Интерфейсы, в которых какой-либо блок памяти совместно используется разными подсистемами. Одна подсистема помещает данные в память, а другие подсистемы используют эти данные.
3. Процедурные интерфейсы. Интерфейсы, в которых одна подсистема инкапсулирует набор процедур, вызываемых из других подсистем. Такой тип интерфейса имеют объекты и абстрактные типы данных.
4. Интерфейсы передачи сообщений. Интерфейсы, в которых одна подсистема запрашивает сервис у другой подсистемы посредством передачи ей сообщения. Ответное сообщение содержит результаты выполнения сервиса. Некоторые объектно-ориентированные системы имеют такой тип интерфейсов; например, так работают системы клиент/сервер [4,5].
Ошибки в интерфейсах являются наиболее распространенными типами ошибок в сложных системах [2] и делятся на три класса.
1. Неправильное использование интерфейсов. Компонент вызывает другой компонент и совершает ошибку при использовании его интерфейса. Данный тип ошибки особенно распространен в параметрических интерфейсах; например, параметры могут иметь неправильный тип, следовать в неправильном порядке или же иметь неверное количество параметров.
2. Неправильное понимание интерфейсов. Вызывающий компонент, в который заложена неправильная интерпретация спецификации интерфейса вызываемого компонента, предполагает определенное поведение этого компонента. Если поведение вызываемого компонента не совпадает с ожидаемым, поведение вызывающего компонента становится непредсказуемым. Например, если программа бинарного поиска вызывается для поиска заданного элемента в неупорядоченном массиве, то в работе программы произойдет сбой.
3. Ошибки синхронизации. Такие ошибки встречаются в системах реального времени, где используются интерфейсы разделяемой памяти или передачи сообщений. Подсистема – производитель данных и подсистема – потребитель данных могут работать с разной скоростью. Если при проектировании интерфейса не учитывать этот фактор, потребитель может, например, получить доступ к устаревшим данным, потому что производитель к тому моменту еще не успел обновить совместно используемые данные [3-5].
Тестирование дефектов интерфейсов сложно, поскольку некоторые ошибки могут проявиться только в необычных условиях. Например, пусть некий объект реализует очередь в виде структуры списка фиксированного размера. Вызывающий его объект при вводе очередного элемента не проверяет переполнение очереди, так как предполагает, что очередь реализована как структура неограниченного размера. Такую ситуацию можно обнаружить только во время выполнения специальных тестов: специально вызывается переполнение очереди, которое приводит к непредсказуемому поведению объекта [3-5].
Другая проблема может возникнуть из-за взаимодействий между ошибками в разных программных модулях или объектах. Ошибки в одном объекте можно выявить только тогда, когда поведение другого объекта становится непредсказуемым. Например, для получения сервиса один объект вызывает другой объект и полагает, что полученный ответ правильный. Если объект неправильно понимает вычисленные значения, возвращаемое значение может быть достоверным, но неправильным. Такие ошибки можно выявить только тогда, когда оказываются неправильными дальнейшие вычисления [4,5].
Несколько общих правил тестирования интерфейсов:
1. Просмотреть тестируемый код и составить список всех вызовов, направленных к внешним компонентам. Разработать такие наборы тестовых данных, при которых параметры, передаваемые внешним компонентам, принимают крайние значения из диапазонов их допустимых значений. Использование экстремальных значений параметров с высокой вероятностью обнаруживает несоответствия в интерфейсах.
2. Если между интерфейсами передаются указатели, всегда следует тестировать интерфейс с нулевыми параметрами указателя.
3. При вызове компонента через процедурный интерфейс необходимо использовать тесты, вызывающие сбой в работе компонента. Одна из наиболее распространенных причин ошибок в интерфейсе – неправильное понимание спецификации компонентов.
4. В системах передачи сообщений нужно использовать тесты с нагрузкой, которые рассматриваются в следующем разделе. Необходимо разработать тесты, генерирующие в несколько раз большее количество сообщений, чем будет в обычной работе системы. Эти же тесты позволяют обнаружить проблемы синхронизации.
5. При взаимодействии нескольких компонентов через разделяемую память следует разработать тесты, которые изменяют порядок активизации компонентов. С помощью таких тестов можно выявить сделанные программистом неявные предположения о порядке использования компонентами разделяемых данных.
6. Обычно статические методы тестирования более рентабельны, чем специальное тестирование интерфейсов. В языках со строгим контролем типов, например Java, многие ошибки интерфейсов помогает обнаружить компилятор. В языках со слабым контролем типов (например, С) ошибки интерфейса может выявить статический анализатор, такой как LINT (обеспечивает статическую проверку, эквивалентную проверке компилятором). Кроме того, при инспектировании программ можно сосредоточиться именно на проверке интерфейсов компонентов [3,5].
Тестирование с нагрузкой
После полной интеграции системы можно оценить такие интеграционные свойства системы, как производительность и надежность. Чтобы убедиться, что система может работать с заданной нагрузкой, разрабатываются тесты для измерения производительности. Обычно планируются серии тестов с постоянным увеличением нагрузки, пока производительность системы не начнет снижаться.
Некоторые классы систем проектируются с учетом работы под определенной нагрузкой. Например, система обработки транзакций проектируется так, чтобы обрабатывать 100 транзакций в секунду; сетевая операционная система – чтобы обрабатывать информацию от 200 отдельных терминалов. При тестировании с нагрузкой выполнение тестов начинается с максимальной нагрузки, указанной в проекте системы, и продолжается до тех пор, пока не произойдет сбой в работе системы. Данный тип тестирования выполняет две функции [4].
1. Тестируется поведение системы во время сбоя. В процессе эксплуатации могут возникать ситуации, при которых нагрузка в системе превышает максимально допустимую. В таких ситуациях очень важно, чтобы сбой в системе не приводил к нарушению целостности данных или к потере сервисных возможностей.
2. Чтобы выявить дефекты, которые не проявляются в обычных режимах работы, система подвергается тестированию с нагрузкой. Хотя подобные дефекты не приводят к ошибкам при обычном использовании системы, на практике могут возникнуть необычные комбинации стандартных условий; именно они воспроизводятся во время тестирования с нагрузкой [4].
Тестирование с нагрузкой чаще всего применяется в распределенных системах. В таких системах при большой нагрузке сеть порой "забивается" данными, которыми обмениваются разные процессы. Постепенно процессы все больше замедляются, поскольку они ожидают данные запросов от других процессов.
Дата: 2019-12-22, просмотров: 368.