теория_4 (Методичка и инструкции на ЛР №8)
Описание файла
Файл "теория_4" внутри архива находится в следующих папках: Методичка и инструкции на ЛР №8, Инструкции, Задание_4. Документ из архива "Методичка и инструкции на ЛР №8", который расположен в категории "". Всё это находится в предмете "технологии разработки программного обеспечения (по)" из 10 семестр (2 семестр магистратуры), которые можно найти в файловом архиве МГТУ им. Н.Э.Баумана. Не смотря на прямую связь этого архива с МГТУ им. Н.Э.Баумана, его также можно найти и в других разделах. Архив можно найти в разделе "лабораторные работы", в предмете "технологии разработки по" в общих файлах.
Онлайн просмотр документа "теория_4"
Текст из документа "теория_4"
9.2. Модульное тестирование
9.2.1. Теоретическое вступление
9.2.1.1. Задачи и цели модульного тестирования
Каждая сложная программная система состоит из отдельных частей – модулей, выполняющих ту или иную функцию в составе системы. Для того, чтобы удостовериться в корректной работе системы в целом, необходимо вначале протестировать каждый модуль системы в отдельности. В случае возникновения проблем это позволит проще выявить модули, вызвавшие проблему, и устранить соответствующие дефекты в них. Такое тестирование модулей по отдельности получило называние модульного тестирования (unit testing).
Для каждого модуля, подвергаемого тестированию, разрабатывается тестовое окружение, включающее в себя драйвер и заглушки, готовятся тест-требования и тест-планы, описывающие конкретные тестовые примеры.
Основная цель модульного тестирования – удостовериться в соответствии требованиям каждого отдельного модуля системы перед тем, как будет произведена его интеграция в состав системы.
При этом в ходе модульного тестирования решаются четыре основные задачи.
-
Поиск и документирование несоответствий требованиям – это классическая задача тестирования, включающая в себя не только разработку тестового окружения и тестовых примеров, но и выполнение тестов, протоколирование результатов выполнения, составление отчетов о проблемах.
-
Поддержка разработки и рефакторинга низкоуровневой архитектуры системы и межмодульного взаимодействия – эта задача больше свойственна "легким" методологиям типа XP, где применяется принцип тестирования перед разработкой (Test-driven development), при котором основным источником требований для программного модуля является тест, написанный до самого модуля. Однако, даже при классической схеме тестирования модульные тесты могут выявить проблемы в дизайне системы и нелогичные или запутанные механизмы работы с модулем.
-
Поддержка рефакторинга модулей – эта задача связана с поддержкой процесса изменения системы. Достаточно часто в ходе разработки требуется проводить рефакторинг модулей или их групп – оптимизацию или полную переделку программного кода с целью повышения его сопровождаемости, скорости работы или надежности. Модульные тесты при этом являются мощным инструментом для проверки того, что новый вариант программного кода работает в точности так же, как и старый.
-
Поддержка устранения дефектов и отладки — эта задача сопряжена с обратной связью, которую получают разработчики от тестировщиков в виде отчетов о проблемах. Подробные отчеты о проблемах, составленные на этапе модульного тестирования, позволяют локализовать и устранить многие дефекты в программной системе на ранних стадиях ее разработки или разработки ее новой функциональности.
В силу того, что модули, подвергаемые тестированию, обычно невелики по размеру, модульное тестирование считается наиболее простым (хотя и достаточно трудоемким) этапом тестирования системы. Однако, несмотря на внешнюю простоту, с модульным тестированием связано две проблемы.
-
Не существует единых принципов определения того, что в точности является отдельным модулем.
-
Различия в трактовке самого понятия модульного тестирования – понимается ли под ним обособленное тестирование модуля, работа которого поддерживается только тестовым окружением, или речь идет о проверке корректности работы модуля в составе уже разработанной системы. В последнее время термин "модульное тестирование" чаще используется во втором смысле, хотя в этом случае речь скорее идет об интеграционном тестировании.
9.2.1.2. Понятие модуля и его границ. Тестирование классов
Традиционное определение модуля с точки зрения его тестирования: "модуль – это компонент минимального размера, который может быть независимо протестирован в ходе верификации программной системы". В реальности часто возникают проблемы с тем, что считать модулем. Существует несколько подходов к данному вопросу:
-
модуль – это часть программного кода, выполняющая одну функцию с точки зрения функциональных требований;
-
модуль – это программный модуль, т.е. минимальный компилируемый элемент программной системы;
-
модуль – это задача в списке задач проекта (с точки зрения его менеджера);
-
модуль – это участок кода, который может уместиться на одном экране или одном листе бумаги;
-
модуль – это один класс или их множество с единым интерфейсом.
Обычно за тестируемый модуль принимается либо программный модуль (единица компиляции) в случае, если система разрабатывается на процедурном языке программирования, либо класс, если система разрабатывается на объектно-ориентированном языке.
В случае систем, написанных на процедурных языках, процесс тестирования модуля происходит так, как это было рассмотрено в темах 2-4 – для каждого модуля разрабатывается тестовый драйвер, вызывающий функции модуля и собирающий результаты их работы, и набор заглушек, которые имитируют поведение функций, содержащихся в других модулях и не попадающих под тестирование данного модуля. При тестировании объектно-ориентированных систем существует ряд особенностей, прежде всего вызванных инкапсуляцией данных и методов в классах.
В случае объектно-ориентированных систем более мелкое деление классов и использование отдельных методов в качестве тестируемых модулей нецелесообразно в связи с тем, что для тестирования каждого метода потребуется разработка тестового окружения, сравнимого по сложности с уже написанным программным кодом класса. Кроме того, декомпозиция класса нарушает принцип инкапсуляции, согласно которому объекты каждого класса должны вести себя как единое целое с точки зрения других объектов.
Процесс тестирования классов как модулей иногда называют компонентным тестированием. В ходе такого тестирование проверяется взаимодействие методов внутри класса и правильность доступа методов к внутренним данным класса. При таком тестировании возможно обнаружение не только стандартных дефектов, связанных с выходами за границы диапазона или неверно реализованными требованиями, а также обнаружение специфических дефектов объектно-ориентированного программного обеспечения:
-
дефектов инкапсуляции, в результате которых, например, сокрытые данные класса оказывается недоступными при помощи соответствующих публичных методов;
-
дефектов наследования, при наличии которых схема наследования блокирует важные данные или методы от классов-потомков;
-
дефектов полиморфизма, при которых полиморфное поведение класса оказывается распространенным не на все возможные классы;
-
дефектов инстанцирования, при которых во вновь создаваемых объектах класса не устанавливаются корректные значения по умолчанию параметров и внутренних данных класса.
Однако, выбор класса в качестве тестируемого модуля имеет и ряд сопряженных проблем.
Определение степени полноты тестирования класса. В том случае, если в качестве тестируемого модуля выбран класс, не совсем ясно, как определять степень полноты его тестирования. С одной стороны, можно использовать классический критерий полноты покрытия программного кода тестами: если полностью выполнены все структурные элементы всех методов, как публичных, так и скрытых, — то тесты можно считать полными.
Однако существует альтернативный подход к тестированию класса, согласно которому все публичные методы должны предоставлять пользователю данного класса согласованную схему работы и достаточно проверить типичные корректные и некорректные сценарии работы с данным классом.
Различия в этих двух методах напоминают различия между тестированием "черного" и "белого" ящиков, но на самом деле второй подход отличается от "черного ящика" тем, что функциональные требования к системе могут быть составлены на уровне более высоком, чем отдельные классы, и установление адекватности тестовых сценариев требованиям остается на откуп тестировщику.
Протоколирование состояний объектов и их изменений. Некоторые методы класса предназначены не для выдачи информации пользователю, а для изменения внутренних данных объекта класса. Значение внутренних данных объекта определяет его состояние в каждый отдельный момент времени, а вызов методов, изменяющих данные, изменяет и состояние объекта. При тестировании классов необходимо проверять, что класс адекватно реагирует на внешние вызовы в любом из состояний. Однако, зачастую из-за инкапсуляции данных невозможно определить внутреннее состояние класса программными способами внутри драйвера.
Тестирование изменений. Как уже упоминалось выше, модульные тесты – мощный инструмент проверки корректности изменений, внесенных в исходный код при рефакторинге. Однако, в результате рефакторинга только одного класса, как правило, не меняется его внешний интерфейс с другими классами (интерфейсы меняются при рефакторинге сразу нескольких классов). В результате обычных эволюционных изменений системы у класса может меняться внешний интерфейс, причем как по формальным (изменяются имена и состав методов, их параметры), так и по функциональным признакам (при сохранении внешнего интерфейса меняется логика работы методов). Для проведения модульного тестирования класса после таких изменений потребуется изменение драйвера и, возможно, заглушек. Но только модульного тестирования в данном случае недостаточно, необходимо также проводить и интеграционное тестирование данного класса вместе со всеми классами, которые связаны с ним по данным или по управлению.
9.2.1.3. Подходы к проектированию тестового окружения
Вне зависимости от того, какая минимальная единица исходных кодов системы выбирается за минимальный тестируемый модуль, существует еще одно различие в подходах к модульному тестированию.
Первый подход к модульному тестированию основывается на предположении, что функциональность каждого вновь разработанного модуля должна проверяться в автономном режиме без его интеграции с системой. Здесь для каждого вновь разрабатываемого модуля создается тестовый драйвер и заглушки, при помощи которых выполняется набор тестов. Только после устранения всех дефектов в автономном режиме производится интеграция модуля в систему и проводится тестирование на следующем уровне. Достоинством данного подхода является более простая локализация ошибок в модуле, поскольку при автономном тестировании исключается влияние остальных частей системы, которое может вызывать маскировку дефектов (эффект четного числа ошибок). Основной недостаток данного метода – повышенная трудоемкость написания драйверов и заглушек, поскольку заглушки должны адекватно моделировать поведение системы в различных ситуациях, а драйвер должен не только создавать тестовое окружение, но и имитировать внутреннее состояние системы, в составе которой должен функционировать модуль.
Второй подход построен на предположении, что модуль все равно работает в составе системы и если модули интегрировать в систему по одному, то можно протестировать поведение модуля в составе всей системы. Этот подход свойственен большинству современных "облегченных" методологий разработки, в том числе и XP.
В результате применения такого подхода резко сокращаются трудозатраты на разработку заглушек и драйверов – в роли заглушек выступает уже оттестированная часть системы, а драйвер выполняет только функции передачи и приема данных, не моделируя внутреннее состояние системы.
Тем не менее, при использовании данного метода возрастает сложность написания тестовых примеров – для приведения в нужное состояние системы заглушек, как правило, требуется только установить значения тестовых переменных, а для приведения в нужное состояние части реальной системы необходимо выполнить целый сценарий. Каждый тестовый пример в этом случае должен содержать такой сценарий.
Кроме того, при этом подходе не всегда удается локализовать ошибки, скрытые внутри модуля, которые могут проявиться при интеграции следующих модулей.
Замечание. О том, что такое Reflection, можно прочесть на http://msdn2.microsoft.com/en-us/library/cxz4wk15(VS.80).aspx
9.2.2. На примере "Калькулятора"
Рассмотренный на предыдущем семинаре пример прост прежде всего за счет того, что нам не приходится создавать тестового окружения. Чтобы увидеть весь описанный механизм в действии, протестируем метод RunEstimate класса AnalaizerClass. Этот метод использует методы из класса CalcClass, в надежности которых мы не уверены. Заменим эти методы заглушкой, состоящей исключительно из функций стандартного класса Math. Для этого воспользуемся файлом My.dll и добавим его в проект.
На семинаре мы не будем составлять тест-требования . Продемонстрируем, как создать тестовое окружение и запустить метод. Проверяем операцию сложения на примере 2+2, т.е. в стеке до начала выполнения самой операции (т.е. после компиляции) находятся следующие элементы: "2", "2", "+".
private void buttonStart_Click(object sender, EventArgs e)
{
// создаем провайдер для генерирования и компиляции кода на C#
System.CodeDom.Compiler.CodeDomProvider prov =
System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp");
// создаем параметры компилирования
System.CodeDom.Compiler.CompilerParameters cmpparam = new
System.CodeDom.Compiler.CompilerParameters();
// результат компиляции - библиотека
cmpparam.GenerateExecutable = false;
// не включаем информацию отладчика
cmpparam.IncludeDebugInformation = false;
// подключаем 2-е стандартные библиотеки и библиотеку