Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шаблоны и архитектура программ.doc
Скачиваний:
11
Добавлен:
04.05.2019
Размер:
558.08 Кб
Скачать

Министерство образования Республики Беларусь

Учреждение образования

«Белорусский государственный университет

информатики и радиоэлектроники»

Кафедра информатики

А.А. Волосевич

ШАБЛОНЫ И АРХИТЕКТУРА ПРОГРАММ

Курс лекций

для студентов специальности I-31 03 04 Информатика

всех форм обучения

Минск 2010

СОДЕРЖАНИЕ

3. ШАБЛОНЫ И АРХИТЕКТУРА ПРОГРАММ

3.1. МОДУЛЬНОЕ ТЕСТИРОВАНИЕ

3.2. ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

3.3. СТРУКТУРНЫЕ ШАБЛОНЫ: Декоратор, Заместитель, мост

Декоратор (Decorator)

Заместитель (Proxy)

Мост (Bridge)

3.4. СТРУКТУРНЫЕ ШАБЛОНЫ: КОМПОНОВЩИК И ПРИСПОСОБЛЕНЕЦ

Компоновщик (Composite)

Приспособленец (Flyweight)

3.5. СТРУКТУРНЫЕ ШАБЛОНЫ: АДАПТЕР И ФАСАД

Адаптер (Adapter)

Фасад (Façade)

3.6. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: ПРОТОТИП, ФАБРИЧНЫЙ МЕТОД, ОДИНОЧКА

Прототип (Prototype)

Фабричный метод (Factory method) #

Одиночка (Singleton)

3.7. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: АБСТРАКТНАЯ ФАБРИКА И СТРОИТЕЛЬ

Абстрактная фабрика (Abstract factory)

Строитель (Builder)

3.8. ШАБЛОНЫ ПОВЕДЕНИЯ: СТРАТЕГИЯ, СОСТОЯНИЕ, ШАБЛОННЫЙ МЕТОД

Стратегия (Strategy)

Состояние (State)

Шаблонный метод (Template method)

3.9. ШАБЛОНЫ ПОВЕДЕНИЯ: ЦЕПОЧКА ОБЯЗАННОСТЕЙ И КОМАНДА

Цепочка обязанностей (Chain of responsibility)

Команда (Command)

3.10. ШАБЛОНЫ ПОВЕДЕНИЯ: ИТЕРАТОР, ПОСРЕДНИК, НАБЛЮДАТЕЛЬ

Итератор (Iterator)

Посредник (Mediator)

Наблюдатель (Observer)

3.11. ШАБЛОНЫ ПОВЕДЕНИЯ: ПОСЕТИТЕЛЬ, ИНТЕРПРЕТАТОР, ХРАНИТЕЛЬ

Посетитель (Visitor)

Интерпретатор (Interpreter)

Хранитель (Memento)

3.12. НЕКОТОРЫЕ НЕКЛАССИЧЕСКИЕ ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

Неизменный объект (Immutable object)

Пул объектов (Object pool)

Отложенная инициализация (Lazy initialization)

Нулевой объект (Null object)

3.13. АНТИПАТТЕРНЫ

3.14. АРХИТЕКТУРА ПРОГРаммного Обеспечения

«Клиент-сервер»

Архитектура, основанная на использовании компонентов

Многоуровневая архитектура

Шина сообщений

Выделенное представление

N-звеньевая архитектура

Объектно-ориентированная архитектура

Архитектура, ориентированная на сервисы

3. Шаблоны и архитектура программ

3.1. Модульное тестирование

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

Модульное тестирование (unit testing) – процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы. При проведении модульного тестирования для каждой нетривиальной функции модуля создаются тесты (так называемые юнит-тесты), проверяющие правильность работы функции. Затем набор тестов запускается (как правило, в автоматическом режиме), и анализируется результат тестового прогона. Кроме выявления ошибок, прогон тестов позволяет достаточно быстро проверить, не привело ли изменение кода к #регрессии, то есть к появлению ошибок в уже написанных и оттестированных #функциях. Правильно составленные юнит-тесты также способны работать #как «живой документ» для модуля: клиенты, которые не знают, как #использовать данный модуль, могут использовать юнит-тест в качестве #примера.######

Когда модульное тестирование используется совместно с объектно-ориентированным проектированием, термин «модуль» соответствует понятию «класс», а термин «функция модуля» – понятию «метод класса». Для большинства современных платформ программирования существуют специальные библиотеки модульного тестирования. Широко распространённой библиотекой тестирования для платформы .NET является NUnit. С сайта http://www.nunit.org можно скачать саму библиотеку, документацию, а также графическую оболочку для запуска тестов.

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

using System;

using System.Collections.Generic;

namespace Lists

{

public class IntegerList

{

private readonly List<int> data;

public int Count { get { return data.Count; } }

public int this[int index]

{

get { return data[index]; }

set { data[index] = value; }

}

public IntegerList(int count)

{

if (count < 0)

{

throw new ArgumentException("Count must be >= 0");

}

data = new List<int>(count);

}

public void Add(int element)

{

data.Add(element);

}

public int FindMax()

{

var max = Int32.MinValue;

foreach (var element in data)

{

if (element > max)

{

max = element;

}

}

return max;

}

}

}

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

Таблица 1

Соответствия между тестируемым и тестирующим кодами

Объект

тестирования

Объект модульного теста

Проект

Создайте проект (библиотеку классов), содержащий тесты, с именем ИсходныйПроект.Tests

Класс

Для каждого класса, подлежащего тестированию, создайте, по крайней мере, один тестирующий класс с именем ИмяКлассаTests. Такие тестирующие классы называются наборами тестов (test fixtures)

Метод

Для каждого метода, подлежащего тестированию, создайте, по крайней мере, один тестирующий public-метод (тест) с именем ИмяМетода_ТестируемоеУсловие_ОжидаемоеПоведение

Следуя правилам табл. 1, создадим проект с именем Lists.Tests, содержащий ссылку на библиотеку nunit.framework.dll. Поместим в этот проект класс IntegerListTests с методом Count_AddTwoElements_Return2().

using NUnit.Framework;

using Lists;

namespace Lists.Tests

{

[TestFixture]

public class IntegerListTests

{

[Test]

public void Count_AddTwoElements_Return2()

{

var obj = new IntegerList(2);

obj.Add(3);

obj.Add(-1);

Assert.AreEqual(2, obj.Count);

}

}

}

Как видно из приведённого листинга, класс, который является набором тестов, помечается специальным атрибутом [TestFixture], а отдельный тест – атрибутом [Test]. Ниже будут рассмотрены и другие атрибуты библиотеки NUnit. Структура метода-теста достаточно типична. Вначале производится создание и настройка необходимых объектов. Затем выполняются запланированные действия с объектами. В конце формулируется некое утверждение, которое будет истинно, если тест закончился правильно. Формулировка утверждения производится при помощи методов класса Assert, который далее будет рассмотрен подробно.

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

Рис. 1. Окно графической оболочки NUnit.

Ознакомившись с основными шагами процесса модульного тестирования, рассмотрим возможности библиотеки NUnit подробнее. Для настройки тестов NUnit предоставляет несколько атрибутов, два из которых уже были рассмотрены: [TestFixture] и [Test]. Класс, содержащий тесты, может иметь методы, помеченные атрибутами [SetUp] и [TearDown]. Если метод помечен атрибутом [SetUp], то он выполняется перед каждым тестовым методом. Атрибут [TearDown] используется для методов, выполняемых после каждого теста. Обычно методы с атрибутами [SetUp] и [TearDown] – это методы подготовки и завершения отдельного теста. Атрибуты [TestFixtureSetUp] и [TestFixtureTearDown] также применяются к методам, но служат для подготовки и завершения всего тестового набора.

[TestFixture]

public class IntegerListTests

{

private IntegerList lst = null;

[SetUp]

public void Prepare()

{

lst = new IntegerList(10);

}

[TearDown]

public void Release()

{

lst = null;

}

[Test]

public void Count_AddTwoElements_Return2()

{

lst.Add(3);

lst.Add(-1);

Assert.AreEqual(2, lst.Count);

}

}

Нередко тест выполняется для того, чтобы проверить, генерирует ли метод требуемое исключение. Такой тест можно пометить атрибутом [ExpectedException] с указанием типа ожидаемой исключительной ситуации. Кроме этого, атрибут [ExpectedException] допускает дополнительное указание ожидаемого сообщения при генерации исключения. Метод с атрибутом [ExpectedException] обычно не содержит обращений к классу Assert.

[Test]

[ExpectedException(typeof(ArgumentException))]

public void Constructor_CallWithNegativeArg_ThrowException()

{

var obj = new IntegerList(-1);

}

Атрибут [Ignore] разрешает проигнорировать тест при прогоне, а атрибут [Category] позволяет указать произвольную категорию для теста.

Практически любой тест использует один или несколько статических методов класса Assert для формулировки тестового утверждения. Упомянем такие методы этого класса, как AreEqual(), Less(), Greater(), GreaterOrEqual(), LessOrEqual(), IsNull(), IsNotNull(), AreSame(), IsTrue(). Начиная с версии NUnit 2.4 доступен новый способ использованием Assert: применяется метод Assert.That(), и формулировка утверждения записывается как аргумент этого метода.

// два эквивалентных утверждения

// в старом

Assert.AreEqual(actual, expected);

// и новом синтаксисе

Assert.That(actual, Is.EqualTo(expected));

Ознакомившись с техникой тестирования, рассмотрим некоторые вопросы тестовой методологии. Специалисты модульного тестирования выделяют шесть областей, на которые распадается общий набор юнит-тестов. Эти области известны в виде акронима Right-BICEP.

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

  • Boundary Condition. Данные тесты проверяют работу модуля на различных граничных условиях. Возможные граничные условия формируют акроним CORRECT:

  • Conformance – входные и выходные данные метода удовлетворяют (conform) определенному ожидаемому формату.

  • Ordering – исходные данные находятся в упорядоченном или неупорядоченном множестве.

  • Range – проверка попадания исходных данных в разумный диапазон.

  • Reference – граничные условия, предполагающие, что до или после тестируемого метода выполняются некие действия, т. е. тестируемый метод имеет ссылки на другие методы.

  • Existence – проверка того, что данные существуют (не равны нулю, не равны null, и т. п.).

  • Cardinality – проверка того, что исходные данные поступают на вход в нужном количестве.

  • Time – проверка порядка обработки данных.

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

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

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

  • Performance Characteristics. Тесты из области характеристики производительности замеряют время выполнения кода, а также то, насколько изменяется время выполнения при изменении размера входных данных.

Так как модульное тестирование предполагает тестирование изолированных компонент, достаточно часто возникает необходимость в моделировании контекста для тестируемого объекта. При этом обычно применяются объекты-заглушки (stubs) и поддельные объекты (mock objects). Хотя разница между этими понятиями, в принципе, условна, адепты модульного тестирования поясняют её так. Объект-заглушка используется тестируемым объектом, а поддельный объект тестируется сам, но работает при этом с тестируемым объектом.

Рис. 2. Объект-заглушка и поддельный объект.

Для упрощения создания объектов-заглушек и поддельных объектов существуют специальные библиотеки и платформы (см., например, Rhino Mocks).