НТП_МУ_ЛР. Томский государственный университет систем управления и радиоэлектроники кафедра компьютерных систем в управлении
![]()
|
3.6 Список использованных источников 1. Git — Book [Электронный ресурс]. — URL: http://git- scm.com/book/ru/v1 (дата обращения 18.12.2014). 2. GitHub [Электронный ресурс]. — URL: https://github.com (дата об- ращения 18.12.2014). 3. Git — Downloading Package [Электронный ресурс]. — URL: http://git-scm.com/download/win (дата обращения 18.12.2014). 4. gitignore/VisualStudio.gitignore [Электронный ресурс]. — URL: https://github.com/github/gitignore/blob/master/VisualStudio.gitignore (дата обращения 18.12.2014). 5. Free Mercurial and Git Client for Windows and Mac | Atlassian Sour- ceTree [Электронный ресурс]. — URL: http://www.sourcetreeapp.com/ (дата обращения 18.12.2014). 6. Git — GUI Clients [Электронный ресурс]. — URL: http://git- scm.com/downloads/guis (дата обращения 18.12.2014). 7. A successful Git branching model// nvie.com [Электронный ресурс]. — URL: http://nvie.com/posts/a-successful-git-branching-model/ (дата обращения 18.12.2014). 8. Удачная модель ветвления для Git // Хабрахабр [Электронный ре- сурс]. — URL: http://habrahabr.ru/post/106912/ (дата обращения 18.12.2014). 58 4 Лабораторная работа № 4. Юнит - тестирование Юнит-тестирование (англ. «unit-testing», или блочное тестирование) — тестирование отдельного элемента изолированно от остальной системы. Относительно парадигмы объектно-ориентированного программирования системой является вся программа, а отдельным элементом — класс или его метод. Юнит-тестирование предназначено для проверки правильности ра- боты отдельно взятого класса. Чтобы исключить из результатов тестирова- ния влияние потенциальных ошибок других классов, тестируемый класс должен быть максимально изолирован, т. е. не использовать объекты и ме- тоды других классов. Данное требование в итоге позволяет иначе взгля- нуть на взаимодействие классов и выполнить рефакторинг на уменьшение связности классов. Фактически, юнит-тестирование заключается в написании некоторо- го класса-обёртки, который бы создавал экземпляр тестируемого класса. В классе-обёртке создаются методы-тесты, выполняющие следующий алго- ритм: 1. Создаются входные параметры — тестовые данные. 2. Подают тестовые данные на вход общедоступного (c модификато- ром доступа public в C#) метода тестируемого класса. 3. Сравнивают возвращаемое тестируемым методом значение с неко- торым эталоном — заранее известным результатом, который должен полу- читься при правильной работе данного метода. Данный эталон определяет- ся в ТЗ, спецификациях или другой проектной документации. 4. Если результат работы метода совпадает с эталоном — тест прой- ден. В любом другом случае тест считается провальным. По данному алгоритму проверяется каждый общедоступный метод тестируемого класса. Защищенные или закрытые члены класса тестирова- нию не подвергаются, чтобы не нарушать инкапсуляцию класса. 59 Таким образом, юнит-тесты представляют программный код, который также требует постоянной поддержки и следования определенным правилам оформления. В отличие от других видов тестирования проведение юнит- тестирования — обязанность разработчиков, а не тестировщиков. 4.1 Задание на лабораторную работу 1. Скачать библиотеку Nunit с сайта nunit.org и установить. 2. Запустите ваше решение в Visual Studio и создайте в нём новый проект — библиотеку классов UnitTests: Новый проект UnitTests должен хранить в себе ссылку на библиотеку nUnit и на тестируемые про- екты решения, например на Model (на рисунке не отображено). Также внутри самого проекта созда- ется папка Model, в которой будут храниться все тесты для проекта Model Рисунок 4.1 — Дерево решения с добавленным проектом для юнит-тестирования Поскольку в данном проекте обычно хранятся блочные и интеграци- онные тесты всего решения, его внутренняя организация должна повторять организацию решения. В нашем случае, внутри проекта необходимо соз- дать отдельную папку для хранения тестов проекта Model. 3. Добавьте в проект ссылку на библиотеку Nunit для возможности написания тестов в проекте. Данную ссылку можно подключить через вкладку «Расширения» в окне «Менеджер ссылок»: 60 Рисунок 4.2 — Менеджер ссылок проекта в Visual Studio Вам нужно подключить только «nunit.framework». Возможная вторая ссылка на «nunit.mocks» в данной лабораторной не рассматривается, она необходима для создания более сложных интеграционных тестов (см. «Мок-тестирование», «Мок-объекты»). 4. Создайте внутри папки Model проекта UnitTests класс с именем <ИмяТестируемогоКласса>Test.cs. В нашем примере для тестируемого класса MyClass класс с тестами будет называться MyClassTest. 5. Рассмотрим написание блочных тестов на примере следующего класса: namespace Model { /// /// Пример класса. /// { /// /// Свойство, выражающее количество чего-нибудь. /// /// /// Выполнить деление двух вещественных чисел. /// /// Числитель. 61 /// Знаменатель. /// { … } } } Реализация класса опущена специально, в настоящий момент она не важна. Важно, что в классе находятся два общедоступных члена с описа- нием их поведения. На основе этого мы можем придумать тестовые слу- чаи, которые и будут проверяться юнит-тестированием. 6. В первую очередь, обозначим наш класс юнит-тестов как тесто- вый. Для этого перед объявлением класса добавим атрибут [TestFixture]: using NUnit.Framework; namespace UnitTests.Model { /// /// Набор тестов для класса MyClass. /// [TestFixture] public class MyClassTest { } } 7. Далее сформируем метод, который будет выполнять тестирование свойства Count. Тест будет заключаться в том, что мы создадим экземпляр тестируемого класса и попытаемся в его свойство Count поместить как корректные, так и некорректные значения. Для начала напишем сам метод: using Model; using NUnit.Framework; namespace Model { /// /// Набор тестов для класса MyClass. /// [TextFixture] public class MyClassTest { 62 /// /// Тестирование свойства Count. /// /// Значение свойства Count. [Test] public double CountTest(int count) { var myClass = new MyClass(); myClass.Count = count; } } } На вход метод принимает некоторое целочисленное значение, кото- рое мы будем присваивать в свойство Count. Входные параметры тестово- го метода нужны нам для возможности создания нескольких тестовых слу- чаев, проверяемых одним тестом. 8. Добавим некоторый тестовый случай, проверяющий работу свой- ства Count при обычном значении, например 4. Для этого добавим после атрибута [Test] еще один атрибут [TestCase] с описанием входных данных и названием тестового случая: [Test] [TestCase(4, TestName = "Тестирование Count при присваивании 4.")] public void CountTest(int count) { var myClass = new MyClass(); myClass.Count = count; } 9. Запустим тест и проверим, выполняется ли он. Если у вас установ- лен Resharper, для запуска тестов достаточно нажать на специальный зна- чок на панели слева: 63 Рисунок 4.3 — Пиктограммы для запуска юнит-тестов Первый значок предназначен для запуска всех тестов, описанных в данном классе. Второй значок предназначен для запуска конкретного тес- тового метода. При этом в контекстном меню можно указать, запустить все тестовые случаи или только один конкретный. У нас пока только один тест с одним тестовым случаем, его и запустим. При запуске тестов появится следующая панель «Unit Test Sessions» (её можно найти во вкладке Re- sharper->Tools->Unit Test Sessions): Рисунок 4.4 — Панель сессий тестирования 64 Данная панель показывает новую созданную сессию тестирования. Каждая сессия может содержать свой набор проверяемых тестов: • Иерархию тестов относительно проекта и класса. • Успешное завершение нашего теста (отмечен зеленой галочкой и статусом Passed). У тестов внутри сессии возможны следующие статусы: • Тест пройден. • Тест провален — в результате будут показаны исходные данные, ожидаемый и фактический результаты выполнения теста. • Результат тестирования устарел — если были совершены изме- нения в тестируемом или тестирующем классах. При запуске тестов выполняется компилирование тестируемого про- екта и тестирующего проекта. Тесты не будут запущены, если при компи- лировании какого-либо из проектов возникла ошибка. Для запуска тестов также необязательно делать проект UnitTests запускаемым проектом. 10. Добавим еще тестовых случаев для свойства Count. Одним из способов формирования позитивных тестовых случаев является определе- ние краевых условий. Например, мы знаем, что свойство Count хранит в себе количество некоторых элементов. Это число может быть достаточно большим, но обязательно положительным или 0. Соответственно, мы оп- ределили интервал от 0 до максимального значения int. На основе этого предположения формируется 4 тестовых случая: минимально допустимое значение, минимально допустимое значение плюс 1, максимально допус- тимое значение, максимально допустимое значение –1: [TestCase(4, TestName = "Тестирование Count при присваивании 4.")] [TestCase(0, TestName = "Тестирование Count при присваивании 0.")] [TestCase(1, TestName = "Тестирование Count при присваивании 1.")] [TestCase(int.MaxValue, TestName = "Тестирование Count при присваи- вании MaxValue.")] [TestCase(int.MaxValue-1, TestName = "Тестирование Count при при- сваивании MaxValue-1.")] 65 После запуска тестов мы убеждаемся, что свойство действительно может принимать подобные значения. Рисунок 4.5 — Запуск тест-кейсов для одного теста 11. Мы описали позитивные сценарии использования свойства Count, т. е. мы присваивали такие значения, которые являются корректны- ми для свойства Count. Однако куда более важно протестировать негатив- ные сценарии — подача заведомо некорректных данных с ожиданием воз- никновения ошибки или исключения. Если программа не среагировала на некорректные данные исключением — это ошибка, так как неправильные входные данные для метода или свойства в итоге могут привести к непра- вильной работе других методов и классов, а отлаживать подобные отло- женные ошибки достаточно трудоёмкий процесс. Поэтому: • Программа, класс или метод должны реагировать на подачу не- корректных данных исключением или любым другим способом сообщения об ошибке. • Юнит-тестирование должно проверять негативные сценарии для классов и методов для избегания возникновения отложенных ошибок. Для свойства Count подобным негативным сценарием может быть попытка присвоения отрицательного значения. Проверку выполним для двух отрицательных чисел — для –1 и самого большого допустимого от- рицательного числа типа int. В данных тестовых случаях мы будем ждать 66 возникновения исключения ArgumentException, соответственно форма за- писи негативного тестового случая несколько изменится: [TestCase(-1, ExpectedException = typeof(ArgumentException), Test- Name = “Тестирование Count при присваивании -1.”)] [TestCase(int.MinValue, ExpectedException = typeof(ArgumentException), TestName = “Тестирование Count при присваивании минимально допустимого целого числа.”)] После запуска негативных тестов, они получили статус Failed. Рисунок 4.6 — Негативный юнит-тест провален Данный статус означает, что при подаче некорректных данных ис- ключение в свойстве Count не возникло, что показывает неправильную реализацию свойства Count. Следовательно, в сеттер свойства Count необ- ходимо добавить проверку на положительность входного значения. 12. Аналогичным образом выполняется тестирование метода Divide. Однако здесь возможны более хитрые тестовые случаи — деление на 0, равенство одного из входных аргументов double.NaN или double.Infinity. При передаче в метод объекта ссылочного типа также стоит выполнять проверки на равенство объекта null. 13. Выполните тестирование ваших классов проекта Model. Для каж- дого класса создайте отдельный тестирующий класс. Для каждого обще- доступного метода класса напишите тест с позитивными и негативными тестовыми случаями. Позитивные сценарии составьте на основе анализа 67 граничных условий, негативные — исходя из назначения метода. По каж- дому методу ожидается не менее 5 проверяемых тестовых случая. Ис- правьте реализацию методов, если в результате выполнения тестов вы об- наружите ошибку. Учтите, что ошибки возможны и в самих тестах. Будьте внимательны! 68 5 Лабораторная работа № 5. Рефакторинг и сборка установщика Согласно результатам исследований 90 % рабочего времени про- граммист занимается чтением программного кода и только 10 % уходит на его написание. Логично сделать вывод, что такое распределение времени весьма неэффективно с точки зрения разработки программного обеспече- ния, так как всего лишь десятая доля затрат связана с написанием новой функциональности. Следовательно, необходимо менять соотношение в пользу разработки. Одним из таких инструментов является рефакторинг. Рефакторинг — процесс улучшения программного кода с целью улучшения его читаемости разработчиками [1]. Именно цель отличает ре- факторинг от оптимизации кода, где целью является повышение произво- дительности алгоритмов. Повышение производительности, приводит к ис- пользованию нетривиальных численных методов и алгоритмов, что в большинстве случаев, наоборот, приводит к еще более запутанному коду. Рефакторинг, в свою очередь, не рассматривает понятия производительно- сти кода, а стремится реорганизовать программный код для упрощения его понимания. Единственная оговорка, которую следует помнить, что в результате выполнения рефакторинга программа может потерять в производительно- сти, но функционально программа не должна изменяться. То есть при за- данных исходных данных программа должна выдавать такой же результат, что и до рефакторинга, если, конечно, в ходе рефакторинга не было обна- ружено, что исходный код содержал ошибки. Для того чтобы убедиться, что в ходе рефакторинга мы не изменили правильность выполнения программы или её части, можно (и нужно) ис- пользовать юнит-тестирование. Фактически рефакторинг состоит из сле- дующих этапов: 69 1. Покрытие целевого исходного кода юнит-тестами. 2. Проверка правильности выполнения тестов для исходного кода. 3. Изменение исходного кода для повышения его читаемости. 4. Проверка правильности выполнения тестов измененного кода. 5. Если тесты не пройдены, откатить изменения исходного кода или устранить ошибку. Где этапы 3—5 могут многократно повторяться до тех пор, пока ка- чество кода не станет удовлетворительным. На практике, большинство программистов пренебрегают написанием блочных тестов при рефакто- ринге, что довольно часто приводит к возникновению ошибок. В результате проведения рефакторинга повышается читаемость кода, уменьшается его сложность, архитектурно код становится более гибким, его легче модифицировать под расширение новой функциональности. Та- ким образом, рефакторинг решает огромное количество проблем при раз- работке ПО и становится полезной практикой. Подход рефакторинга также широко применяется программистами при разборе чужого исходного кода, так как в процессе рефакторинга программист максимально глубоко по- гружается в принципы работы кода. Более подробно о рефакторинге и ка- честве программного кода можно почитать в книгах Мартина Фаулера «Рефакторинг» [2] и Стивена МакКоннелла «Совершенный код» [3]. 5.1 Задание на лабораторную работу Перед проведением рефакторинга в данной лабораторной работе вы- полним следующее задание. 1. Добавьте на главную форму панель, аналогичную окну AddObject или ModifyObject, которая показывает поля выбранного в таблице объекта. В настоящий момент таблица показывает только основные, общие для всех типов объектов поля, однако для просмотра уникальных полей каждого объекта приходится заходить в окно ModifyObject. Такой подход может 70 привести к случайному изменению данных. Отображение же всех возмож- ных полей в таблице приводит к её избыточности, так как большинство уникальных полей для разных типов дочерних объектов будут всегда пус- тыми. Для решения данной проблемы добавим на главную форму панель, которая показывает все возможные поля выбранного объекта. Таким обра- зом, в таблице отображаются лишь общие поля, однако если пользователь захочет просмотреть более подробную информацию, ему достаточно вы- брать нужный объект в таблице, и его данные появятся на панели. При этом панель не должна предоставлять возможности изменения данных — для этого есть специальное окно ModifyObject. 2. Избавьтесь от дублирования кода, создав пользовательский эле- мент управления ObjectControl. Если вы проанализируете свой программ- ный код, то заметите, что в нём присутствует изрядная доля дублирования. Окна AddObject и ModifyObject, а также ранее добавленная панель имеют одну и ту же верстку и выполняют одну и ту же функциональность. Разни- ца между ними заключается лишь в том, что AddObject создает новый объ- ект, ModifyObject инициализируется ранее созданным объектом, а панель инициализируется ранее созданным объектом, но не позволяет его менять. Такое дублирование избыточно, что в дальнейшем может привести к про- блеме поддержки. Поэтому лучше вынести данную функциональность в отдельный элемент управления, который можно было бы использовать во всех трёх случаях. Пользовательские элементы управления создаются аналогично фор- мам. Для этого нажмите правой кнопкой по проекту в Обозревателе реше- ния, выберите «Создать новый элемент», затем в появившемся окне выбе- рите WinForms UserControl (пользовательский элемент управления WinForms). Как уже упоминалось, пользовательские элементы управления ана- логичны формам, они также разрабатываются из уже готовых элементов 71 управления, например TextBox, Button, ListBox, или элементов, которые вы создали ранее. Однако отличие заключается в том, что формы являются самостоятельными элементами и не могут быть в составе других форм. Для них мы можем вызвать метод Show, и форма отобразится на экране. Пользовательский элемент управления не может быть отображен само- стоятельно, он обязательно должен располагаться на форме для его ото- бражения. Разработайте пользовательский элемент управления ObjectControl. Целью данного элемента является создание и отображение объектов любо- го дочернего типа. Таким образом, элемент должен инкапсулировать про- верки правильности ввода значений соответствующих полей. 3. Добавьте элементу ObjectControl открытое свойство Object. Реали- зуйте свойство Object типа данных Object (где под Object подразумевается ваш базовый класс или интерфейс в бизнес-логике). Логика работы данно- го свойства следующая: если в данное свойство присвоить некоторый объ- ект (аксессор set), то все поля ввода на пользовательском элементе управ- ления должны инициализироваться соответствующими значениями из присвоенного объекта. Таким образом, если в вашем варианте объектом является Person, то при присваивании объекта Person данному свойству все поля ObjectControl должны проинициализироваться данными из при- своенного Person. Если из данного свойства запросить значение (аксессор get), то элемент должен создать экземпляр Person и проинициализировать его значениями из полей ввода. Таким образом, данное свойство значительно упростит работу с дан- ным элементом управления, позволяя либо инициализировать его значе- ниями, либо получать готовый сформированный объект согласно введен- ным значениям. 4. Добавьте элементу ObjectControl открытое булево свойство ReadOnly. Свойство должно работать следующим образом: при присвое- 72 нии в свойство значения true (аксессор set) все поля ввода на пользователь- ском элементе должны стать ReadOnly, т. е. только отображать данные, но не позволять их ввод. При присвоении значения false пользовательский элемент вновь позволит редактировать данные полей. При получении зна- чения свойства (аксессор get) свойство просто возвращает текущее состоя- ние ReadOnly. Данное свойство в итоге позволит использовать данный эле- мент управления для отображения данных, без возможности их изменения. 5. Убедитесь, что у созданного элемента управления кроме двух опи- санных свойств больше нет открытых членов класса. Два описанных свой- ства должны быть единственными способами взаимодействия с данным элементом управления: они позволяют инициализировать поля значениями объекта, получить созданный объект из элемента, а также управлять воз- можностью редактирования. 6. Переделайте пользовательский интерфейс программы с использо- ванием созданного элемента ObjectControl. Перепишите главную форму, формы AddObjectForm и ModifyObjectForm с использованием элемента управления. Учтите, что формы AddObjectForm и ModifyObjectForm также можно объединить в одну форму, и тем самым сократив количество кода. 7. Соберите установщик программы с помощью программы InnoSetup. InnoSetup — программа, позволяющая быстро и удобно соби- рать установщики приложений с помощью скриптов. Изучение написания скриптов для InnoSetup остается на самостоятельное изучение. Программа содержит обширную документацию с реальными примерами скриптов. ПРИМЕЧАНИЕ: для сборки установщика используйте программу, скомпилированную под версией Release, а не Debug, так как Debug-версия содержит лишний промежуточный код, необходимый при отладке. Такая версия будет менее производительной по сравнению с Release-версией. После компиляции проекта все необходимые файлы программы можно бу- 73 дет взять в папке bin\release компилирующегося проекта. Не забудьте уда- лить лишние файлы и оставить только dll и exe. *8. Реализуйте возможность запуска файлов проекта по двойному клику в файловой системе. Ваша программа может сохранять базу данных в отдельный файл. Для пользователя было бы гораздо удобнее, если про- грамму можно было бы запускать по двойному клику файла базы данных непосредственно из файловой системы. Чтобы сделать данную функциональность необходимо: 1. Изменить установочный скрипт InnoSetup. При установке уста- новщик должен добавить в реестр Windows записи о вашем собственном формате файлов и о том, какая программа по умолчанию должна откры- вать данный тип файлов. В вашем случае, программой по умолчанию явля- ется ваша программа. Реализация подобного скрипта представлена в обу- чающих примерах программы InnoSetup. 2. Изменить конструктор главной формы и метод Main для возмож- ности принятия переменных из командной строки. |