Пресловутый TDD
Запись от surlac размещена 24.01.2011 в 20:19
Обновил(-а) surlac 27.01.2011 в 13:24 (global refactoring)
Обновил(-а) surlac 27.01.2011 в 13:24 (global refactoring)
... а функциональность тем временем росла не по дням, а по часам. И решил я, как выходец из мира Java, наконец воспользоваться пресловутым Test Driven Design, который так часто выручал меня.
Введение.
Разработка через тестирование (TDD) - это один из способов получить изящный, гибкий и понятный код, который легко модифицировать, который корректно работает и который не подкидывает своим создателям неприятных сюрпризов [1]. В основе методики TDD (кстати, данная методика входит в состав очень практически направленной методологии управления проектами, как Экстремальное Программирование - XP) лежит парадоксальная идея - "тестируйте программу до того, как она написана". Бессмыслица? Не спешите делать выводы о нецелесообразности идеи вообще и неприменимости её к разработке для Flash-платформы в частности.
Применимость.
Как и любая другая методика XP, TDD не претендует на звание панацеи от всех болезней{1}. Определенно, существуют задачи, которые невозможно решить только при помощи тестов. Так что не спешите закрывать QA-отделы Ваших предприятий .
Тесты возможно писать почти до бесконечности, углубляясь с помощью декомпозиции в детали. Но каков практически значимый предел? Phlip Plumlee: "Пишите тесты до тех пор, пока страх не превратится в скуку". То есть, очень индивидуально для каждого проекта. Я лично считаю, что нужно писать до тех пор, пока тесты не покроют весь критически важный для проекта код, то есть всю функциональность с приоритетами Blocker и Critical, если говорить в терминах систем отслеживания ошибок.
Мотивация.
Как часто Вам приходилось бывать в ситуации, когда буквально за 5 минут до показа продукта заказчику функциональность модуля "падала" из-за поспешных "коммитов" разработчиков другого модуля. С нашей командой такое бывало несколько раз, пока мы не настроили build-сервер в связке с SVN-репозиторием, который через ANT запускал пачку тестов на проверку самих модулей и их взаимодействия. В итоге, да, сроки изначально растягивались, так как нам приходилось писать не только модуль, но и тесты для него. Но это окупалось за счет предсказуемости, надежности и сохранности наших нервов в конце концов!
Реализация.
И так, постепенно переходим от теоретических выкладок к практическим мероприятиям.
Платформа - Flash (повлияет только на выбор инструмента).
В качестве инструмента можно использовать как ASUnit, так и FlexUnit {2}. Я не углублялся в сравнение этих двух тулз, но выбор пал, чисто субъективно, на ASUnit из-за обвинения разрабов FlexUnit в нарушении конвенции xUnit.
Для удобства запуска тестов и разделения логики тестов от тестируемой функциональности (см. п. "2. Файл с настройками для сценария") был выбран Apache ANT {3}. Т.к. я с ним достаточно долго работал - тоже субъективно {4}.
И так, далее в этой статье:
- покажем, как прикрутить ASUnit с помощью ANT
- реализуем небольшой примерчик
1. Сценарий build.xml.
По большому счету, здесь происходит настойка целей, которые запустят mxmlc для компиляции флешки. Эта флешка стартанет тесты и выдаст результаты.
Код:
<property file="build.properties"/> <!-- flex resources --> <property name="mxmlc.jar" location="${flex.dir}/lib/mxmlc.jar"/> <property name="flex.config" location="${flex.dir}/frameworks/flex-config.xml"/> <property name="flex.lib" location="${flex.dir}/frameworks/libs"/> <property name="flextask.jar" location="${flex.dir}/ant/lib/flexTasks.jar"/> <property name="compc.jar" location="${flex.dir}/lib/compc.jar"/> <property name="FLEX_HOME" value="${flex.dir}"/> <!-- testing --> <target name="tests"> <taskdef resource="flexTasks.tasks" classpath="${flextask.jar}"/> <mxmlc file="${test.main}" output="${tests.output}" incremental="true" debug="false" static-link-runtime-shared-libraries="true"> <source-path path-element="${src.dir}"/> <source-path path-element="${tests.dir}"/> <source-path path-element="${asunit.dir}"/> <load-config filename="${flex.config}"/> <library-path dir="${flex.lib}" append="true"> <include name="flex.swc"/> </library-path> <library-path dir="${libs.test.dir}" append="true"> <include name="*.swc"/> </library-path> <keep-as3-metadata name="Inject"/> <keep-as3-metadata name="Test"/> <keep-as3-metadata name="Suite"/> <keep-as3-metadata name="Before"/> <keep-as3-metadata name="BeforeClass"/> <keep-as3-metadata name="After"/> <keep-as3-metadata name="AfterClass"/> <keep-as3-metadata name="RunWith"/> <keep-as3-metadata name="Ignore"/> </mxmlc> <exec executable="cmd.exe" osfamily="windows"> <arg line='/C start ${tests.output}'/> </exec> </target>
2. Файл с настройками для сценария. build.properties.
Как видно их сценария, для тестов используется две директории src.dir и tests.dir. Первый определяет что тестировать, второй - как тестировать. Это используется для отделения логики тестирования, от самих модулей.
Код:
###################################### ## project properties ###################################### source.file=ru/pkg/Example.as output.file=Example.swf deploy.dir=deploy source.dir=src ###################################### ## tools ###################################### # where you installed flex: flex.dir=D:/flex_sdk_4.1 # browsers firefox=C:/Program Files/Mozilla Firefox/firefox.exe ie=C:/Program Files/Internet Explorer/iexplore.exe compiler=bin/mxmlc.exe saplayer=player/debug/SAFlashPlayer.exe flashdevelop=D:/FlashDevelop/FlashDevelop.exe # Build locations src.dir=${basedir}/src package.dir=ru/pkg libs.dir=${basedir}/libs output.dir=${basedir}/bin output.swc=${output.dir}/${project.name.versioned}.swc # testing tests.dir=${basedir}/tests tests.output=bin/tests.swf libs.test.dir=D:/asunit/asunit-4.0/lib/ asunit.dir=D:/asunit/asunit-4.0/src/ test.main=${tests.dir}/${package.dir}/AllTestsRunner.as docs.dir=${basedir}/docs
Сам тест-кейс. Хранит методы для тестирования. Рекомендуется наследовать от TestCase, т.к. он агрегирует класс Assert и оборачивает его методы.
В качестве примера, протестируем обработчик абсолютно упругого столкновения RecoilProcessor для шаров произвольного радиуса. Создадим искусственную ситуацию, когда шарик движется по оси OX с заданной скоростью и сталкивается с вертикальной стеной.
package ru.pkg.core { import asunit.framework.*; import ru.pkg.core.*; public class RecoilProcessorTest extends TestCase { private var instance:RecoilProcessor; //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- // public methods //-------------------------------------------------------------------------- [test] public function testOne():void { // создаем экземпляр шарика var circle:FloatingCircle = new FloatingCircle(0xFFFFFF, 10); // задаем скорость по осям circle.speedByX = 5; circle.speedByY = 0; // устанавливаем искусственно точку столкновения (point of collision) var poc:Point = new Point(10, 0); // RecoilProcessor высчитывает новые скорости по осям и возвращает в виде Point. // т.к. шарик двигался горизонтально и столкнулся с крайней правой точкой шарика, // то отскок произойдет в противоположную сторону со скоростью -5 по OX и 0 по OY. var resultPoint:Point = instance.calcNewSpeed(circle.speedByX, circle.speedByY, poc.x, poc.y, circle.radius); // ВНИМАНИЕ! Проверка. Результаты вычисления новых скоростей RecoilProcessor'а сравниваются с // заранее верными значениями. Если возвращаемые значения совпадают с -5 и 0 - тест // завершается успешно, иначе - Fail. assertEquals(-5, resultPoint.x); assertEquals(0, resultPoint.y); } //-------------------------------------------------------------------------- // protected methods //-------------------------------------------------------------------------- protected override function setUp():void { // Здесь происходит первоначальная настройка теста. instance = new RecoilProcessor(); } protected override function tearDown():void { // Убираем за собой после выполнения теста. instance = null; } } }
4. Suite
Suite-класс, хранящий все тест-кейсы. Насколько я понял, движок asunit создает TestCase объекты, объявленные здесь и инжектит их в переменные объекта Suite.
package ru.pkg { import ru.pkg.core.RecoilProcessorTest; [Suite] public class AllTests { public var _RecoilProcessorTest:RecoilProcessorTest; } }
Главный класс для запуска Suite. Должен быть наследником DisplayObject, т.к. выводит результаты на экран. TextCore настраивает объектами классов Suite и TextPrinter ядро asunit - AsUnitCore.
package ru.pkg { import asunit.core.TextCore; import flash.display.Sprite; [SWF(width='1000',height='800',backgroundColor='#333333',frameRate='31')] public class AllTestsRunner extends Sprite { private var core:TextCore; public function AllTestsRunner() { core = new TextCore(); core.start(AllTests, null, this); } } }
Заключение.
Это конечно же лишь тонкий намек на то, что на самом деле умеет ASUnit, как инструмент методики TDD. Лишь введение в TDD со стороны Flash-платформы и небольшой толчок в сторону применения методик Экстремального Программирования.
Перспективы.
В перспективе рассмотреть такие вещи, как:
- асинхронное тестирование - для тестирования клиент-серверного взаимодействия, например.
- использование ASUnit в связке с build-сервером для непрерывной интеграции.
- текущее положение дел FlexUnit и его применение.
Литература:
1. К. Бек "Экстремальное программирование: разработка через тестирование".
2. L. Cripsin, J. Gregory "Agile testing: a practical guide for testers and agile teams".
Сноски:
1. Инфицированная тестами система - определение, придуманное Эрихом Гаммой (Erich Gamma).
2. Нужно заметить что у обоих претендентов "ноги растут" из старого доброго JUnit для Java, это просто порты на AS3.
3. Не будем здесь описывать тонкости настройки ANT, для этого существует множество хорошей документации.
4. Я считаю, что для Flash-платформы, инструмента ANT будет хватать с лихвой. Врядли тут понадобится централизованное хранение либ, как в случае Maven, для того, чтобы не пихать либы в SVN, а хранить их в отдельном глобальном или локальном mvn-репозитории.
Всего комментариев 10
Комментарии
24.01.2011 22:07 | |
Спасибо! Занятно будет почитать) а то что то эта аббревиатура начала все чаще мелькать перед глазами.
|
24.01.2011 22:40 | |
Бессистемная статья. Расскажите, пожалуйста, зачем заворачивать UnitTest в ant? Вы даже толком (ссылками) не объяснили про этот инструмент. Зачем даете описание опций компилятора? Где объяснение непоняток? Что такое "функционал", наконец?
Нужно работать. |
25.01.2011 03:53 | |
Вот мне, как человеку не знающему зачем TDD нужен, после такой статьи ещё меньше захотелось знать об этом. Меня пугает, то, что для сравнения пяти с пятью нужно столько знать.
|
25.01.2011 13:49 | |
Во флеше TDD не нужен. Слишком низкие стандарты качества.
|
25.01.2011 17:23 | |
Странное заявление. Как будто технология задаёт стандарты качества? Скорее во флеше TDD мало кто используют ибо не умеют и не удобно для конкретных проектов.
По большому счёту я хоть и пробовал использовать Unit тесты, мне они не помогали, а только мешали ибо моя область деятельности клиент/сервер и соответственно основные ошибки трудно отлавливаются на момент компиляции. Тут в дело вступает логирование и багрепорты. В качестве юниттестеров выступают люди, автоматизация такого тестирования может быть дороже и сложнее самого тестируемого контента. А насчёт статьи, хотелось бы больше информации об ант, как ставить, как работать в FB/FD/Idea, как писать свои сценарии, синтаксис, настройка и т.п. |
|
Обновил(-а) Котяра 25.01.2011 в 17:53
|
25.01.2011 17:37 | |
Цитата:
хотелось бы больше информации об ант, как ставить, как работать в FB/FD/Idea, как писать свои сценарии, синтаксис, настройка и т.п.
|
25.01.2011 20:24 | |
Благодарю за толковые комментарии, особенно dimarik.
Цитата:
Сообщение от Котяра
А насчёт статьи, хотелось бы больше информации об ант, как ставить, как работать в FB/FD/Idea, как писать свои сценарии, синтаксис, настройка и т.п.
Цитата:
Сообщение от Rzer
Вот мне, как человеку не знающему зачем TDD нужен, после такой статьи ещё меньше захотелось знать об этом. Меня пугает, то, что для сравнения пяти с пятью нужно столько знать.
Другое дело, что само управление проектами и TDD в частности имеет максимум эффективности при достаточно больших командах (3 до 12 чел - см. "Scrum and XP from the Trenches") и при циклах разработки от 2 до 6 недель (т.е достаточно большой проект). Согласен с Вами в том, что если проект небольшой - максимума эффективности от TDD мы не добьемся. Цитата:
Сообщение от †‡Paladin‡†
Во флеше TDD не нужен. Слишком низкие стандарты качества.
|
26.01.2011 16:43 | |
Цитата:
Как будто технология задаёт стандарты качества?
Цитата:
Нужен/не нужен определяется индивидуально для каждого проекта. Само собой Вы можете напрочь отказаться от инструментов, позволяющих успешно завершать проекты и делать всё по-своему. Без проблем. И это работает, но только для небольших проектов и команд.
|
|
Обновил(-а) †‡Paladin‡† 26.01.2011 в 16:46
|
26.01.2011 23:33 | |
Цитата:
Нужен/не нужен определяется индивидуально для каждого проекта. Само собой Вы можете напрочь отказаться от инструментов, позволяющих успешно завершать проекты и делать всё по-своему. Без проблем. И это работает, но только для небольших проектов и команд.
Цитата:
Вот мне, как человеку не знающему зачем TDD нужен, после такой статьи ещё меньше захотелось знать об этом. Меня пугает, то, что для сравнения пяти с пятью нужно столько знать.
Автору: От теста "assertEquals(5, 5)", даже не "assert(2 + 2, 4)", сразу переходить к непрерывной интеграции?? Не слишком ли круто? Может лучше рассмотреть создание конкретного класса с помощью тестов? Оффтоп: surlac, а как вы ant-файлы запускаете, через коммандную строку или в IDE горячую клавишу настроили? |
|
Обновил(-а) expl 26.01.2011 в 23:52
|
27.01.2011 18:38 | |
Цитата:
Сообщение от expl
От теста "assertEquals(5, 5)", даже не "assert(2 + 2, 4)", сразу переходить к непрерывной интеграции?? Не слишком ли круто?
Цитата:
Сообщение от expl
Может лучше рассмотреть создание конкретного класса с помощью тестов?
Цитата:
Сообщение от expl
Оффтоп:
surlac, а как вы ant-файлы запускаете, через коммандную строку или в IDE горячую клавишу настроили? |
Последние записи от surlac
- Пресловутый TDD (24.01.2011)