СиППО (46-53) (987682), страница 5
Текст из файла (страница 5)
· Один класс может быть типом возвращаемого значения метода другого класса.
· Метод одного класса создает экземпляр другого класса, как часть своей реализации.
Общим признаком перечисленных случаев является то, что один класс обслуживает структуру данных для другого класса: обеспечивает хранение данных со сложной структурой и операции над ними. Типичным примером такого использования классов являются контейнерные классы в языках С++ и С#. Такое взаимодействие классов называют коллекцией. Тестирование коллекций проводится аналогично тестированию примитивных классов, описанному в предыдущем параграфе. Тестовый драйвер должен обеспечить выполнение всех заложенных в коллекции операций.
Тестирование взаимодействия двух объектов является достаточно простой задачей. Количество потенциальных взаимодействующих объектов быстро становится значительным, и многие серьёзные ошибки возникают именно при совместном использовании некоторого множества объектов. При этом возникает вопрос: как тестировать каждое взаимодействие – индивидуально или в рамках некоторой группы взаимодействий? Правильный выбор «куска» для тестирования имеет большое практическое значение для успеха процесса в целом.
При составлении тестов для проверки взаимодействия классов необходимо учитывать, какой подход был применен при разработке классов: контрактный или защитный. При контрактном подходе ответственность за выполнение предварительных условий операций возлагается на отправителя. Поэтому тестированием должно быть установлено, что ни один класс не отправляет сообщений, не удовлетворяющих предусловиям методов класса – получателя. В классе получателе такие проверки тогда не нужны. Если правильность предусловий не может полностью быть проверена в классе отправителе (например, класс получатель должен часть необходимых для выполнения операции данных получить из своей базы данных), то для таких операций может быть применен и принцип защитного программирования.
При применении защитного программирования необходимо тестами проверить допустимость постусловий и выполнение реакций методов на невыполнение постусловий.
Для эффективного построения тестов взаимодействия могут быть использованы диаграммы взаимодействия. Напомним, что одна диаграмма взаимодействия соответствует реализации одного варианта использования по одному сценарию. Отсюда следует, что для достаточного тестирования взаимодействия классов необходимо таким образом подбирать тесты, чтобы все возможные сценарии были реализованы. Дополнительно для проверки достаточности тестирования можно использовать приведенные выше критерии достаточности тестирования одного отдельно взятого класса (см. предыдущий параграф).
2) Тестирование иерархии классов.
Тестирование иерархии классов всегда выполняется «сверху - вниз». Допустим, что класс С является наследником класса А и класс А прошел тестирование классов и взаимодействия классов.
С может отличаться от А по следующим признакам:
· В С добавлены новые операции в интерфейс класса и, возможно, новые методы для их реализации.
· В С изменена спецификация и/или реализация операции из А.
· С содержит новые переменные по сравнению с А.
· Инвариант класса С может отличаться от инварианта А.
Поскольку С наследует часть своей спецификации от А, то все тесты, ориентированные на тестирование A, могут быть задействованы и при тестировании С. Для проверки новых операций необходимо разработать и новые тесты. Если инварианты в подчиненном классе уточнены, то могут потребоваться и тесты для их проверки.
Инкрементные изменения, внесенные в класс А во время построения производного класса В, могут быть использованы при выявлении, что потребуется тестировать в В. Унаследованными тестовыми случаями называют тесты, предназначенные для тестирования базового класса и используемые для тестирования класса-наследника. Путем несложного анализа можно выяснить, какие унаследованные тестовые случаи могут быть использованы при тестировании наследника и какие нет.
Рассмотрим эти случаи подробнее.
В С добавлены новые операции в интерфейс класса и, возможно, новые методы для их реализации. В таком случае новые операции привносят новые функциональные возможности и новые программные коды, которые должны быть протестированы.
В С изменена спецификация операции из А. Требуются новые тесты для проверки измененных операций. Новые тесты могут быть составлены согласно рассмотренным выше принципам.
В С перекрыт метод из А. Для него можно повторно использовать все унаследованные тестовые случаи. В связи с появлением нового программного кода потребуется пересмотр всех тестов для их всесторонней проверки.
С содержит новые переменные по сравнению с А. Новая переменная, скорее всего, появилась вместе с изменениями в методах. Поэтому тестирование новых (перекрытых) методов проверяет и работу с этой переменой.
Инвариант класса С может отличаться от инварианта А. Инвариант класса можно рассматривать как дополнительное постусловие. Результат теста подвергается ограничениям со стороны инвариантов. Следовательно, если инвариант менялся, требуется повторный прогон всех унаследованных тестов, чтобы убедиться в выполнении новых инвариантов.
Нет необходимости добавлять тесты, ориентированные на спецификации, которые остались неизменными при переходе от базового класса к наследнику. Тесты могут быть повторно использованы в первозданном виде. Их повторный прогон не требуется, если методы, для проверки которых они предназначались, не изменились. Потребуется повторный прогон теста, если метод изменился косвенным путем (например, использует измененный метод). В таком случае могут потребоваться и дополнительные тесты для проверки измененных операций.
Для практической организации тестирования класса-наследника можно построить тестовый драйвер в виде класса-наследника тестового драйвера базового класса.
При тестировании абстрактных классов возникает принципиальная трудность, потому что их объекты не могут быть созданы. Используют следующие подходы:
1. создается класс-наследник с заглушкой чисто виртуального (С++) или абстрактного метода (Delphi, C#);
2. абстрактный класс тестируется как часть прямого потомка;
3. разрабатывается специальная реализация абстрактного класса для тестирования.
52. Тестирование целостности и системное тестирование.
Систе́мное тести́рование програ́ммного обеспече́ния — это тестирование программного обеспечения (ПО), выполняемое на полной, интегрированной системе, с целью проверки соответствия системы исходным требованиям. Системное тестирование относится к методам тестирования чёрного ящика, и, тем самым, не требует знаний о внутреннем устройстве системы.
Альфа-тестирование и бета-тестирование являются подкатегориями системного тестирования.
А́льфа-тести́рование (англ. alpha testing) — использование незавершённой (альфа) версии продукта (как правило, программного или аппаратного обеспечения), в которой реализована не вся функциональность, запланированная для данной версии продукта, штатными программистами (разработчиками и тестерами) с целью выявления ошибок в работе реализованных модулей и функций для их последующего устранения перед бета-тестированием.
Бе́та-тести́рование (англ. beta testing) — интенсивное использование почти готовой версии продукта (как правило, программного или аппаратного обеспечения) с целью выявления максимального числа ошибок в его работе для их последующего устранения перед окончательным выходом (релизом) продукта на рынок, к массовому потребителю.
В отличие от альфа-тестирования, проводимого силами штатных разработчиков или тестировщиков, бета-тестирование предполагает привлечение добровольцев из числа обычных будущих пользователей продукта, которым доступна упомянутая предварительная версия продукта (так называемая бета-версия).
Кроме того, открытие бета-тестирования может использоваться как часть стратегии продвижения продукта на рынок (например, бесплатная раздача бета-версий позволяет привлечь широкое внимание потребителей к окончательной дорогостоящей версии продукта), а также для получения предварительных отзывов о нём от широкого круга будущих пользователей.
Бета-версия не является финальной версией продукта, поэтому разработчик не гарантирует полного отсутствия ошибок, которые могут нарушить работу компьютера и/или привести к потере данных. Хотя, и в финальных версиях всё чаще таких гарантий разработчики не дают.
53. Сравнение объектно-ориентированного и процедурного программирования.
Программа на процедурном языке программирования состоит из последовательности операторов (инструкций), задающих процедуру решения задачи. Основным является оператор присваивания, служащий для изменения содержимого областей памяти. Концепция памяти как хранилища значений, содержимое которого может обновляться операторами программы, является фундаментальной в императивном программировании.
Выполнение программы сводится к последовательному выполнению операторов с целью преобразования исходного состояния памяти, то есть значений исходных данных, в заключительное, то есть в результаты. Таким образом, с точки зрения программиста имеются программа и память, причем первая последовательно обновляет содержимое последней.
Процедурный язык программирования предоставляет возможность программисту определять каждый шаг в процессе решения задачи. Особенность таких языков программирования состоит в том, что задачи разбиваются на шаги и решаются шаг за шагом. Используя процедурный язык, программист определяет языковые конструкции для выполнения последовательности алгоритмических шагов.
Процедурное (императивное) программирование является отражением архитектуры традиционных ЭВМ, которая была предложенафон Нейманом в 1940-х годах. Теоретической моделью процедурного программирования служит алгоритмическая система под названием Машина Тьюринга.
Объе́ктно-ориенти́рованное программи́рование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов (либо, в менее известном варианте языков с прототипированием, — прототипов).
Объектное и объектно-ориентированное программирование (ООП) возникло в результате развития идеологии процедурного программирования, где данные и подпрограммы (процедуры, функции) их обработки формально не связаны. Кроме того, в современном объектно-ориентированном программировании часто большое значение имеют понятия события (так называемое событийно-ориентированное программирование) и компонента (компонентное программирование).
ООП ориентировано на разработку крупных программных комплексов, разрабатываемых командой программистов (возможно, достаточно большой). Проектирование системы в целом, создание отдельных компонент и их объединение в конечный продукт при этом часто выполняется разными людьми, и нет ни одного специалиста, который знал бы о проекте всё.
Объектно-ориентированное проектирование основывается на описании структуры и поведения проектируемой системы, то есть, фактически, в ответе на два основных вопроса:
Из каких частей состоит система.
В чём состоит ответственность каждой из частей.
Выделение частей производится таким образом, чтобы каждая имела минимальный по объёму и точно определённый набор выполняемых функций (обязанностей), и при этом взаимодействовала с другими частями как можно меньше.
Несмотря на отдельные критические замечания в адрес ООП, в настоящее время именно эта парадигма используется в подавляющем большинстве промышленных проектов. Однако, нельзя считать, что ООП является наилучшей из методик программирования во всех случаях.
Обычно сравнивают объектное и процедурное программирование:
Процедурное программирование лучше подходит для случаев, когда важны быстродействие и используемые программой ресурсы, но требует большего времени для разработки.
Объектное — когда важна управляемость проекта и его модифицируемость, а также скорость разработки.