автореферат диссертации по информатике, вычислительной технике и управлению, 05.13.12, диссертация на тему:Разработка высокоуровневых веб-приложений с использованием методологии "Разработка через тестирование"

кандидата технических наук
Прохоров, Алексей Сергеевич
город
Москва
год
2011
специальность ВАК РФ
05.13.12
Автореферат по информатике, вычислительной технике и управлению на тему «Разработка высокоуровневых веб-приложений с использованием методологии "Разработка через тестирование"»

Автореферат диссертации по теме "Разработка высокоуровневых веб-приложений с использованием методологии "Разработка через тестирование""

На правах рукописи

005001663

Прохоров Алексей Сергеевич

РАЗРАБОТКА ВЫСОКОУРОВНЕВЫХ ВЕБ-ПРИЛОЖЕНИЙ С ИСПОЛЬЗОВАНИЕМ МЕТОДОЛОГИИ «РАЗРАБОТКА ЧЕРЕЗ ТЕСТИРОВАНИЕ» (НА ПРИМЕРЕ РНРШк)

Специальность 05.13.12. - Системы автоматизации проектирования

(по отраслям)

Диссертация в виде научного доклада на соискание ученой степени кандидата технических наук

1 о НОЯ 2011

МОСКВА 2011

005001663

Научный руководитель

кандидат физико-математических наук Миняев Павел Михайлович

Официальные оппоненты: доктор технических наук, профессор,

академик РАЕН, ВАН Попов Павел Георгиевич, доктор физико-математических наук, профессор Харченко Сергей Григорьевич

Ведущая организация Российская академия естественных наук

Защита состоится 10 сентября 2011 г. в 12 часов на заседании Диссертационного совета Д (05) при Международной академии образования по адресу: 170100, г. Тверь, ул. Московская, д.1.

С диссертацией можно ознакомиться в Российской государственной библиотеке, Национальной государственной библиотеке. Автореферат разослан «05» августа 2011 г.

Ученый секретарь Диссертационного совета,

кандидат экономических наук, доцент

/Кочетова И.Н./

ОБЩАЯ ХАРАКТЕРИСТИКА РАБОТЫ

Актуальность темы диссертационного исследования. Проблема разработки и обеспечения качества программного обеспечения не нова, но мало кто станет спорить с ее важностью. Многие, даже опытные, программисты тратят огромное количество времени на создание действительно качественного программного обеспечения. За 25 лет существования Интернета в качестве популярной среды обмена информацией программисты далеко продвинулись в развитии принципов программирования и поддержания качества программного обеспечения от момента начала разработки до критических моментов внедрения.

Разработка высококачественных программных приложений - задача нетривиальная. Она требует интенсивной поддержки и серьезных финансово-временных затрат от разработчика. Зачастую проблемы, связанные с разработкой высококачественных приложений, могут негативно влиять на программные системы, процессы, связи и даже организационные структуры.

По мнению многих современных разработчиков, карликовые темпы работы многих высоконагруженных программных систем не отвечают вызовам массовости пользователя современного Интернета. Хотя многие из этих новых систем критически важны только для бизнеса, в реальности они от этого не становятся менее критическими. Наоборот, в последнем случае мы зачастую имеем дело с дополнительными уровнями сложности; к ним относятся: распределенные команды разработчиков, требование к соблюдению веб-стандартов, потребность в интернационализации, ограниченные сроки реализации в БааЗ1 и многое другое.

В дополнение ко всему вышеизложенному заметим, что в приложениях для электронной коммерции, где простои в работе непосредственно сказываются на прибыли, требование мощного обеспечения качества программы

' http://ru.wikipedia.org/wiki/Software_on-demand

приобретает критическое значение. Эти проблемы требуют особого внимания к гибкости приложения. Короткие сроки для исправления ошибок (плюс время, чтобы проверить и развернуть исправленное ПО), а также возможность работы непрерывных транзакций в режиме реального времени, чтобы обеспечить не только работу приложения, но и реальность осуществления транзакций.

Кроме того, рост потребностей среднего Интернет-пользователя также означает, что ожидание высокого качества от приложения становится все более значимым. И если информационная система не обеспечивает пользователя тем, что он желает получить, то функциональность приложения должна быть увеличена в очень короткий промежуток времени без ущерба для качества. Адекватность работы системы поддержки столь быстрого оборота изменений должна быть на высоком уровне.

Таким образом, необходимость определения наиболее эффективного подхода к обеспечению качества веб-приложений является очень актуальной задачей.

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

Степень разработанности проблемы. К сожалению, исследования, посвященные проблеме разработки высококачественных веб-приложений практически отсутствуют в отечественной литературе. Возможно, это связано с тем, что веб-сигмент программной разработки в нашей стране возник относительно недавно, и, первоначально, привлек большое число программистов-дилетантов, чем был сильно дискредитирован в глазах профессиональных разработчиков. В связи с этим, научное сообщество про-

фессиональных теоретиков веб-программирования в нашей стране еще находится на этапе становления.

Наоборот, зарубежное научное сообщество уже довольно долгое время занимается изучением вопросов обеспечения качества программного обеспечения для Интернета. Можно привести немало примеров дискуссий в сообществе программистов, посвященных указанной проблеме. Однако большинство разработчиков склоняются к мнению, что основным необходимым условием создания высокопроизводительных веб-приложений является тестирование.

Приведем несколько мнений программистов с мировыми именами. Мэри Поппендик (Mary Poppendieck) считает, что создание тестов перед написанием кода является одним из двух самых эффективных и производительных изменений процесса повышения качества кода2. Джефф Сазерленд (Jeff Sutherland), один из создателей Scrum3, замечает, что проведение тестов прежде внедрения приложения в производство удваивают производительность команды: «Фактические результаты могут варьироваться, но команды, использующие TDD4, переживают увеличение производительности и качества»5. «Сотрудничество клиента-разработчика-тестера уменьшает количество ненужных циклов в процессе развития», - считает Кен Пуг (Ken Pugh)6. Как пишут Джерри Вайнберг (Jerry Weinberg) и Дон Гэюз (Don Gause): «Удивительно, что один из самых эффективных способов проверить условия программы, - это протестировать ее как завершенную систему»7. Тестирование - это «авторитетное и надежное начало того, что

2 http://www.poppendieck.com/design.htm

3 http://ru.wikipedia.org/wiki/Scrum

4 http://mwikipedia.org/wikWa3pa6orca_Hepe3_TeCTHpoBaHHe

«^Se acceptance test driven development: better software through collaboration, Pearson

^GaiKe°Donai(fc,'and Gerald M. Weinberg. Exploring Requirements: Quality Before Design. Dorset House

Publishing Company, 1989.

программное обеспечение должно будет сделать функционально», - говорит Гойко Аджич (Gojko Adzic)8.

И все-таки, несмотря на то, что проблема создания высококачественных программных приложений через тестирование широко обсуждается в зарубежном научном сообществе, в российской науке на сегодняшний день фактически не существует последовательных трудов на эту тему. В связи с этим, данное исследование стремится восполнить указанную лакуну.

Объектом исследования является среда модульного тестирования PHP программ - PHPUnit. Написанный Себастианом Бергманом (Sebastian Bergmann), этот фреймфорк на сегодняшний день является фактическим стандартом тестирования веб-приложений на PHP.

Предметом исследования определяется методология модульного тестирования в применении к разработке высокопроизводительных веб-приложений.

Методы исследования, применяемые в работе, основаны на теории информации, теории надежности, методах оптимизации. В качестве общего теоретического подхода применяется системный анализ. Для моделирования естественного функционирования программных приложений в искусственной среде используется язык PHP.

Целью диссертационной работы является исследование типов, методов и алгоритмов разработки высокопроизводительных веб-приложений на основе тестирования с целью поиска ошибок в исходном коде программы.

Для достижения поставленной цели решены следующие задачи:

8 Adzic, Gojko. Bridging the Communication Gap'. Specification by Example and Agile Acceptance Testing. Neuri Limited, 2009.

- анализ экономических преимуществ «разработки через тестирование»;

- анализ существующих типов, методов и алгоритмов тестирования ПО и выявление возможности их использования применительно к программированию;

- разработка тестов с учетом особенностей среды функционирования веб-приложений;

- оптимизация процедуры тестирования;

- программная реализация выявленных алгоритмов тестирования;

- применение полученных в диссертации научных результатов на практике.

Научная новизна работы состоит в следующем:

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

2. Сформулированы преимущества методов модульного тестирования для повышения эффективности качества разработки и последующего функционирования программных продуктов, а так же удобства использования веб-приложений конечными пользователями.

3. Предложен новый подход к практической разработке PHP приложений. В сравнении с существующими принципами создания веб-приложений, этот подход учитывает экономические аспекты в работе программиста и его взаимоотношениях с потенциальными клиентами, а так же ориентируется на вопросы долговременной перспективы поддержки конечного продукта.

Результаты исследования, полученные лично автором:

1. Осуществлен системный анализ признаков, характеризующих понятие качества в Интернет среде.

2. Доказана обоснованность применения Объектно-ориентированного подхода в разработке Интернет-программ относительно к языку PHP.

3. Исследовано состояние подходов к повышению производительности и функциональности веб-приложений

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

Положения, выносимые на защиту:

1. Концепция использования языка PHP в качестве одного из основных языков программирования для Веб.

2. Экономическая и практическая целесообразность применения парадигмы «Разработка через тестирование» в отношении создания интернет-приложений.

3. Использование фреймворка PHPUnit в качестве наиболее эффективного инструмента модульного тестирования РНР-программ.

Теоретическая и практическая значимость. Полученные в диссертационной работе результаты могут использоваться для решения практических задач тестирования программного обеспечения событийно-управляемых систем, а также для тестирования функциональности программ с точки зрения пользовательского интерфейса. Результаты настоящей работы были использованы при разработке веб-сайтов в студии веб-дизайна «Danmer» г. Тверь.

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

Апробация результатов исследования. Ряд методологических и методических принципов и положений выполненного исследования нашли отражение в рекомендациях научно-практической конференции, проводимых в рамках специальности «Компьютерные и телекоммуникационные технологию) в Армавирском лингвистическом социальном институте (2009, 2010), докладывались на научно-тематических конференций Московского института инновационных технологий (2010, 2011), на семинарах в Международной академии образования, Российской академии естественных наук, Верхневолжской инженерной академии (2009-2011) и др.

Структура работы. Диссертация состоит из введения, трех глав, заключения и списка литературы, включающего 83 наименования. Работа изложена на 96 страницах, содержит 1 таблицу и 44 иллюстрации кода.

ОСНОВНОЕ СОДЕРЖАНИЕ РАБОТЫ

Согласно исследованию сообщества программистов ТЮВЕ, PHP является самым популярным языком программирования после C/C++ и Java9. В свете этого компания Gartner10 прогнозирует, что динамические языки программирования будут оказывать решающее влияние на выбор многих следующих поколений разработчиков приложений, и видит PHP в качестве одного из наиболее мощных представителей этого типа языков программирования".

С самого начала PHP был предназначен для разработки веб-приложений и был, вероятно, одной из движущих сил Интернет-бума на рубеже тысячелетий. С тех пор PHP «созрел» до языка общего назначения, который поддерживает как процедурный, так и объектно-ориентированный стиль программирования. В прошлые годы, такие темы, как производительность, масштабируемость и безопасность, горячо обсуждались в РНР-сообществе. Однако в последние годы все больше внимания уделяется архитектуре и качеству. Исходя из практического опыта, мы можем заметить, что все большее количество сообществ программистов стремится модернизировать свое программное обеспечение на PHP, основывая подход к разработке на принципах Agile12 и TDD13. Модернизация базы кода, как правило, обусловлена миграцией с РНР4 на РНР5 и/или внедрением фреймворка14 для стандартизации разработки.

В связи с этим неудивительно, что существует большое количество РНР-фреймворков. Все фреймворки призваны помочь с решением проблемы

'"ЛОВЕ Software BV, "TIOBE Programming Community Index," данные на начало 2011года Kttp://www.tiobe.com/index.php/content/paperinfo/tpci/mdex.htm]

10 http://ru.wikipedia.org/wiki/Gartner

" Gartner Inc., "Dynamic Programming Languages Will Be Critical to the Success of Many Next-Generation AD Efforts," 2008, accessed April 10,2010, http://www.gartner.com/ DisplayDocument?ref=g_search&id=832417.

12 http://ru.wikipedia.org/wiki/Agile

" http.//ru.wikipedia.0rg/wiki/Pa3pa60TKa_4epe3_TecTHp0BaHHe

14 http://ruwikipediaorg/wiki/Framework

повторения кода и стандартизации разработки. Динамические и статические методы тестирования, также как и автоматизация построения приложений и непрерывная интеграция, больше не являются чуждыми понятиями для PHP-разработчиков. Особенно в критически-нагруженных приложениях, простое PHP-программирование превращается в разработку программного обеспечения на PHP.

ГЛАВА 1

ПОНЯТИЕ «КАЧЕСТВА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ»

Данное исследование посвящено вопросам обеспечения качества РНР-проектов. Что обычно подразумевается под термином «качество программного обеспечения»? Одним из примеров модели качества программного обеспечения является предложенная Hewlett-Packard идеология FURPS (функциональность, юзабилити, надежность, производительность, поддерживаемость)15. Хотя модель FURPS применима ко всем видам программного обеспечения, ее можно дополнить атрибутами качества, относящимися к веб-приложениям, а именно: находимость, доступность и правовое соответствие16.

Как замечает Питер Лиггисмеер (Peter Liggesmeyer), обеспечение качества компьютерных программ является многогранной темой: «Каждая компания, разрабатывающая программное обеспечение, будет пытаться достигнуть максимально возможного качества. Но эта цель может быть ясно достигнута только тогда, когда четко определено, чем термин «максимально возможное качество» не является. Программное обеспечение многогранно, то есть, понятие «качество программного обеспе-

,s Robert Grady and Deborah Caswell, Software Metrics: Establishing a Company-wide Program (Prentice Hall, 1987. ISBN 978-0138218447).

16 Klaus Franz, Handbuch zum Testen von Web-Applikationen (Springer, 2007. ISBN 978-3-54U-24539-1).

чения» включает в себя многие стороны. Не все из ннх одинаково важны для пользователей и производителей программного обеспечения»17. Таким образом, взгляд пользователя на качество программы, отличается от взгляда производителя. Можно согласиться с точкой зрения Нила Бевана (Nigel Bevan), определяющего наличие разницы между внешним и внутренним качеством программ18.

Внешнее качество

Клиенты или конечные пользователи приложения, сосредотачивают свое внимание на тех аспектах достоинства программы, которые имеют для них существенное значение. Согласно мнению Себастьяна Бергмана (Sebastian Bergmann) эти аспекты являются показателями внешнего качества приложений19:

• Функциональность означает, что приложение фактически может выполнять предлагаемые задачи.

• Юзабилити означает, что пользователь может работать эффективно, удобно и при этом еще получать удовлетворение от работы приложения. Доступность является частью юзабилити.

• Ре-активность (reactivity) подразумевает короткое время отклика программы. Это особенно важно, если производитель желает, чтобы пользователь был удовлетворен работой программы.

• Безопасность приложения является одним из наиболее важных факторов для востребованности программы.

• Доступность и надежность особенно важны для веб-приложений с большим количеством пользователей. Приложения должны быть способ-

" Peter Liggesmeyer, Software-Qualität: Testen, Analysieren und Verifi zieren von Software, 2. Aufl age (Spektrum Akademischer Verlag, 2009. ISBN 978-3-8274-2056-5).

18 Nigel Bevan, "Quality in use: Meeting user needs for quality," Journal of Systems and Software 49, Issue 1 (December 1999): 89-96,ISSN0164-1212.

15 Sebastian Bergmann, Stefan Priebsch - Real-World Solutions for Developing High-Quality PHP Frameworks and Applications.

ны выдерживать большие нагрузки и обязаны работать даже в нестандартных ситуациях.

Все аспекты внешнего качества могут быть проверены путем тестирования приложения в целом, с использованием, так называемых, «непрерывных» (епсЬш-епс!) тестов. Применение тестирования позволяет в автоматическом режиме проверять, что программный продукт выполняет все свои функциональные требования.

Для повышения ре-активности приложения, мы должны измерять время отклика. Необходимо использовать специальные инструменты и техники, чтобы оптимизация программы давала максимальный выигрыш производительности при минимальном объеме работы и сохранении низкой стоимости. Еще на этапе планирования мощностей, разработчики и администраторы должны определить потенциально опасные места в приложении, если вдруг изменится программное окружение или увеличится трафик. Необходимо изначально обладать этими данными, чтобы гарантировать качество приложения с учетом его доступности и надежности в долгосрочной перспективе.

Внутреннее качество

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

Внутреннее качество программного обеспечения практически незаметно для клиентов и конечных пользователей. Конечные пользователи ожидают, что программа будет удовлетворять всем или, по крайней мере, большин-

большинству их функциональных ожиданий, а также быть простой в использовании. Фактически, если при использовании продукт работает достаточно быстро, большинство клиентов будут удовлетворены.

Однако недостатки внутреннего качества программы проявляются во временной перспективе. Требуется больше времени для исправления даже тривиальных ошибок. Любые изменения или дополнения к программному обеспечению требует огромных усилий. В итоге, разработчик рано или поздно начинает просить увеличить бюджет на чистку и рефакторинг20 кода. А так как клиенты или начальство обычно не понимают в чем польза рефакторинга, эти запросы чаще всего остаются без внимания.

Автоматизированные тесты индивидуальных программных модулей (юнит-тесты) позволяют немедленно отслеживать появление новых ошибок, которые могут возникнуть при изменении кода. Без автоматизированных тестов рефакторинг кода был бы очень трудной работой.

Основная цель обеспечения качества, а точнее, управления качеством программного приложения, - сделать затраты и выгоды прозрачными для всех сторон, участвующих в создании ПО. Низкое внутреннее качество программы в долгосрочной перспективе вызывают дополнительные затраты. Если эти затраты могут быть определены количественно, то можно сделать вывод, что достижение лучшего внутреннего качества приведет к снижению затрат. Это, видимо, единственный способ заставить начальство или клиента рассмотреть вопрос о выделении бюджета для рефакторинга кода.

Технический долг

Вард Канингем (Ward Cunningham), автор термина «технический долг», пишет: «Хотя незрелый код может хорошо работать и быть полностью

20 Рефакторинг означает изменение внутренней структуры программного кода, без изменения его внешнего поведения.

приемлем для клиента, избыток кода сделает программу неисправляемой, что приведет к увеличению нагрузки на программное окружение и, наконец, невозможности изменения конечного продукта. Предоставление «сырого» кода равносильно залеганию в долги. Небольшой долг ускоряет разработку, но только до тех пор, пока, наконец, не возникнет необходимость оперативно переписать программу. В результате, каждая минута, которая тратится на исправление «не-совсем-правильного» кода, становится равносильной процентам по долгу. В конечном итоге, за «долги»

единственного ленивого программиста будет расплачиваться весь инже-

21

нерно-технический состав предприятия» .

Канингем сравнивает плохой код с финансовым кредитом, имеющим высокую процентную ставку. Кредит может стать хорошей идеей, если он поможет проекту развиваться быстрее. Однако если кредит не возвращается, рефакторинг основного кода и, следовательно, улучшение внутреннего качества, значительно увеличивает сумму дополнительных расходов в виде процентов, накапливающихся с течением времени. В конце концов, выплата процентов уменьшит финансовую базу на столько, что наконец, придется объявить о банкротстве. Перенося вышесказанное на разработку программного обеспечения, мы можем сказать, что приложение становится непригодным обслуживания. Любое небольшое изменение кода становится настолько дорогим, что поддержание кода уже экономически нецелесообразно.

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

7' Ward Cunningham, "The WyCash Portfolio Management System," March 26,1992, accessed April 17, 2010, http://c2.com/doc/oopsla92.htm!.

кращении расходов по проекту и уменьшение времени выхода на рынок, разработчики не имеют возможности предоставить высококачественный код. Однако в долгосрочной перспективе клиент вынужден значительно больше потратить на обслуживание.

Очень важно для каждого программного проекта, а особенно для стороннего проекта, не только определить критерии в отношении внешних качеств, но и обратить внимание на уровень внутреннего качества. Конечно, это требует выделения несколько большего бюджета, так что у разработчиков появляются финансовые причины для того, чтобы обратить внимание на внутреннее качество.

Расходы на поддержку программного обеспечения, как правило, значительно недооцениваются. Время разработки среднего программного проекта может занимать один или два года, но готовое приложение может работать в течение десятилетий. «Проблема 2000 года» доказала, что многие приложения находятся в рабочем состоянии гораздо дольше, чем первоначально ожидалось. Приложения, которые требуют частых изменений, требуют и самых больших затрат на эксплуатацию и техническое обслуживание. Веб-приложения, как известно, требуют частых изменений, что является одной из причин, почему многие разработчики выбирают динамические языки типа PHP для их реализации.

Другие приложения, например, работающие на мэйнфреймах или АТС, которые должны быть постоянно доступными, изменяются редко. В то время как один релиз в квартал может показаться излишне частым для такого рода приложений, многие веб-приложений требуют по нескольку релизов в месяц.

Рон Джеффрис напоминает нам, что ускорение разработки за счет принесения в жертву внутреннего качества является плохой идеей: «Если экономия на качестве дает нам возможность работать быстрее, это явное свидетельство того, что существуют возможности для быстрого улуч-

шения нашей способности обеспечить качество»22. Очевидно, что значение внутреннего качества соотносится с растущим числом изменений в приложении.

График 1-1 показывает, что относительная стоимость исправлений на стадии кодирования проекта в 10 раз, а на стадии эксплуатации - в 100 раз, больше, чем на стадии планирования. Это доказывает, что попытка сократить расходы, экономя на задачах разработки программного обеспечения с экономической точки зрения просто глупо.

>150х

Технические Дизайн Кодирование Тесты Приемочные Эксплуатация

требования разработчиков тесты

Рисунок 1-1: Относительная стоимость исправлений23 Конструктивное обеспечение качества

Многие популярные модели совершенствования процессов разработки, такие как «Capability Maturity Model Integration» (CMMI)24 или «Software Process Improvement and Capability Determination» (SPICE)25 предлагают ограниченный набор решений для обеспечения качества приложений, поскольку исключают тестирование. Все уровни, которые CMMI и SPICE

22 Ron Jefferies, "Quality vs Speed? 1 Don't Think So!" April 29, 2010, accessed May 1,2010, http:// xprogramming.com/articles/quality/.

" Barry Boehm, Ricardo Valerdi, and Eric Honour, "The ROI of Systems Engineering: Some Quantitative Results for Software-Intensive Systems," Systems Engineering 11, Issue 3 (August 2008): 221-234, ISSN 1098-1241.

24 http://ra.wikipedia.org/wiki/CMMI

25 Malte Foegen Mareike Solbach und Claudia Raak, Der Weg zur professionellen IT: Eme praktische Anleitung für das Management von Veränderungen mit CMMI, ITIL oder SPICE (Springer, 2007. ISBN 978-3-540-72471-1).

предлагают в отношении структуры и организации процесса являются необходимыми для успешности анализа уже готового программного обеспечения. Курт Шнайдер (Kurt Schneider) считает конструктивное обеспечение качества, «мерами, направленными на усиление отдельных аспектов программного обеспечения вместо, логичных в данном случае, проверки и коррекции))26.

Идея, что избегать ошибок лучше, чем искать и исправлять их после, не является новой. Дейкстра (Edsger W. Dijkstra) писал еще в 1972 году: «Тот, кто хочет получить действительно надежное программное обеспечение, обнаружит, что необходимо найти способ избежать большинства ошибок с самого начала, и в результате процесс программирования станет дешевле. Если вы хотите получить более эффективную работу от программистов, необходимо понять: вместо того, чтобы после тратить свое время на отладку - программисты не должны плодить ошибки с самого начала»21.

Один из основных способов предотвращения написания «дефектной» программы - это «сперва-тест» программирование (test-first programming)28. «Сперва-тест» программирование является технической практикой, которая позволяет конструктивно обеспечивать качество в форме написания тестирования кода, прежде чем писать код самой программы. Разработка через тестирование (Test-Driven Development), которая основана на идеологии «сперва-тест» программирования, в идеале подразумевает следующее:

• Весь рабочий код должен быть мотивирован тестом. Это снижает риск написания ненужного в работе кода.

26 Kurl Schneider, Abenteuer Softwarequalität—Grundlagen und Verfahren für Qualitätssicherung und Qualitätsmanagement (dpunkt. vertag, 2007. ISBN 978-3-89864-472-3).

Edsger W. Dijkstra, "The humble programmer," Communications of the ACM 45, Issue 10 (October 1972): 859-866. ISSN 0001-0782.

28 Термин взят отсюда Sebastian Bergmann, Stefan Priebsch - Real-World Solutions for Developing High-Quality PHP Frameworks and Applications.

• Весь рабочий код прикрывается, по крайней мере, одним тестом

(прикрытие кода).

• Рабочий код - это тестируемый код, и потому - это чистый код.

. Из-за того, что код не может быть протестирован, возникает внутреннее «ощущение дискомфорта», что мотивирует программиста изменять

плохой код через рефакторинг.

Программные исследования, как например, Дэвида С. Янзена (David S. Janzen)29, показывают, что разработка через тестирование может привести к значительному повышению производительности труда разработчиков и улучшению качества программного обеспечения.

Конструктивное определение качества и нормальная разработка программного обеспечения не могут быть разделены. Объектно-ориентированное программирование и использования шаблонов проектирования улучшают адаптируемость программного обеспечения. Написание чистого кода, а также такие понятия, как трехслойная архитектура или модель-представление-контроллер, при правильном использовании, ведут к значительным улучшениям в области тестируемости, поддерживаемости и возможности повторного использования отдельных компонентов программного обеспечения.

Чистый код

В своей книге «Чистый код» Роберт С. Мартин (Robert С. Martin) среди прочего отвечает на вопрос «что такое чистый код?»: «Чистый код может быть прочитан и расширен разработчиком, без консультации с его автором. Он имеет модульные и приемочные тесты. Он обладает значимыми именами классов, методов, переменных и т.д. Он предлагает один, а не несколько путей, чтобы выполнить одно действие. Он имеет минималь-

29 David S. Janzen, Software Architecture Improvement through Test-Driven Development (University of Kansas, Electrical Engineering and Computer Science, Lawrence, Kansas, USA, 2006).

ный набор зависимостей, которые явно определены, и обеспечивает четкий и минималистичный API, Код должен быть грамотным по отношению к языку, и вся необходимая информация может быть ясно выражена в коде»30.

Стив Фриман и Нат Прайс (Steve Freeman and Nat Pryce) добавляют сюда, что хорош тот код, который легко протестировать: (.(.Модульные тесты для классов должны быть простыми. Класс должен иметь явные зависимости, которые могут легко быть заменены, и четко определенные обязанности, которые могут легко быть выполнены и проверены. В программно-технических терминах, это означает, что код должен быть гибкосвязанным и высокосплоченным, - иными словами, хорошо сконструированным'»31.

Разберемся с этими положениями подробнее.

Явные и минимальные Зависимости

Все зависимости метода тестирования должны быть четко и ясно определены в API этого метода. Это означает, что все необходимые объекты должны быть переданы либо в конструктор класса, либо в сам тестируемый метод (внедрение зависимостей). Обязательные объекты не должны создаваться в теле метода, потому что это не допускает их обмена на объекты макета. То есть, чем меньше зависимостей у метода, тем легче становится написание тестов.

Четкое распределение обязанностей

Принцип единственной обязанности (SRP)32 означает, что класс должен иметь одну, четко определенную обязанность и должен содержать только те методы, которые непосредственно вовлечены в выполнение этой обя-

30 Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship (Prentice Hall International

2008. ISBN 978-0-132-35088-4).

31 Steve Freeman and Nat Pryce, Growing Object-Oriented Software, Guided by Tests (Addison-Wesley,

2009. ISBN 978-0-321-50362-6).

32 Robert C. Martin, Agile Software Development. Principles, Patterns, and Practices (Prentice Hall International, 2002. ISBN 978-0-135-97444-5).

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

простой.

Отсутствие дублирования

Класс, который делает слишком многое, и не имеет четкой обязанности - «прекрасное место для размножения дублированного кода, хаоса и смерти»33. Дублированный код делает обслуживание программного обеспечения очень трудным, потому что каждый экземпляр дублированного кода должен редактироваться отдельно, и ошибка, найденная в повторяющемся коде, не может быть исправлена только в одном месте.

Краткие методы с несколькими ветвями исполнения

Чем длиннее метод, тем труднее его понять. Короткий метод прост не только для понимания и повторного использования, но также и для тестирования. Меньше путей исполнения означает, что меньше тестов необходимо написать.

Метрика программного обеспечения

Существуют различные метрики для измерения внутреннего качества программного обеспечения. Они являются основой для количественной оценки затрат, которые возникают в результате низкого внутреннего качества ПО. «Метрика программного обеспечения - это функция, которая переводит устройство программы в числовое значение. Это значение говорит, насколько хорошо программный модуль отвечает целям качест-ва»34.

33 Martin Fowler, Refactoring. Wie Sie das Design vorhandener Software verbessern (Addison-Weslgy, 2000. ISBN 3-8273-1630-8). ■.*"',. '.' "'.'".'.'

34 Schneider, Abenteuer. ' ' '' vli "' '»..........

Тестируемость является важным критерием для поддержания качества программного обеспечения в рамках модели ISO/IEC 9126-135. Примеры для количественной тестируемости на основе объектно-ориентированных метрик программного обеспечения можно найти в книгах «Прогнозирование тестируемости класса с использованием объектно-ориентированных метрик» Магила Брунтика и Ари ванн Деурсена36 и «Метрически-основанная тестируемость для объектно-ориентированного дизайна (MTMOOD)» Р.А. Кан и К.Мустафа37.

Цикломатическая сложность и NPATHсложность

Цикломатическая сложность - это число возможных путей решения в программе или программном модуле, как правило, методе или классе38. Он вычисляется путем подсчета управляющих структур и логических операторов в блоке программы, и представляет структурную сложность блока программы. МакКейб (Thomas J. McCabe) утверждает, что последовательность команд понять легче, чем последовательность веток в потоке управления.

Большая Цикломатическая сложность означает, что программный модуль восприимчив к дефектам и трудно тестируем. Чем больше путей исполнения программных блоков, тем больше тестов обязательны необходимо. Сложность Npath подсчитывает количество ациклических путей исполнения39. Чтобы сохранить это число конечным и устранить избыточную информацию сложность Npath не принимает все возможные итерации циклов во внимание.

35 http-.//ru. wikipedia.org/wiki/ISO_9126

36 Magiel Bruntink and Arie van Deursen, "Predicting Class Testability using Object-Oriented Metrics," SCAM '04: Proceedings of the Source Code Analysis and Manipulation, Fourth IEEE International Workshop (2004): 136-145. ISBN 0-7695-2144-4.

37 R. A. Khan and K. Mustafa, "Metric Based Testability Model for Object Oriented Design (MTMOOD)," SIGSOFT Software Engineering Notes 34, Issue 2 (March 2009): 1-6. ISSN 0163-5948.

38 Thomas J. McCabe, "A Complexity Measure," IEEE Transactions on Software Engineering 2, No. 4 (IEEE Computer Society Press, Los Alamitos, CA, USA, 1976).

39 Brian A. Nejmeh, "NPATH: A Measure of Execution Path Complexity and its Applications," Communications of the ACM 31, Issue 2 (February 198S): 188-200. ISSN 0001-0782.

Индекс риска изменяемости Анти-паттернов (CRAP)40 «Индекс риска изменяемости Анти-патгернов», ранее известный как «Индекс анализа рисков и предсказуемости изменений», не имеет явного отношения к тестированию. Но его стоит отметить, поскольку он рассчитывается из Цикломатической сложности и Закрытости кода, что достигается с помощью тестов. Код, который не является слишком сложным и достаточно подтвержден тестами, имеет низкий индекс CRAP. Это означает, что риск того, что изменения в коде приведут к неожиданным побочным эффектам ниже, чем для кода, который имеет высокий индекс CRAP. Код с высоким индексом CRAP - сложен и имеет недостаточно или даже

вовсе не имеет тестов.

Индекс CRAP может быть снижен за счет написания тестов или рефак-торинга кода. Шаблоны рефакторинга, например, «извлечение метода» или «замена условий полиморфизмом», позволяют сократить методы, уменьшить число путей решения, а, таким образом, и Цикломатическую сложность.

Неложная общая рекурсивная Цикломатическая сложность

Мишко Хевери (Misko Hevery), создатель так называемого Эксплорера тестируемости4\ - инструмента для измерения тестируемости Java-кода, определяет понятие «не-макетной метрики общей рекурсивной Цикломатической сложности» программного обеспечения. Понятие состоит из следующих частей:

• Цикломатическая сложность - структурная сложность метода.

• Рекурсивность - мы следим за Цикломатической сложностью метода и учитываем Цикломатическую сложность вызываемого кода.

• Общая структурная сложность создания объектов также принимается во внимание.

40 Интересно заметить, что английское слово «CRAP», которая используется здесь как аббревиатура, буквально переводится как «дерьмо».

41 http://code.google.eom/p/testabilify-explorer/

• Неложность - любой зависимый код, который может быть заменен мок-объектомп (ложным объектом), игнорируется. Мок-объект заменяет реальный объект во время тестирования.

Короче говоря, «неложная общая рекурсивная Цикломатическая сложность» измеряет количество сложного кода, который не может быть заменен ложными объектами в целях тестирования. Эти сложные зависимости, которые запрещают изоляцию кода в целях тестирования, приводят к «неудобствам» при тестировании. Все эти зависимости должны быть переработаны, например, путем введения «инъекции зависимостей», так чтобы они могли быть заменены мок-объектами.

Глобально-изменяемое состояние

Глобально-изменяемое состояние - еще одна метрика, которую Мишко Хевери определил для своего Эксплорера тестируемости. Она подсчитывает все элементы глобального состояния, которые программный модуль прописывает или мог бы написать. В PHP - это все глобальные и суперглобальные переменные и статические атрибуты класса. Изменение глобального состояния является побочным эффектом, который не только делает каждый тест более сложным, но требует, чтобы любой другой тест был от него изолирован. PHPUnit, например, поддерживает сохранение и восстановление глобальных и суперглобальных переменных, статических атрибутов класса перед запуском теста и их восстановление после прохождения теста, так чтобы изменения глобального состояния не сделали остальные тесты провальными. Такая изоляция, которая может быть расширена путем выполнения каждого теста в своем собственном PHP процессе, является ресурсоемкой и ее следует избегать, не полагаясь на глобальное состояние.

42 http'.//wiki.agiledev.iu/doku.php?id=tdd:mocks

Сплоченность и сцепление

Система строгой сплоченности состоит из компонентов, каждый из которых отвечает ровно за одну четко определенную задачу. Гибкая связь достигается тогда, когда классы являются независимыми друг от друга и общаются только через четко определенные интерфейсы43. Закон Демет-рыы требует, чтобы каждый метод объекта вызывает методы только в данном объекте и методы в объекте, которые были переданы ему в качестве параметров. Следование «закону Деметры» приводит к зависимостям и явным гибким связям. Это дает возможность для замены зависимостей фиктивными объектами, что делает написание тестов более простым.

Инструменты

Сколь многогранной является тема качества программного обеспечения, столь же многогранной являются инструментарий, который РНР-разработчики могут использовать для измерения и улучшения качества программного обеспечения проектов.

PHPUnit

PHPUnit (http://phpun.it/) является де-факто стандартом модульного тестирования в PHP. Данный фреймворк поддерживает запись, организацию и выполнение тестов. При написании тестов, разработчики moijt использовать следующее:

• Мок-объекты

• Функционал тестирования взаимодействия с базой данных

43 Edward Yourdon and Larry Constantine, Structured Design: Fundamentals of aDiscipline of Computer Program and Systems Design (Prentice Hall, 1979. ISBN 978-0138544713.)

44 K J. Lienberherr, "Formulations and Benefi ts of the Law of Demeter," ACM SIGPLAN Notices 24, Issue 3 (March 1989): 67-78. ISSN 0362-1340.

• Интеграция с Selenium45 для браузерных непрерывных тестов. Результаты теста могут фиксироваться с помощью JUnit, а прикрытие кода -с помощью Clover XML.

Phpioc

phploc (http://github/sebastianbergmann/phploc) измеряет объем PHP проекта с помощью сравнения форм различных строк кода (LOC). Кроме того, подсчитывается количество пространств имен, классов, методов и функций проекта, а также некоторые значения сложности и длины классов и методов.

PHP Copy-Paste-Detector (phpcpd)

PHP Copy-Paste-Detector (phpcpd) (http://github/sebastianbergmann/phpcpd) ищет повторяющийся код в проекте, phpcpd может быть использован для автоматизированного и регулярного поиска повторяющегося кода в контексте непрерывной интеграции.

PHP Dead Code Detector (phpdcd)

PHPDeadCodeDetector (phpdcd) (http://github.com/sebastianbergmann/phpdcd) ищет в проекте код, который больше не вызывается и, потенциально, может быть удален.

PHPDepend (pdepend)

PHP_Depend (pdepend) (http://pdepend.org/) является инструментом для статического анализа PHP кода. Вдохновленный успехом JDepenctб, он вычисляет различные показатели программного обеспечения, например, Цикломатические сложности и Npath сложности, о которых мы упоминали ранее. PHP_Depend так же позволяет визуализировать различные аспекты качества программного обеспечения.

45 http://ru. wikipedia.org/wiki/Selenium

46 http://clarkware.co m/softwareAfDepend.html

PHP Mess Detector (phpmd)

PHP Mess Detector (phpmd) (http://phpmd.org/) базируется mPHP_Depend и позволяет определять правила, которые оказывают влияния на «сырые данные» показателей программного обеспечения. Если установленное правило нарушается, например, из-за того, что Цикломатическая сложность превышает указанный предел, срабатывает предупреждение или ошибка.

PHPCodeSniffer (pftpcs)

PHP_CodeSniffer (phpcs) (http://pear.php.net/php_codesniffer/) является наиболее часто используемым инструментом для статического анализа

47

PHP кода. Он определяет с помощью метрик степень «вони кода» в отношении правил форматирования для обнаружения возможных дефектов и проблем с производительностью.

bytekit-cli

bytekit-cli (http://github.eom/sebastianbergmann/bvtekit-cli) - интерфейс командной строки для расширения Bytekit PHP (http://bvtekit.org/). Bytekit позволяет анализировать программу на уровне байт-кода. С помощью bytekit-cli можно генерировать вывод работы проверяемого кода. Также возможна разборка и визуализация байт-кода PHP.

PHPjCodeBrowser (phpeb)

(http://github.com/mavflowergmbh/PHP_CodeBrowser) - это генератор XML отчетов вывода других инструментов, таких как PHP Copy-Paste-Detector, PHP_CodeSniffer и PHP Mess Detector. Он создает единый отчет, который является чрезвычайно полезным в непрерывной интеграции.

CruiseControl and phpUnder Control

phpUnderControl (http://phpUnderControl.org/) - представляет собой модификацию и расширение CruiseControl (http://cruisecontrol.sourceforge.net/), - Java open-source решение, которое изначально сделало популярной непрерывную интеграцию. Себастьян Нон

47 Fowler, Refactoring.

в 2006 году был одним из первых, кто использовал CruiseControl в PHP проектах48.

На заседании PHP Usergroup в Дортмунде (Германия), в котором приняли участие Мануэль Пихлер, Коре Нордман, и Тобиас Шлитт, родилась идея упростить конфигурационную среду непрерывной интеграции для проектов, основанных на PHP CruiseControl. Результатом стал phpUnderControl, который - как и CruiseControl в мире Java - сделал популярной непрерывную интеграцию в мире PHP.

Hudson

Как и CruiseControl, Hudson (http://hudson-ci.org/) — это open-source решение для непрерывной интеграции. В мире Java, Hudson заменяет устаревший CruiseControl. Это не удивительно, потому что Hudson является более надежным и простым в обращении, и он активно развивается. Phphudson-template project (http://github.com/sebastianbergmann/php-hudson-template) - это шаблон конфигурации для PHP проектов в Hudson.

Arbit

Arbit (http://arbitracker.org/) представляет собой модульное решение для управления проектами. Он оснащен системой отслеживания проблем, вики, браузером кода и сервер непрерывной интеграции. Arbit в настоящее время еще находится в стадии alpha и поэтому не очень подходит для корпоративного использования.

Вывод

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

48 Sebastian Nohn, "Continuous Builds with CruiseControl, Ant and PHPUnit," March 7,2006, accessed April 28,2010, http://nohn.net/blog/view/id/ cruisecontrol_ant_and_phpunit.

грации это позволяет, данные должны использоваться для освещения вопроса проверки качества программного продукта. В этом случае может помочь, определенный Виктором Р. Базили, подход «Метрика-Цель-Запрос» (0<ЗМ)/9 обобщенный Куртом Шнайдером в одном предложении: «Не измеряй то, что легко измерить, но измеряй то, что нужно для дос-

50

тижения целей улучшения» .

В этой главе были изложены некоторые аспекты улучшения внутреннего качества программного обеспечения, например, тестируемость, поддержи-ваемость и возможность многократного использования. Мы обозначили некоторые программные показатели измерения этих аспектов. Надеемся, что обсуждение «технического долга» улучшит взаимопонимание между различными сторонами, вовлеченными в разработку программного обеспечения, и ясно даст понять важность внутреннего контроля качества программного обеспечения.

Большинство веб-приложений изменяются и адаптируются довольно часто и быстро. Их среда, например, размер и поведение пользовательской базы данных, постоянно меняется. Внутреннее и внешнее качество - это просто временные слепки. Что было достаточно вчера, может быть уже недостаточно сегодня. В веб-среде особенно важно контролировать и постоянно совершенствовать внутреннее качество, и не только при разработке, но и при обслуживании программного обеспечения.

45 Victor R Basiii, Gianluigi Caldiera, and H. Dieter Rombach, ,

Encyclopedia of Software Engineering, 2 Volume Set (John Wiley & Sons, 1994. ISBN 1-54004-8.)

50 Schneider, Abenteuer.

ГЛАВА 2

ТЕСТИРОВАНИЕ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ

Это нормально, не тестировать код, который никогда не меняется.

Для всего остального нужны тесты.

Тимоти Фитц.

Тесты «черного» и «белого» ящика

В классической, не-итерационной разработке программного обеспечения, программирование и интеграция (или тестирование системы) - это два различных этапа проекта, и они часто выполняются разными командами. В этом случае, разработчик не имеет особенной мотивации для тестирования свои работы. Независимый тестирировщик подходит к продукту с совершенно другой точки зрения, потому что не знает деталей реализации. Таким образом, он вынужден сосредоточиться на тестирование API. Он использует приложение иначе, чем оригинальный разработчик, который все еще думает о коде интуитивно и тестирует функциональность, которая и так будет работать. Независимым тестировщикам обычно приходится проявлять изрядную креативность, чтобы сделать работу разработчика пригодной для тестирования. Приходится прибегать к реально бессмысленным вводам данных, разрывать экшены и даже манипулировать URL. Начинающие пользователи приложения, кстати, также отличные тестеровщики. Тесты, которые выполняются без знаний деталей реализации, называются тестами черного ящика. Тесты, которые проводятся на основе исходного кода приложения, называются тестами белого ящика.

Какое количество тестов можно считать достаточным?

На первый взгляд, тестирование веб-приложений является довольно тривиальной задачей. Программа получает входную строку (запрос HTTP) из браузера и генерирует другую строку (как правило, HTML-код), который отправляется обратно браузеру, где и визуализируется. Другие форма-

ты, как например XML или JSON так же могут быть использованы, но они опять же являются только строками. Таким образом, любой тест должен проверить, что программа генерирует правильную строку вывода для данной входной строки. Хотя, фактически, довольно легко убедиться, что вывод для одной определенной строки ввода является правильным, большое количество возможных строк ввода само по себе делает невозможным определение, генерирует ли программа правильный вывод для любого ввода.

К сожалению, программа принимает не только «осмысленные» запросы, так что вовсе не достаточно проверить лишь несколько строк ввода. URL-адреса может содержать до 37 различных символов (алфавитно-цифровых символы в верхнем и нижнем регистре и несколько специальных символов). Любой другой символ должен специальным образом кодироваться. Список всех возможных адресов с максимальной длиной 20 символов с чередующимися значениями указанных 37 альфа-нумерических знаков, подразумевает несколько ундециллионов вхождений. Если бы мы могли просматривать один миллион адресов в секунду, прохождение этого списка целиком займет примерно 1023 лет. Тем не менее, так как ожидается, что солнце превратится в сверхновую примерно через 109 лет, уничтожив при этом землю, мы, скорее всего, никогда не сможем выполнить эту задачу.

Чтобы уменьшить число входных строк, которые мы должны протестировать, необходимо создать классы эквивалентности. Класс эквивалентности содержит число вводов, которые не приведут к изменению хода работы программы, даже если расчеты производятся с различными значениями входных данных. Предположим, что вы хотите протестировать программу, которая инкрементирует значение данного целого числа. Не имеет значения, использует ли программа, чтобы выполнить свою задачу, операцию инкрементирования или сложения (или любой другой подход). Если предположить, что PHP работает правильно, тестирования единственного ввода будет вполне достаточно. Если программа дает правильный резуль-

тат, мы можем предположить, что она работает правильно, без фактического тестирования для каждого целого числа.

Ограниченные и недопустимые входящие значения требуют особого внимания. Они образуют дополнительные классы эквивалентности, в дополнение к нормальному, ожидаемому вводу. Каждый класс эквивалентности требует тестирования. Что произойдет, например, если мы пытаемся увеличить максимально возможное целое значение? А что произойдет, когда мы пытаемся инкрементировать нечисловое значение - например строку?

На практике не всегда легко определить классы эквивалентности - другими словами, протестировать представление вводных данных. Как правило, в первую очередь приходится всегда тестировать «правильный путь»51. Затем добавляются тесты, если введено максимальное значение или максимально допустимое значение плюс один, или наоборот максимально низкое значение. Если это сделано, не составит никакого труда проверить NULL значения или недопустимые типы данных.

Для «черных» тестов, выявление классов эквивалентности является более сложным, чем для «белых» тестов, где в случае дифференциации и исключений, появляется хорошее понимание путей тестировании входящих значений.

Поскольку HTTP является по определению протоколом без передачи состояний, нет зависимости между двумя последовательными HTTP запросами. Опять же, тестирование HTTP-основанных приложений кажется простым, потому что каждый запрос HTTP может быть проверен только один раз.

Как всем известно, большинство веб-приложений обходят статичность HTTP использованием куки и сессий на стороне сервера. Без фиксирова-

Sl Правильный путь - сценарий по-умолчанию, в котором не должно произойти никаких ошибок или исключений.

ния состояния приложение не может отличить анонимного пользователя от авторизованного, если только учетные данные не отправляются повторно с каждым HTTP запросом.

Приложения с сохранением состояния могут по-разному реагировать на один и тот же вход, в зависимости от его внутреннего состояния. Так что не достаточно только протестировать все возможные входные значения, даже если мы знаем, что их может быть больше, чем нам бы хотелось. Для полноценного тестирования с отслеживанием состояния приложения, мы должны были бы проверить программу со всеми возможными последовательностями входных данных.

Типичный пример режима, который различается в зависимости от состояния приложения - попытаться получить доступ к странице с непубличным содержанием. Анонимный посетитель пытается войти в систему, в то время как авторизованный пользователь получает содержимое для просмотра, если, конечно, он имеет соответствующие права доступа. Само собой разумеется, что таким образом невозможно полностью протестировать веб-приложение до конца времен. Мы даже не сможем перечислить все возможные запросы к тому времени!

Системные тесты

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

Браузер но е тестирование

Одним из серьезных преимуществ PHP приложений является возможность выполнить код, который только что был написан, и увидеть результат в окне браузера. Это мгновенная обратная связь, вероятно, одна из главных причин, почему так много разработчиков предпочитают PHP ком-

пилируемым языкам, таким как Java. Может показаться, что тестирование приложения в браузере является подходящим способом.

Приложения PHP не просто генерируют статический код HTML. Используя JavaScript, можно по желанию управлять DOM представлением страницы HTML в браузере. Новые элементы страницы могут быть добавлены, удалены или скрыты. В соединении с асинхронными HTTP запросами, которые принимают данные в виде XML или JSON структуры, можно избежать полностраничной перезагрузки. Благодаря AJAX веб-приложения могут обеспечить пользовательский опыт, подобный классическому GUI приложению.

Использование AJAX, позволяет браузеру сохранять состояние, в противоположность статичному HTTP протоколу. Представленный HTML документ становится клиентским приложением, которое технически обладает состоянием абсолютно независимым от сервера и связывается с сервером через AJAX запросы. Разработчики внезапно сталкиваются с новыми трудностями при записи веб-приложений - например, проблемы тайм-аута или зависания приложения.

В большинстве случаев недостаточно просто выполнить статический анализ HTML кода, сгенерированного PHP приложением. Статический анализ может проверить, что HTML код синтаксически корректен или, когда используется XHTML, правильно построен. Более или менее сложным процессом парсинга можно удостовериться, что страница содержит (только) требуемую информацию. Поскольку серьезная часть функциональности приложения в наши дни реализуется на JavaScript, любой серьезный тест должен также выполнить JavaScript код.

Мы могли бы использовать движки JavaScript, как Rhino, V8 или SpiderMonkey, но из-за того что рано или поздно также нужно будет видеть фактическое представление страницы, тестирование в браузере становится неизбежным. Как известно, каждое семейство браузеров, или даже каждая

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

Если приложение при тестировании ведет себя не так как мы ожидаем, при тестировании в браузере становиться трудно понять причину. Происходит ли та же самая проблема в других браузерах? Будет ли поведение таким же при различных настройках безопасности? Может ли недопустимая разметка HTML или ошибка в коде JavaScript быть причиной поломки,

или она в базе данных?

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

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

В социальной сети, например, каждый друг должен быть в состоянии прокомментировать последнюю запись в блоге пользователя. Если комментарии должны модерироваться, новый комментарий изначально не виден никому кроме его автора и модератора. Как только комментарий одобрен, он должен стать видимым. Теперь, автор этого комментария, имеет право удалить или изменить его? Должны ли последующие модификации модерироваться снова? Чтобы протестировать подобные бизнес-правила, нужно войти в систему под двумя различными учетными запися-

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

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

Автоматизированные Тесты

Поскольку ручное тестирование приложения требует много времени и нервов, тесты должны быть автоматизированы в максимально возможной степени. Автоматизация не только увеличивает скорость выполнения тестирования, но также позволяет избежать «человеческих» ошибок. Помимо малого количества энергозатрат, нет никаких дополнительных условий для того, чтобы выполнять тесты снова и снова. Автоматизация тестирования -необходимое условие тестирования приложения с различными операционными системами, базами данных, версиями и конфигурациями PHP.

Изначальный подход к автоматизации браузерных тестов осуществляется с помощью так называемых «получи и воспроизведи» инструментов. Простые инструменты реагируют на перемещения мыши и любые вводы, а затем их воспроизводят. Поскольку веб-приложения часто используются с различными разрешениями экранов и размерами окон, инструмент должен получать не фактические экранные позиции, а события браузера на уровне DOM или JavaScript.

Selenium - инструмент свободного программного обеспечения, который поддерживает запись и воспроизведение тестов браузера как расширение

Firefox. В соединении с PHPUnit, основанный на Java прокси-сервер позволяет удаленно управлять браузером во время тестирования PHPUnit. Это позволяет выполнять браузерные тесты как часть непрерывной интеграции.

Selenium тесты оперируют деревом DOM, чтобы удостовериться, что текущая страница HTML отвечает определенным критериям. Поскольку тест проходит непосредственно в браузере, код JavaScript выполняется. Таким образом, приложения AJAX могут быть также протестированы с Selenium. Различные операторы контроля позволяют проверять присутствие или содержание произвольных элементов HTML. Элементы страницы (или DOM узлы) доступны через HTML-идентификаторы, CSS селекторы, JavaScript-код или выражения XPath.

Так как функция удаленного контроля доступа реализуется через JavaScript, который внедряется в тестируемую страницу, возникают некоторые сложности из-за ограничений безопасности. Например, невозможно протестировать загрузку файла, потому что это потребовало бы, чтобы код JavaScript получил доступ к локальной файловой системе для выбора загружаемого файла. Обычно, никто не желает, чтобы у веб-страницы был доступ к файловой системе. Некоторые браузеры позволяют обходить ограничения при использовании специального режима с меньшим уровнем безопасности.

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

В большинстве случаев, вы не станете выполнять системные тесты против живой системы. Ваши пользователи, вероятно, будут удивлены, когда

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

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

Изоляция тестов

Одна из основных функций каждого веб-приложения - создание учетной записи пользователя и пользовательский вход в систему. Когда пользователи не могут больше войти в систему - деньги потеряны. Таким образом, регистрация и вход в систему из всего функционала веб-приложений обычно тестируются наиболее часто. Чтобы протестировать вход в приложение, обычно требуются несуществующее имя пользователя и неиспользованный адрес электронной почты. Если тестируется база данных уже содержащая пользователей, первой потенциальная проблемой является тест с сообщением об ошибке, что имя пользователя или адрес электронной почты уже используются.

Предположим, что первый тест создания пользователя удался. Прежде, чем мы сможем проверить, может ли пользователь войти в систему, мы обычно подтверждаем адрес электронной почты через посещение URL, содержащего уникальный код подтверждения. Сделав это, мы можем протестировать, может ли пользователь войти в систему. Однако если создание пользователя терпит неудачу - подтверждение также терпит неудачу, вероятно, потому что тест был неспособен получить код подтверждения с

итоговой страницы. Вход в систему терпит неудачу также, потому что нет никакой учетной записи с указанными данными авторизации.

Если есть зависимости между тестами, единственный провальный тест может привести к каскаду провальных тестов. Это не только усложняет изоляцию оригинальной проблемы, но также и подрывает веру в тесты и таким образом в качество кода, потому что незначительная проблема может оказать большое воздействие и заставить провалиться множество тестов.

Важная цель при тестировании программного обеспечения состоит в том, чтобы изолировать тесты друг от друга. Идеально если у каждого теста есть свои собственные, минимальные условия испытаний. Практически, это означает, что каждый тест требует отдельной базы данных с соответствующими тестовыми данными. Это единственный способ надежного тестирования последовательных рабочих процессов: регистрация -> подтверждение -> вход. Каждый шаг должен быть независимым от предыдущих тестов.

Процесс управления всей этой тестовой средой, так называемым «креплением», - утомителен. Такое усилие может окупиться для нескольких основных функций, но соотношение стоимости/пользы тестирования всей функциональности такого приложения - сомнительно.

Помимо усилий, требуемых для управления и поддержки всего тестового «крепления», тестовая среда должна быть настроены для каждого отдельного теста, что обычно подразумевает заполнение тестовой базы данных. В зависимости от приложения также может быть необходимо создать файлы в файловой системе или предварительно заполнить кэши с определенными данными, чтобы выполнить тест. Тестовое крепление может стать в определенный момент очень сложным, например, когда вы захотите протестировать, как социальная сеть взаимодействует с пользователем, у которого 50 ООО друзей. Едва ли возможно реально протестировать не-

функциональные аспекты, как, например время ответа с урезанным набором данных.

Приемочные тесты

Системные тесты - обычно являются тестами «черного ящика», это означает, что они написаны и выполнены без любого знания о внутренностях проверяемой системы. Приемочные тесты - системные тесты, которые написаны клиентом. Они обеспечивают уверенность в том, что программное обеспечение соответствуют спецификации и обеспечивает ожидаемую функциональность.

Приемочные тесты относительно функциональных аспектов могут быть автоматизированы использованием Selenium в качестве инструмента. Даже если вы разрабатываете программное обеспечение для своего собственного использования, вы должны считаться с записями приемочных тестов, потому что это заставляет думать о функциональности с точки зрения конечного пользователя, не заботясь о проблемах реализации. Это, между прочим, соответствует парадигме <<разработка через тестирование» (TDD).

Самое важное преимущество приемочных испытаний состоит в том, что они могут указать, где программное обеспечение не соответствует своей спецификации. Тесты «белого ящика» основаны на действующем коде и сосредотачиваются на поиске и покрытии недостатков в тестируемом коде. Поскольку код для недостающей функциональности не существует, никакой тест «белого ящика» не может выявить проблему. Только тесты «черного ящика», которые основаны на специфике программного обеспечения, могут указать на недостающий код.

Ограничения системных тестов

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

версий, но никакой компьютер не может заменить человеческий глаз, когда дело доходит до оценки, насколько приемлем определенный элемент дизайна и гармоничен ли проект в целом.

Могут быть проблемы с началом тестирования пока программное обеспечение еще не готово, особенно в проектах, над которыми работают в течение нескольких месяцев или даже лет. Когда тесты автоматизированы, и никакое ручное тестирование не производится, - не проблема повторять тесты так часто, сколь это необходимо пока программа «созревает». Однако все еще требуется определенное время, пока тесты смогут быть проведены. Чем раньше начнется тестирование, тем лучше.

Системные тесты медленны, потому что они требуют сложных условий для испытаний, и в них вовлечены все компоненты приложения, - от базы данных до браузера. Возможно, не проблема провести тест, который выполняется в течение нескольких часов, пока обкатывается релиз. В конце концов, автоматизированные тесты намного быстрее, чем ручные тесты. Но системные тесты не очень хорошо подходят для обеспечения разработчиков быстрой обратной связью, обозначая, вызвало ли внесенное изменение нежелательный побочный эффект. Одновременно, они не смогут легко быть выполнены, потому что обычно они зависят от баз данных, кэшей, и потенциально, файлов в файловой системе.

Если тесты многократно проходят неправильно, могут возникнуть ошибки, которые почти невозможно выявить. Это полная противоположность того, для чего предназначены системные тесты.

Модульные тесты

В противоположность непрерывным тестам, которые проверяют целое приложение, - модульные проверяют только часть приложения. Чаще всего тестируется один класс, но иногда тестирование включает комплект объектов или даже целый архитектурный уровень приложения. Модульные тес-

ты обычно «белые», потому что они написаны на основе тестируемого кода.

Многие разработчики стремятся достигнуть цели покрытия большей части кода при помощи модульных тестов. Это означает, что каждая строка рабочего кода должна выполняться с тестом. Однако покрытие кода выполняется построчно, и потому не принимается во внимание, что у программы обычно есть несколько путей исполнения. Упомянутый выше инструмент проверки сложности Npath измеряет число путей выполнения кода модуля, что соответствует количеству тестов, которые требуются не только для того, чтобы выполнить каждую строку кода, но и чтобы достигнуть полного покрытия кода. PHP-расширение Xdebug может собирать статистику покрытия кода, которая может быть обработана при помощи PHPUnit, выведена как HTML или сохранена как XML.

Само по себе покрытие кода не означает, что тестирование идет хорошо; оно только показывает те части кода, которые еще непроверенны. В настоящее время нет никакого инструмента для PHP, который может создавать статистику путей покрытия, возможно в будущем это смогут делать Xdebug и PHPUnit. Пока, однако, разработчики вынуждены смотреть на код, чтобы проверить и выяснить, какие пути выполнения доступны и как их протестировать. Чем большую сложность приобретает рабочий код, тем задача становится более трудной. Как показывает следующий пример, количество необходимых тестов может быть решительно сокращено разделением кода на маленькие части:

function а($а, $b, $с, $d)

<

if ($а) { П ...

}

if ($b) { // ...

}

if ($C) { // ...

}

if ($d) { II ...

}

}

Функция a () имеет Npath сложность 16, потому что каждый i f удваивает число путей выполнения. Таким образом, требуется 16 тестов, чтобы достигнуть полного покрытия пути для данной функции.

Если мы разделим функцию на две меньших, мы выясним, что обе уже обладают сложностью 4:

function а($а, $Ь) {

if ($а) { II ...

}

if ($Ь) { // ...

}

}

function Ь($с, $d) {

if ($С) {

II ...

J

if ($d) { II ...

}

Теперь достаточно восьми тестов. Как мы видим, написание коротких методов с меньшими путями исполнения и тестирование их по-отдельности, определенно окупается.

Модульные тесты должны выполняться быстро, так чтобы каждый разработчик смог запустить их повторно после каждого изменения кода и получить немедленный ответ: работает ли еще код, как ожидается. Если применяется парадигма «тестирование через разработку», то тесты будут написаны прежде рабочего кода. Это заставляет разработчиков сосредоточиться на API вместо реализации. Разработка начинается с провальных тестов, так как функциональность еще не реализована. По определению, функция реализована, когда все ее тесты проходят. Разработка через тестирование помогает разработчикам решить конкретную проблему вместо того, чтобы пытаться ее обобщить. Таким образом, TDD может значительно повысить производительность, потому что никакие спекулятивные особенности и особые случаи, которые могли бы появиться в будущем, не осуществлены.

Даже когда «тестирование через разработку» не применяется, модульные тесты должны всегда писаться в тот же день что и рабочий код. Код с архитектурными недостатками обычно сложнее тестировать. Многие разработчики допускают ошибку, когда не пишут модульные тесты вместо изменения архитектуры рабочего кода. Нужно избегать любой ценой «ненаписания» модульных тестов, потому что тогда становится сложнее тестировать программу, не изменяя рабочий код.

Любые проблемы, которые возникают при написании тестов, обычно являются признаками плохого кода. Наоборот, хороший код легко протестировать. Хорошие модульные тесты, таким образом, сильный индикатор высокого внутреннего качества кода.

Возвращаемые значения

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

function add($a, $b) {

if (!is_numeric($a) || !is_numeric ($b)) {

throw new invalidArgumentException ('Not a number');

}

return $a + $b;

}

Написание модульного теста для этой функции тривиально, по крайней мере, пока у кода нет никаких дальнейших зависимостей.

class CalculatorTest extends PHPUnit_Framework_TestCase {

public function testAddReturnsSumOfTwoIntegers() {

$this->assertEquals(7, add(3, 4));

}

! * *

* SexpectedException InvalidArgumentException */

public function testAddThrowsExceptionWhenFirstParamete-

rlsNotNumeric() {

add('nonsense', 4);

}

* @expectedException InvalidArgumentException

public function testAddThrowsExceptionWhenSecondParamete-

rlsNotNumeric() {

add(3, 'nonsense');

}

}

Этот пример показывает, как должен быть написан модульный тест для PHPUnit:

• Все тесты для Calculator включены в классе CalculatorTest.

• CalculatorTest наследует PHPU-nit_Framework_TestCase.

• Тесты - это публичные методы с именем, начинающимся на t е s t.

• Внутри тестовых методов операторы контроля, как assertEquals () используются, чтобы сравнить вычисленные значения с ожидаемыми.

Пример CalculatorTest также показывает, что вполне возможно писать модульные тесты для процедурного кода. Если бы add () был методом класса Calculator, тест выглядел бы почти так же.

На первый взгляд, может показаться излишним написание модульного теста для такой простой функции как add () . Однако, модульный тест подтверждает не только то, что функция возвращает действительное значение, но и то, что ее можно повторить в любое время, чтобы удостовериться, что код работает, как ожидалось даже после рефакторинга. Это позволяет избежать регрессии.

Действительное значение модульного теста показывается в течение всего времени, когда тесты внезапно начинают говорить разработчику, что, якобы, безопасное исправление изменило поведение кода.

Другое преимущество написания модульных тестов для простых функций как add () состоит в том, что вы можете узнать в, так сказать, «безопасной среде», как код ведет себя после ввода неожиданных данных. На-

пример, если нечисловые значения передаются в качестве параметров, должно быть выброшено исключение.

Также бывает полезно подумать об ограничениях. Что произойдет, когда первый параметр - это наивысшее возможное значение, которое может быть представлено 32- или 64-битным целым числом? Можно было бы ожидать ошибку, но фактически вместо этого PHP конвертирует integer во float, что означает увеличение диапазона, но потерю точности. Поэтому, конечно лучше написать модульный тест, чтобы узнать, как код ведет себя, чем потом отлаживать последовательную ошибку в приложении.

Исключения не являются реально возвращаемыми значениями, но для целей тестирования мы можем обработать их как возвращаемые значения. Аннотация @expectedException заставляет PHPUnit генерировать неявный оператор контроля, что напоминает следующий код:

public function testAddThrowsExceptionWhenlsNotNumeric ()

{

try {

add('nonsense', 4); $this->fail(/* ... */) ;

} catch (Exception $e) {

$this->assertType('InvalidArgumentException', $e) ;

}

}

Если при вызове add () не возникло никакое исключение, тест проваливается.

Зависимости

К сожалению, мы не живем (и не пишем код) в совершенном мире, где все модульные тесты столь же просты как показанный в предыдущем разделе. В реальности у кода есть зависимости, обычно даже большие, чем мы этого хотим. Они включают:

• Константы (глобальные константы или константы класса)

• Глобальные переменные (доступ к global или $GLOBALS)

• Суперглобальные переменные

• Функции (определенные пользователем или встроенные)

• Классы (определенные пользователем или встроенные)

• Статические методы

• Статические признаки

• Объекты

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

Если интерфейсы используются в качестве указателей типа вместо названий классов, у переданного объекта будут определены публичные методы, которые можно вызвать для подтверждения.

В то время как довольно легко работать с неявными, глобальными зависимостями единственного, изолированного класса, эти зависимости быстро превращаются в кошмар, когда код становится более сложным. Каскад глобальных зависимостей может вызвать проблемы при, казалось бы, безопасных изменениях кода, которые обнаруживаются в различной области программы, что делает эти проблемы чрезвычайно трудными для отладки.

Использование явных зависимостей не только делает код проще для тестирования, но также и помогает написать объекты с единственной, ясно определенной функцией. Код легче использовать, так как сигнатуры методов API ясно указывают, какие зависимости имеет класс. Использование интерфейсов укрепляет слабо связанный код.

Побочные эффекты

И процедурное, и объектно-ориентированное программирование зависят от побочных эффектов. Типичный побочный эффект состоит в изменении переменной в глобальном или в некотором внешнем контексте. Это подразумевает изменение системного состояния, которое в программах представлено как глобальными, так и локальными переменными.

В предыдущем разделе мы показали пример функции, которая только возвращает возвращаемое значение, но не вызывает никаких побочных эффектов. В реальности, однако - это редкий случай. В большинстве случаев, код вызывает методы в других объектах, создавая побочные эффекты, которые обычно непосредственно не влияют на возвращаемое значение. Поэтому, относительно контролируемости, побочные эффекты - настоящая проблема.

Если побочные эффекты функции или метода могут быть ограничены взаимодействием с другими объектами, которые были переданы как параметры (Закон Деметры),-побочные эффекты в модульных тестах, как мы скоро увидим, перестают быть проблемой. Все глобальные и неявные зависимости, как, например, вызовы статических методов, не только делают тестирование более трудным, но также и очень усложняют предсказание поведения приложения, поскольку код становится более сложным.

Вывод

Серьезная ценность и модульных, и системных тестов состоит в возможности их комбинирования. Тестируя небольшие блоки кода, мы можем убедиться, что они работают как ожидалось. Интегрируя тестируемые модули кода, мы можем сосредоточиться на интерфейсе. Поскольку отдельные части кода ведут себя очень предсказуемо, снижается возможность возникновения потенциальных источников ошибок. В зависимости от размера модуля кода мы можем различить следующие типы тестов:

• Приемочные тесты в форме непрерывных тестов подтверждают, что программное обеспечение работает как ожидалось.

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

• Модульные тесты проверяют, что блок кода работает в изоляции,

без своих зависимостей.

Цель всех тестов состоит в том, чтобы обнаружить проблемы как можно раньше. Твит Джейсона Хаггинса (Jason Huggins) от 22 ав1уста 2009, дает хорошее объяснение основного различия между непрерывными (он называет их функциональными тестами), и модульными тестами: «Функциональные тесты могут сказать вам, что что-то сломалось. Модульные тесты могут сказать, ЧТО сломалось» 52. Отношения между непрерывными тестами, модульными тестами, внутренним и внешним качеством тестированного программного обеспечения охарактеризованы Стивом Фриманом и Натом Прайсом (Steve Freeman and Nat Pryce): «Выполнение непрерывных тестов сообщает нам о внешнем качестве нашей системы, а написание их говорит нам что-то о том, как хорошо мы [...] понимаем эту область. Но непрерывные тесты не говорят нам, как хорошо мы написали код. Написание модульных тестов позволяет нам много узнать о качестве нашего кода, а их выполнение сообщает нам, что мы не нарушили целостность ни одного из классов [...]».53

" Growing Obj ect-Oriented Software, Guided by Tests. Steve Freeman and Nat Piyce, Addison-Wesley, 2009,

ISBN 978-0-321-50362-6

ГЛАВА3

РЕАЛЬНЫЙ ПРИМЕР

Попробуем написать модульные тесты для контроллера фиктивного MVC фреймворка и продемонстрировать, как разукрупнение кода и сокращение зависимостей увеличивают тестируемость. Небольшой рефакто-ринг почти волшебным образом упрощает тестирование кода.

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

Мы не будем показывать полный код контроллера, - только метод, который мы собираемся тестировать. Чтобы сделать листинг проще, мы также уберем нерелевантный код из других классов, которые будут показаны.

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

class DserController

{

public function resetPasswordAction() {

if (lisset{$_POST['email'])) {

return new ErrorView(

' resetPassword', 'No email specified'

) ;

}

$db = new PDO(Configuration::get('DSN'));

$statement = $db->prepare(

'SELECT * FROM Users WHERE email=:email;'

$statement->bindValue(

':email', $_POST['email'], PDO::PARAM_STR

);

$statement->execute() ;

$record = $statement->fetch(PDO::FETCH_ASSOC); if ($record === FALSE) { return new ErrorViewf 'resetPassword',

'No user with email ' . $_POST['email']

);

}

$code = CryptHelper::getConfirmationCode(); $statement = $db->prepare(

'UPDATE Users SET code=:code WHERE email=:email;'

) ;

$statement->bindValue(

':code', $code, PDO::PARAM_STR

);

$statement->bindValue(

email', $_POST['email'Ь PDO::PARAM_STR

) ;

$statement->execute(); mail (

$_POST['email' ] , 'Password Reset', 'Confirmation code: ' . $code

) ;

return new View('passwordResetRequested' );

}

}

Сначала мы проверяем, существует ли переданная методом POST переменная email. Если нет, мы возвращаем экземпляр объекта ErrorView с соответствующим сообщением об ошибке. Это конечно упрощение, пото-

му что сообщения об ошибках не всегда выражаются специальным представлением (view), и когда мы обрабатываем формы, за один раз может произойти больше чем одна ошибка.

Если адрес электронной почты был отправлен через POST, мы пытаемся найти пользователя с таким же адресом в базе данных. Если никакой пользователь не найден, мы возвращаем экземпляр объекта ErrorView с соответствующим сообщением об ошибке. В противном случае, случайный код подтверждения создается и сохраняется в базе данных. Это позволит найти пользователя позже, используя код подтверждения в качестве критерия поиска.

Потом мы посылаем пользователю электронное письмо с подтверждением. Для сохранения простоты, это письмо содержит только код подтверждения. В реальности, электронная почта содержала бы некоторый объяснительный текст и кликабельную ссылку с кодом подтверждения в качестве параметра URL.

В конце возвращается экземпляр объекта текущего представления, которому передается в качестве параметра название сценария представления, которое мы хотим вывести на экран. Этот сценарий представления обычно выводит текст, в котором администратор просит, чтобы пользователь проверил свою электронную почту.

Как говорилось ранее, этот пример является неполным. В настоящее время мы не знаем, было ли сохранение данных в базе успешным. Мы также не проверяем, был ли код подтверждения уже сохранен с пользовательским отчетом. Мы предполагаем, что код подтверждения будет удален из базы данных после того, как новый пароль был создан.

Глобальная конфигурация содержит статический метод, который обеспечивает доступ ко всем настройкам конфигурации. Эти настройки сохранены в ассоциативном массиве:

class Configuration

{

protected static $values = array();

public static function init(array $values) {

self : :$values = $values;

}

public static function get($key) i

if (!isset(self:: $values[$keyl)) {

throw new Exception('No such key');

)

return self : : $values [$key] ;

}

}

Как и в обычном MVC фреймворке, мы используем родовое представление класса, что требует использования названия сценария в качестве параметра представления:

class View {

protected $viewScript;

public function _construct($viewScript)

{

$this->viewScript = $viewScript;

1

}

У производного класса ErrorView есть дополнительный параметр конструктора, а именно, сообщение об ошибке:

class ErrorView extends View {

protected $errorMessage;

public function construct(SviewScript, $errorMessage) {

$this->errorMessage = $errorMessage;

parent::_construct($viewScript) ;

}

}

Чтобы сохранить пример простым, мы игнорируем фактическую реализацию и сценарий представления. Класс хелпера CryptHelper содержит метод создания случайного кода подтверждения:

class CryptHelper

{

protected static Ssalt = '...';

public static function getConfirmationCode () {

return shal(uniqid(self::$salt, TRUE));

}

}

Сгенерированные коды подтверждения не должны быть последовательными или предсказуемыми. Они должны оставаться уникальными, по крайней мере, до тех пор, пока сброс пароля не подтвержден.

Анализ тестируемого кода

Контроллер, который мы хотим протестировать, помимо некоторых встроенных функций PHP имеет много зависимостей:

• POST переменная email

• DSN ключ в конфигурации

• Класс PDO (расширение PDO [PHP Data Objects])

• В зависимости от содержания POST переменной email - классы CryptHelper и View, или класс ErrorView

• Отправка электронной почты

Все зависимости неявны, поэтому мы можем их обнаружить только в тестах «белого ящика». Строго говоря, есть даже больше неявных зависимостей: код требует наличие базы данных с таблицей Users, содержащей, по крайней мере, столбцы code и email. В зависимости от ситуации, в

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

Контроллер возвращает экземпляр объекта View, но также создает побочные эффекты при записи в базу данных и отправке электронной почты.

При выполнении метода resetPasswordAction (), могут произойти следующие ошибки:

• Запрос POST не содержит адрес электронной почты

• Конфигурация не содержит DSN

• База данных недоступна

• База данных не содержит таблицу Users, или в таблице пропущен один из столбцов

• База данных не содержит пользователя с указанным адресом электронной почты

• Запись в базу данных при помощи команды UPDATE терпит неудачу

• Электронное письмо не может быть отправлено

Текущий код контроллера игнорирует некоторые из этих ошибок. Мы не проверяем, было ли UPDATE или отправка почты успешным. Мы также не принимаем во внимание, что один из классов Configuration, CryptHelper, View или ErrorView может не существовать. Поскольку мы уверены, что это, безусловно, приведет к фатальной ошибке, написание модульного теста будет излишним.

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

Контроллер обладает четвертой сложностью Npath. Поскольку оба if оператора содержат возвращаемые значения, следующие три пути исполнения для нас релевантны:

• Никакой адрес электронной почты не задан

• Для данного адреса электронной почты не найден пользователь

• Успешный путь - когда все работает правильно

Установка среды тестирования

Любой модульный тест требует особых условий для испытаний, также называемых «креплениями». Тесты креплений модульных тестов должны быть настолько простыми насколько это возможно. Сложные крепления -серьезный индикатор плохого внутреннего качества тестируемого кода.

Чтобы протестировать контроллер или resetPasswordAction () метод нам нужна база данных с таблицей Users, содержащей, по крайней мере, столбцы email и code. Мы используем базу данных SQLite, потому что SQLite доступна в PHP по умолчанию и не требует никакого стороннего сервера базы данных. Упрощенная схема базы данных, сохраненная в файле schema. sql, выглядит так: CREATE TABLE "Users" (

"id" INTEGER PRIMARY KEY AUTO_INCREMENT NOT NULL, "username" VARCHAR UNIQUE NOT NULL, "email" VARCHAR UNIQUE NOT NULL, "code" VARCHAR

) ;

Теперь нам нужна база данных SQLite. В идеале, мы используем базу данных в памяти, потому что это позволит тесту отработать быстрее и не добавит зависимости от файловой системы. Кроме того, нам не придется заботиться об удалении базы данных, когда тест закончится. Дополнительное преимущество, которое нельзя недооценивать, состоит в том, что мы

можем выполнять произвольное число модульных тестов параллельно, не заботясь о тестах, обращающихся к той же самой базе данных:

$db = new PDO ( ' sqlite : :memory : ' ) ;

$db->exec(file_get_contents(_DIR_. '/schéma.sql'));

Теперь мы создаем тестового пользователя в базе данных:

$db->exec(

"INSERT INTO Users (username, email) VALUES {'Вася Пупкин', 'user@example.ru');"

) ;

Поскольку контроллер читает «именованное хранилище данных» (DSN) го Configuration, мы, соответственно, должны его инициализировать:

Configuration::init(array('DSN' => ' sqlite:rmemory:')) ;

Чтобы смоделировать форму с полем адреса электронной почты, отправляемой POST методом, мы устанавливаем подходящий адрес электронной почты в $_POST [ ' email ' ] :

$_POST ('email'] = 'user@example.ru';

Теперь мы можем выполнить контроллер:

Scontroller = new UserController;

$view = $controller->resetPasswordAction();

Большинство из этих инициализаций станут основной функцией контроллера. Хотя мы и собираемся тестировать контроллер изолированно, мы не будем утилизировать главный контроллер. Хорошие фреймворки помогают разработчику тестировать объекты независимо от изоляции.

Возвращаемое значение легко проверить, потому что resetPasswordAction ( ) возвращает экземпляр объекта View в случае успеха и экземпляр объекта ErrorView при ошибке. И так, мы пишем два теста:

class UserControllerTest extends PHPUnit_Framework_TestCase t

protected function setUp() {

$this->db = new PDO('sqlite::memory:'); $this->db->exec(

file_get_contents(_DIR_ . '/schema.sql')

) ;

$this->db->exec(

"INSERT INTO Users (username, email) VALUES ('Вася Пушкин', 'user@example.ru');"

) ;

Configuration::init(array(' DSN' => 'sqlite::memory:')); $this->controller = new UserController;

}

protected function tearDownO {

unset($this->db); unset($this->controller); Configuration::init(array()); $_POST = array();

}

public function testDisplaysErrorViewWhenNoEmail () {

$_POST['email'] = " ;

$view = $this->controller->resetPasswordAction(); $this->assertType('ErrorView', $view);

}

public function testDisplaysViewWhenEmailAddressGiven()

{

$_POST['email'] = 'user@example.ru';

$view = $this->controller->resetPasswordAction();

$this->assertType('View', $view);

}

}

Прямо сейчас, мы не можем различить между пропущенным адресом электронной почты и несуществующим пользователем, потому что в обоих случаях, возвращается экземпляр объекта ErrorView. Мы могли бы ана-

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

Все тестовые методы выполняются независимо друг от друга. PHPUnit выполняет setUp ( ) метод перед каждым тестовым методом. setUp ( ) отвечает за установку крепления. После выполнения тестовых методов, выполняется tearDown ( ), чтобы удалить тестовое крепление. Поскольку мы должны заполнять $_POST каждый раз по-разному, мы не будем помещать это в setUp ( ) метод.

Мы не установили адрес электронной почты в первом тесте, чтобы вынудить первый if оператор выйти из метода. Во втором случае мы подставляем адрес электронной почты, так, чтобы метод завершился успешно. Наконец, при помощи setUp () мы сохраняем "Васю Пупкина" в базе данных.

Важно удостовериться, что tearDown ( ) уничтожает тестовое крепление правильным образом, особенно когда у тестированного кода есть зависимости от $_POST или Configuration.

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

Избегание глобальных зависимостей

Наш контроллер требует запроса DSN через Configuration. Это вынуждает нас инициализировать Configuration с действительными значениями по умолчанию для каждого случая. Это не так легко, как может показаться, потому что мы сперва должны прочитать код контроллера, чтобы выяснить, какие значения считаны из configuration.

Вопрос такой: должен ли контроллер действительно знать, что объект Configuration существует? Поскольку только одно значение будет читаться, зависимость от центрального класса Configuration не является обоснованной. Лучше сделать DSN параметром конструктора контроллера:

class UserController {

protected $dsn;

public function_construct($dsn)

{

$this->dsn = Sdsn;

}

// ...

}

Теперь контроллер не будет знать о Configuration, таким образом, мы можем удалить ее инициализацию из setUp ( ) и переустановить в tearDown ( ), соответственно:

class UserControllerTest extends PHPUnit_Framework_TestCase {

protected function setup() {

?this->db = new PDO(' sqlite:¡memory :') ; $this->db->exec(

file_get_contents(_DIR._ . '/schema.sql')

) ;

$this->db->exec(

"INSERT INTO Users (username, email) VALUES ('Вася Пупкин', 'user0example.ru');"

) ;

$this->controller = new UserController (' sqlite: : memory:'); }

protected function tearDown() {

unset($this->db);

unset($this->controller);

$_POST = array();

}

}

Мы улучшили дизайн приложения, удалив зависимость от контроллера. Функция заполнения контроллера конкретными DSN была передана коду, создающему экземпляр контроллера.

Тестирование независимо от хранилищ данных

Зависимость контроллера от базы данных является серьезной проблемой, потому что здесь мы смешиваем значения, которые должны быть разделены на два различных уровня приложения, а именно, доступ к данным и логика.

В данный момент, мы не можем тестировать контроллер без базы данных. Поскольку мы используем SQLite, модульные тесты не требуют внешнего сервера базы данных, но как только возникнет необходимость использовать специфические особенности баз данных, мы столкнемся с проблемой. Желательно сохранить модульные тесты независимыми от базы данных, что означает разделение проблемы доступа к данным и логики. Чтобы достигнуть этого, мы просто переместим код получения доступа к базе данных в новый класс. Поскольку мы имеем дело только с одной таблицей базы данных, мы используем паттерн "Шлюз к данным таблицы" (Table Data Gateway)54.

Новый класс должен расширять TableDataGateway, поэтому мы назовем его UsersTableDataGateway. Базовый класс, который в этом примере не совсем релевантен, может содержать методы хелпера в качестве замены параметра или конструкции WHERE:

class UsersTableDataGateway extends TableDataGateway

54 Объект, действующий как шлюз к одной таблице базы данных. См.: http://design-pattem.ni/pattems/table-data-gateway.html

{

protected $db; public function _construct(PDO $db)

{

$this->db = $db;

)

public function findUserByEmail($email) {

$statement = $this->db->prepare(

'SELECT * FROM Users WHERE email=:email;'

>;

$statenent->bindValue(':email' , $email,

PDO::PARAM_STR);

$statement->executeО;

return $statement->fetch(PDO:: FETCH__ASSOC);

}

public function updateUserWithConfirmationCode($email, $code) f

$statement = $this->db->prepare( '

'UPDATE Users SET code=:code WHERE email=:email;'

) ;

$statement->bindValue(':code', $code, PDO::PARAM_STR); $statement->bindValue(':email' , $email,

PDO::PARAM_STR) ;

$statement->execute();

}

}

Второй метод все еще не проверяет, была ли операция UPDATE успешной. Чтобы достигнуть этого, мы можем либо проанализировать код ошибки SQL, либо настроить РНР выбрасывать исключения, когда появляется ошибка базы данных. Для PDO это легко:

$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Провал операции UPDATE теперь вызывает PDOException, которое должно быть поймано и обработано в любом месте кода. Контроллер больше не общается с базой данных; вместо этого, у него появилась зависимость от TableDataGateway:

class UserController {

protected $gateway;

public function _construct(TableDataGateway $gateway)

{

$this->gateway = $gateway;

}

public function resetPasswordAction() {

// ...

$record = $this->gateway->f indUserByErr.ail ( $_POST['email']

); // ...

$this->gateway->updateUsertiithConfirmationCode(

$_POST['email'], $code

);

II ...

}

}

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

class MockUsersTableDataGateway extends TableDataGateway

' {•

public function findUserByEmail($email) {

return FALSE;

}

Мы создадим другой подкласс TableDataGateway, переписав метод f IndUserByEmail () с реализацией, которая всегда возвращает FALSE. Мок-объекты - это просто пустые объекты, которые не содержат реальной логики и не принимают решений. Они всего лишь только предлагают определенный API, который работает вроде подсказок, поскольку мок-объект является подклассом объекта, который он заменяет.

Мок-объекты возвращают сложно-кодированные данные вместо того, чтобы обращаться к внешним ресурсам. Мок-объекты используются вместо «реальных» объектов в модульных тестах, чтобы сэкономить время и проработать меньше кода, который непосредственно не тестируется. В идеале, тестируется только один объект, со всеми зависимостями замененными мок-объектами. Это сокращает количество потенциальных источников ошибок.

Наши модульные тесты больше не требуют базы данных. Не имеет значения, какой адрес электронной почты мы определяем, потому что мок-объект позволяет не искать запись в базе данных. Теперь мы тестируем путь исполнения, в котором данный адрес электронной почты не найден:

class OserControllerTest extends PHPUnit_Framework_TestCase

{

protected function setup() {

$this->gateway = new MockUsersTableDataGateway;

$this->controller = new UserController($this->gateway);

}

protected function tearDown() {

unset($this->controller);

unset($this->gateway);

$_POST = array();

}

public function testDisplaysErrorViewWhenNoUserFound () {

$_POST['email'] = 'nonsense';

$result = $this->controller->resetPasswordAction (); $this->assertType(' ErrorView', $result);

}

}

Если бы мы подключили шлюз со статическим методом UsersTableDataGateway: : f indUserByEmail (), название класса в контроллере было бы сложно-кодировано, что лишает нас возможности использовать мок-объект. Благодаря новой особенности в РНР5.3, а именно, - позднему статическому связыванию, - мы можем заменить сложно-кодированное имя класса переменной, которая в свою очередь позволяет запросить статические методы в различных классах: class UserController

{ protected $gatewayClass = 'UsersTableDataGateway';

public function setGatewayClass ($className) {

$this->gatewayClass = $className;

I

public function resetPasswordAction () (

$gatewayClass = $this->gatewayClass; H ...

$record = $gatewayC-

lass::findUserByEmail($_POST['email']); // ...

$gatewayClass::updateUserWithConfirmationCode( $_POST['email'], $code

) ;

H ...

}

class UserControllerTest extends PHPUnit_Framework_TestCase {

protected function setup()

{

$this->controller = new UserController;

$this->concroller->setGatewayClass( 'MockUsersTableDataGateway'

);

1

II ...

)

Как бы то ни было, здесь есть несколько проблем. Во-первых, мы не знаем, является ли значение, переданное в setGatewayClass () допустимым именем класса. Даже если это так, нет никакой гарантии, что класс имеет методы findüserByEmail () и

updateUserWithConfirmationCode () с соответствующими сигнатурами. Заменяя, с целью тестирования, действительный объект моком, эти две проблемы перестают волновать, потому что подсказки типа в конструкторе UserController гарантируют, что переданный (мок-) объект обладает правильным типом. Кроме того, статический шлюз может иметь состояние, нарушающее тестовую изоляцию.

PHPUnit выполняет каждый тест независимо, но в пределах одного PHP процесса. Класс, который был однажды инициализирован, должен быть явно сброшен в tearDown() используя reset () метод, в противном случае, нам придется работать в глобальном состоянии.

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

Чтобы создать новый тест, который проверяет поведение контроллера, когда UPDATE работает не так, как надо, и вызывается исключение, мы можем использовать следующий мок-объект, который, безусловно, вызовет подходящее исключение:

class MockUsersTableDataGateway extends TableDataGateway {

public function findUserByEmail($email) {

return array(

'id' => 42,

'username' => 'Вася Пупкин', 'email' => 'user@example.ru', 'code' => NULL

) ;

}

....." public function updateUserWithConfirmationCode($email,

$cod<?) {

throw new PDOBxception (/* ... */);

>

}

Мок-объект обычно используется только для одного теста. Это приведет к большому количеству «ложных» объектов, и нам нужно будет их прописывать и обслуживать. Любое изменение API объекта требует, чтобы мы применяли все полученные мок-объекты. Заглушки и ложные объекты55 являются двумя способами подменить объект с целью тестирования.

Чтобы упростить поддержку мок-объектов, PHPUnit может создавать мок-объекты практически «на лету». Это оказывается более предпочтительным, так как изменения ложного класса в таком случае влияют на мок-

55 Заглушка - это объект, который заменяет реальный объект с целью тестирования. Методы могут быть сконфигурированы, чтобы возвратить определенные значения. Мок-объект позволяет определять ожидаемые значения.

объект меньше. Это также позволяет сделать тестовый код более читаемым:

class UserControllerTest extends PHPUnit_Framework_TestCase

{

protected SusersGateway;

protected function setup()

{

$this->usersGateway = $this->getMock(

'UsersTableDataGateway', array(), array(), FALSE

) ;

I/ ...

}

// ...

public function testDisplaysErrorViewWhenNoUserFound ()

{

$this->usersGateway

->expects($this->once()) ->method('findUserByEmail') ->will($this->returnValue(FALSE));

/I ...

}

}

В setup () PHPUnit создает мок-объект для класса TableDataGateway. Метод getMock() ожидает до семи параметров, но все кроме первого являются необязательными. Первый параметр - это имя класса, который мы хотим сделать фиктивным. По умолчанию, все методы являются моками, что означает, что они заменены пустыми методами, которые ничего не делают. Второй параметр перечисляет методы, которые нужно сделать фиктивными. Если он дан, только указанные методы становятся моками. Третий параметр содержит массив значений, которые передаются как параметры конструктора в мок-объект. Четвертый параметр - это строка, которая представляет имя класса мок-объекта. Если эта

строка пуста, для мок-класса используется случайное имя. Пятый параметр содержит булево значение, которое контролирует, будет ли вызван ли конструктора мок-класса. В нашем случае важно поставить FALSE; иначе, мы должны будем передать экземпляр объекта PDO как параметр конструкто-

ра56.

Конечно, мы можем (и должны) добавить мок-объекту, по крайней мере, некоторую функциональность. PHPUnit предлагает так называемый «быстрый» интерфейс, который позволяет вызывать вложенные методы для определения ожидаемых результатов таким образом, что они создают почти читабельное предложение.

В вышеупомянутом примере мок-объект ожидает метод findUserByEmail () , который возвращает значение FALSE, чтобы быть вызванным только однажды. PHPUnit автоматически генерирует исходный код мок-объекта, который выглядит подобным коду, написанному вручную. Поскольку мок-объект является подклассом мок-класса, все определения типа соответствуют, и мок-объект может использоваться вместо реального в любое время.

PHPUnit неявно создает операторы контроля из ожидаемых данных. Если метод findUserByEmail () не вызван, или вызван не более одного раза во время выполнения

testDisplaysErrorViewWhenNoUser Found () - тест проваливается.

Теперь мы «упростили» проблему чтения пользователя с данным адресом электронной почты из базы данных, для выяснения у другого объекта, существует ли пользователь с данным адресом. На самом деле, нам не особенно важно, общается ли данный объект с базой данных или каким-либо другим внешним хранилищем данных.

56 Полная документация всех параметров может быть найдена в Руководстве по РНРШй Себастиана Бергмана, которое доступно здесь http://wTvw.phpunit.de/manualicurrent/en/index.html.

Простой рефакторинг, а именно, перемещение нескольких строк кода в новый класс, очень улучшил архитектуру приложения. Две различные проблемы: доступ к данным и логика - теперь ясно отделены друг от друга. Это увеличивает способность к тестированию. Теперь мы можем тестировать логику независимо от хранилища данных и нам больше не нужно иметь дело с креплениями базы данных.

Тестирование асинхронных событий

Давайте рассмотрим следующую проблему, а именно, отправку электронных писем при помощи контроллера. Это асинхронная коммуникация с внешней системой: функция mail() отправляет электронную почту агенту пересылки сообщений (МТА). В Unix системах это обычно делается вызовом sendmail.

В пределах PHP программы мы не можем обнаружить, когда и действительно ли МТА отправляет это электронное письмо. Булево значение, возвращаемое mail () сообщает только, была ли посланная электронная почта успешно передана МТА. FALSE таким образом является верным признаком, что отправка почты провалилась, но TRUE в действительности не означает, что письмо послали; оно только, возможно, было передано МТА.

Асинхронная отправка электронной почты имеет в этом случае преимущество: PHP программа не должна дожидаться, пока электронное письмо будет послано. Занятый почтовый сервер мог заставить программу ждать дольше, чем конечный пользователь будет готов дожидаться вывода следующей страницы HTML в браузере. Полный тест процесса почтовой пересылки, таким образом, требует интеграционных тестов. Можно было бы создать специальный почтовый ящик, который будет проверяться автоматически или даже вручную. Отправка электронной почты является асинхронным процессом, таким образом, автоматизированная проверка не проста для осуществления, так как никогда нельзя сказать наверняка, была ли почта уже получена или сколько времени еще нужно ждать.

Поскольку отправка электронной почты обладает критической функциональностью для большинства веб-приложений, можно приравнять текущий мониторинг к живой системе проверки действительности отправки электронных сообщений. Рабочая система может посылать электронные письма в специальный аккаунт каждые пять минут, а другая программа может мониторить этот аккаунт и «поднимать тревогу», если электронное письмо не было получено в течение, скажем, 10 минут. Хотя сигнал тревоги все еще не гарантирует, что электронное письмо послано: сетевые отказы или проблема достижения МТА также могут быть причиной этого.

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

Во время работы модульных тестов, можно бьшо бы сконфигурировать PHP таким образом, чтобы, вместо использования реального МТА для отправки, только входить в почту.

Это позволило бы нам без задержки проверять, были ли электронные письма фактически посланы. Модульный тест теперь имеет зависимость от файловой системы, тем не менее, электронные письма должны быть где-нибудь зарегистрированы. Если многочисленные тесты отправки электронных писем выполняются одновременно, трудно сказать, который из них отправил какое письмо. Таким образом, зависимость от файловой системы нарушает испытательную изоляцию.

Лучший вариант состоит в том, чтобы передать вызов функции mail () новому методу или функции. Многие разработчики используют глобальную константу, запускающую программу в тестовом режиме:

function send_mail{)

{

if (TESTJMODE) {

log(/* ... */); } else {

mail{/* ... */);

}

}

Такое решение действительно даст результат, но неопределенная константа TEST_MODE становится дополнительным источником ошибок. Если другие части приложения содержат подобные варианты дифференциации, система может не работать как ожидается, если будет активирован тестовый режим. В вышеупомянутом примере электронные письма больше не будут посылаться. Таким образом, намного лучше выполнить дополнительный вход в систему в тестовом режиме:

function send_mail()

{

if (TEST_MODE) {

log (/* ...*/); } mail(/* ... */);

}

Это, однако, означает, что реальные электронные письма будут посланы во время выполнения модульного теста. В большинстве случаев, клиентов не стоит беспокоить тестовыми электронными письмами, поэтому, код отправки электронных сообщений с целью модульного тестирования должен быть заменен мок-объектом.

Мок-объект, конечно, не отправляет e-mail. Подобно тому, что мы делали в предыдущем разделе, тест всего лишь удостоверяется, что мы хотим

послать электронное письмо. Нам все равно, посылается ли электронное письмо фактически.

Класс Mailer - очень простой:

class Mailer {

public function send($recipient, $subject, $content) {

return mail($recipient, $subject, $content);

}

}

У контроллера теперь есть новая зависимость, а именно, зависимость от Mailer. Соответственно, мы должны изменить конструктор:

class UserController {

protected $gateway; protected $mailer;

public function _construct(TableDataGateway $gateway,

Mailer $mailer) {

$this->gateway = $gateway; $this->mailer = Smailer;

}

// ...

)

В методе resetPasswordAction () мы заменяем вызов mail () вызовом метода send () в Mailer:

class UserController t

public function resetPasswordAction() {

// ...

$this->mailer->send( $ POST ['email'5,

' Password Reset', 'Confirmation code: ' . $code

) ;

I/ ...

}

Класс Mailer можно протестировать отдельными модульными тестами, чтобы удостовериться, что он действительно может послать электронные письма. Преимущество состоит в том, что электронные письма должны послаться в единственную тестовую учетную запись, а не на адреса реальных пользователей. Если Mailer использует SMTP вместо функции mail (), мы могли бы сделать запись связи между Mailer и реальным почтовым сервером, просто для того, чтобы заменить его ложным объектом для модульного теста.

Опять же мы улучшили архитектуру приложения только простым ре-факторингом. Мы можем изменить реализацию Mailer в любое время, например, чтобы отправить электронное письмо через SMTP. Любые дополнительные параметры, которые могут стать параметрами конструктора альтернативной реализации, таким образом, UserController не должен взаимодействовать с ними.

Пока создание объекта строго отделено от использования объекта, - это будет работать нормально. Если бы мы должны были создавать экземпляр Mailer непосредственно в контроллере, тогда либо контроллер должен был бы знать все зависимости Mailer, либо мы должны были бы читать все параметры из центральной конфигурации.

Теперь мы можем написать модульный тест, который проверяет, посылает ли контроллер электронное письмо пользователю:

class UserControllerTest extends PHPUnit_Framework_TestCase

{

protected function setup()

и ...

$this->mailer = $this->getMock(

'Mailer', array (), array О , ", FALSE

);

$this->controller = new UserController( $this->usersGateway, $this->mailer

) ; // ...

}

protected function tearDownO {

// ...

unset($this->mailer) ; // ...

}

public function testSendsEmailToTheUser() i

$_P0ST['email'] = 'user9example.ru $usersGateway

->expects($this->once()) ->method(' findUserByEraail' ) ->with('userSexample.ru ->will($this->returnValue( array('id' => 42, 'username' => 'Вася Пупкин' 'email' => 'user@example.ru 'code' => NULL))) ; $mailer

->expects($this->once()) ->method('send') ' ->with('userSexample.ru;

$this->controller->resetPasswordAction();

}

Этот тест требует ложного шлюза, который возвращает запись для данного адреса электронной почты. Пункт with ( 'usergexample. ru' ) проверяет, что контроллер передает правильный адрес электронной почты в шлюз. Ложный шлюз работает, как будто он нашел соответствующую запись в базе данных и возвращает ее. Точно так же как в реальной почтовой программе, send() метод ложной почтовой программы ожидает user@example.ru в качестве первого параметра. Пока мы игнорируем дополнительные параметры.

Мы все еще не проверяем, проходит ли отправка почты не так, как надо. Чтобы сделать это, мы должны немного изменить тестируемый метод контроллера. Фиктивная почтовая программа может быть сконфигурирована, чтобы возвращать булево значение вызовом will($this-> returnValue (FALSE) ). Это позволит нам симулировать, что отправка действительной почты идет не так, как надо, даже не пытаясь фактически послать электронное письмо. Хранение изменений в Базе данных

Если наш контроллер находит пользователя с соответствующим адресом электронной почты, - отправляется e-mail, содержащий случайный код подтверждения. Метод resetPasswordAction () должен сохранить код подтверждения в базе данных, таким образом, мы можем позже сверить его с учетными записями пользователей.

Мы переместили все функции взаимодействия с базой данных в свой собственный класс. Теперь мы должны удостовериться, что контроллер вызывает updateUserWithConfirmationCode () метод с верными параметрами. Чтобы добиться этого, мы определяем другое ожидание для нашего ложного объекта: class UserControllerTest extends PHPUnit_Framework_TestCase { public function testStoresConfirmationCode{)

$_POST['email'] = 'userSexample.ru'; $this->usersGateway

->expects($this->once()) ->method('findUserByEmail') ->with('userSexample.ru' ) ->will($this->returnValue(

array('id' => 42,

'username' => 'Вася Пупкин', Nemail' => 'user@example.ru', 'code' => NULL))); $this->usersGateway

->expects($this->once())

->method('updateUserWithConfirmationCode')

->with('user@example.ru') ; // ..•

Поскольку мы хотим протестировать побочные эффекты контроллера, а не его реализацию, нам все равно, в каком порядке вызываются методы в ложном шлюзе. Отметим, что мы создали новый тест ^Б^гезСог* 1ппа1:1опСос1е () вместо того, чтобы добавить новое ожидание к testSendsEmailToUser () - Написание отдельного теста для каждого функционального аспекта является хорошей практикой. Это „е только позволит провальному модульному тесту ясно сообщать, что пошло не так, но также составляет для модульных тестов документацию всех особенностей тестируемого кода. Этого помогает достигнуть ясная схема именования тестов. Вызывая РНРШ* с флагом тесто-

вая документация автоматически генерируется из имен тестовых методов.

Модульный тест проверяет,

updateUserWithConfirmationCode () вызывается для пользователя,

который был определен через POST. До сих пор, мы игнорировали второй параметр, а именно, код подтверждения.

Непредсказуемые результаты

Как мы можем удостовериться, что действительный код подтверждения передается при вызове метода

updateUserWithConfirmationCode ()? Очевидное решение состоит в том, чтобы изменить ожидания фиктивного шлюза ожиданием второго действительного параметра. Поскольку CryptHelper возвращает случайное значение, однако, определение другого ожидания невозможно. Мы даже не можем создать оператор контроля для кода подтверждения, потому что это всего лишь локальная переменная в контроллере.

Данное решение не делает код подтверждения публичным атрибутом контроллера и не добавляет метод доступа. Оно заменяет случайное значение предсказуемым значением с целью тестирования. Это значение может использоваться в ожидании.

Пока CryptHelper выглядит так:

class CryptHelper

{

protected static $salt = '...';

public static function getConfirmationCode() {

return shal(uniqid(self::$salt, TRUE));

}

}

getConf irmationCode () метод является статическим, поэтому мы

имеем сложно-кодируемое имя класса CryptHelper в контроллере:

class UserController

{

public function resetPasswordAction() {

$code = CryptHelper::getConfirmationCode();

}

Статические методы нельзя подменить без возникновения отрицательных эффектов, о которых упоминалось ранее, поэтому мы модифицируем CryptHelper, чтобы использовать методы экземпляра объекта вместо

статических методов:

class CryptHelper

I

protected static $salt = '■■■'•

public function getConfirmationCodeO {

return shal(uniqid(self::$salt, TRUE));

}

}

CryptHelper теперь превращается в очередную зависимость UserController, которую можно легко заменить мок-объектом:

class UserControllerTest extends PHPUnit_Framework_TestCase {

protected function setup О {

// ...

$this->cryptHelper = $this->getMock(

'CryptHelper', array О, array(), ", FALSE

) ;

$this->controller = new UserController(

$this->usersGateway, $this->mailer, $this-

>cryptHelper ) ; // ...

}

protected function tearDownO {

// ...

unset($this->cryptHelper); U ...

public function testStoresConfirmationCode() {

// ...

$cryptHelper

->expects(Sthis->once()) ->method('getConfirmationCode') ->will($this->returnValue('123456789')); $this->usersGateway

->expects($this->once()) ->method('updateUserWithConfirmationCode') ->with('user@example.ru', '123456789'); II ...

}

}

Мы должны адоптировать UserController:

class UserController {

protected Sgateway; protected $mailer; protected $cryptHelper;

public function _construct(TableDataGateway $gateway,

Mailer $mailer, CryptHelper $cryptHelper)

i

$this->gateway = $gateway; $this->mailer = $mailer; $this->cryptHelper = $cryptHelper;

}

public function resetPasswordAction() {

п ...

$code = $this->cryptHelper->getConfirmationCode();

II ...

}

}

Модульный тест больше не зависит от случайных значений. Мы используем «сложный» код подтверждения вместо того, который может легко использоваться в качестве ожидаемого значения. Если бы мы нуждались в многократных кодах подтверждения для одного модульного теста, то мы могли бы написать мок-объект, который возвращает одно значение из последовательности кодов подтверждения:

class MockCryptHelper {

protected $index = -1;

protected $codes = array('...', '...'/ '•■•'/ •••)'

public function getConfirmationCode() {

$this->index++;

return $this->codes[$this->index];

}

}

Чтобы сохранить пример простым, мы просто проигнорируем факт, что тест мог бы запросить больше кодов подтверждения, чем содержит мок-объект. Мок-объект заново генерируется для каждого теста, таким образом, этот вариант никогда не реализуется. Инкапсуляция входных данных

UserController теперь полностью тестирован. Однако все еще есть дополнительный потенциал для усовершенствований. У контроллера нет больше никаких неявных зависимостей от других классов, но он читает ввод из суперглобального массива $_POST.

Чтобы избавиться от этой последней глобальной зависимости, мы вводим объект Request, который содержит все входные данные:

class Request {

protected $post = array();

public function _construct(array $post)

$this->post = $post;

public function getPost($key)

return $this->post[$key];

public function hasPost($key)

return erapty($this->post[$key]);

}

Пример снова упрощен. Мы только показываем методы для обработки данных POST. Реальный объект Request также содержал бы аксессоры данных GET, cookies, и вероятно всех остальных суперглобальных переменных $_SERVER, $_ENV, и $_FILES57. Благодаря использованию объекта Request, контроллеры и, надеемся, остальные части приложения становятся независимыми от суперглобальных переменных. Мы создали другую явную зависимость в форме параметра, но в контроллере нет больше неявных, глобальных зависимостей:

class UserController {

public function resetPasswordAction(Request $request) {

57 Мы сознательно игнорируем $ REQUEST, потому что никогда нельзя сказать, откуда данные в $_REQUEST поступают, что может быстро привести к проблемам безопасности.

п ...

}

Вместо того чтобы передать Request в метод экшена, мы также могли бы сделать его параметром конструктора контроллера. Поскольку мы создаем объект Request в тестовом методе, а не в setup (), лучше сделать Request параметром метода экшена. В противном случае, мы должны были бы изменить Request, чтобы запрашивать входные данные в качестве параметров конструктора.

Теперь особенно легко «фальсифицировать» входные данные:

class UserControllerTest extends PHPUnit_Framework_TestCase {

public function testDisplaysErrorViewWhenNoUserFound () {

$request = new Request(

array('email' => 'user@example.com')

) ;

$resuit = $controller->resetPasswordAction($request);

П ...

}

}

Инкапсуляция ввода также позволяет намного легче тестировать сложный ввод как, например, загрузка файла в изоляции. К тому же, входные данные не могут быть изменены, конечно, если мы не добавим сеттеры. После того, как Request был инициализирован в файле начальной загрузки приложения, мы можем удалить суперглобальные переменные, чтобы заставить разработчиков читать ввод из объекта Request. Последующая рефлексия

Так же, как мы инкапсулировали входные данные в объекте Request, мы можем инкапсулировать весь вывод в объекте Response. Это позволяет нам полностью отделить контроллеры от представлений. Вместо того

чтобы установить вывод в представлении, мы можем хранить его в объекте Response. В зависимости от успеха метода контроллера мы можем ин-стандиировать подходящий объект представления, который извлекает данные из объекта Response. Это делает Response посредником между контроллером и представлением. Намного легче иметь дело с cookies и заголовками, потому что они хранятся вместо того, чтобы непосредственно выводиться.

Фактический вывод сгенерирован из информации в Response:

class Response

{

public function addError(/* ... */)

{

}

II ...

}

Все сообщения об ошибках также собраны в Response. Поэтому контроллер не должен возвращать экземпляр объекта ErrorView, чтобы вывести ошибку на экран: он должен только установить соответствующее сообщение об ошибке. С целью тестирования, мы можем либо использовать реальный объект Response либо мок, когда мы определяем сообщения об ошибках как ожидаемые. Сообщения об ошибках могут быть объектами, содержащими информацию о полях формы, к которым они относятся.

ЗАКЛЮЧЕНИЕ

В данной работе мы рассмотрели разные подходы и методы обеспечения качества программного обеспечения. Важную роль в этом занимает тестирование, хотя тесты сами по себе не могут ни улучшить качество программного обеспечения, ни доказать, что оно свободно от ошибок. Тесты могут только доказать существование ошибок, но никогда их отсутствие. Обширное пространство возможных вводов лишает возможности, даже для простого веб-приложения, доказать, что программное обеспечение работает правильно для всех вводов.

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

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

Программное обеспечение обычно живет дольше, чем первоначально ожидается. Веб-приложения особенно известны высокой частотой изменяемости, что делает высокое внутреннее качество жизненно необходимым условием. Итеративная разработка и методы agile помогают в производстве высококачественного программного обеспечения. Не особенно важным является вопрос, какие из множества agile методов используются, пока разработчики производят программное обеспечение «маленькими шагами». Программное обеспечение часто разрабатывается с использованием причудливого соединения agile методик, которые должны быть представлены клиенту в качестве неитеративной разработки через использование классического подход водопада. У особенно крупных компаний, так же как правительственных учреждений, есть, в некоторых случаях, очень строгие и ограничивающие инструкции относительно того, каким образом должен быть выполнен проект разработки программы. Эти инструкции очень часто предназначены для разработчиков аппаратных приложений и являются неподходящими для разработки веб-приложений высокого качества.

Частые изменения - неотъемлемая часть усилий по разработке программного обеспечения. Изменяющиеся требования делают непрерывные изменения кода необходимыми. Архитектура программного обеспечения и процессы разработки должны, поэтому, вдохновлять изменения и делать их настолько легкими насколько возможно. В конечном счете, непрерывный рефакторинг программного обеспечения обязателен для сохранения программного обеспечения поддерживаемым. Рефакторинг без автоматизированных модульных тестов - опасен. Тестовая автоматизация существенна для предотвращения регресса вследствие высокой частота изменений кода. Удобочитаемость и ремонтопригодность кода даже более важны, чем максимально возможная производительность.

Нужно начинать тестировать программное обеспечение как можно раньше и меньшими блоками. Существует непосредственная связь между

высоким внутренним качеством и тестируемостью. Поэтому разумно будет писать тесты как можно ближе по времени к написанию того кода, для которого они будут созданы. Код, который трудно тестировать - плохой код. Но тестируемый код - хороший код, потому что у него мало зависимостей, мало ветвей исполнения, короткие блоки кода, и ясно распределенные обязанности.

Факты, что некоторые тесты, как например тесты юзабилити, трудно „ли даже невозможно автоматизировать, не должен препятствовать автоматизации тестов и созданию процессов в максимально возможной степени. Проект непосредственно извлекает выгоду из воспроизводимости, которую гарантирует автоматизация и косвенно приносит выгоду из того факта, что автоматизация является необходимым условием для непрерывной интеграции и инспекции кода. Это позволяет беспристрастно оценить

состояние кода и проекта в целом.

Системные тесты не всегда легко осуществить, особенно для сложных программных систем. Когда бы ни было возможно, нужно избегать написания системных тестов вместо модульных. Когда написание модульных тестов становится трудным, - это обычно указывает на слишком большое количество связей между отдельными компонентами программного обеспечения, например, вследствие использования глобального состояния. Попытки заменить зависимости удвоением тестов является бесполезным трудом, таким образом, увеличивая размер требуемых для тестирования усилий. Системные тесты должны дополнять модульные тесты и тестировать программное обеспечение вширь, а не вглубь. Таким образом, системные

тесты не являются заменой для модульных тестов.

Отдельные уровни архитектуры должны быть чисто отделены друг от друга. Недостаточное разделение уровней архитектуры, например, смешение бизнес-логаки и доступа к данным (логика постоянства) через использование Объектно-реляционного отображения, основанного на Active

Record — приводит к столь сложным модульным тестам, что их стоило бы назвать системными тестами. Взаимодействие с базой данных, так же как рендеринг вывода необходимо тестировать. Однако нужно сосредоточиться на надлежащем разделении этих проблем и прежде всего, тестировать их отдельные реализации изолированно. Более сложные тесты в таком случае играют роль интеграционных тестов, которые гарантируют, что компоненты, тестированные в изоляции соответствующими модульными тестами, также работают правильно, когда используются вместе. Пока отдельные компоненты взаимодействуют только друг с другом, основываясь на ясных и определенных программных интерфейсах (API), написание этих интеграционных тестов не должно вызывать проблем.

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

БИБЛИОГРАФИЯ

1. Ambler, Scott W. Agile Modeling: Effective Practices for Extreme Programming and the Unified Process. Wiley & Sons, 2002. ISBN 0-4712-0282-7.

2. Balzert, Helmut. Lehrbuch der Sofhvaretechnik. Bd. 1. SoftwareEntwicklung. Spektrum Akademischer Verlag, 2005. ISBN 3-8274-0480-0.

3. Basiii, Victor R., Gianluigi Caldiera, and H. Dieter Rombach. "Goal Question Metrie Paradigm." In Encyclopedia of Software Engineering, 2 Volume Set. John Wiley & Sons, 1994. ISBN 1-54004-8.

4. Beck, Kent. Test-Driven Development: By Example. Addison-Wesley, 2003. ISBN 0-3211-4653-0.

5. Bergmann, Sebastian. "Isolated (and Parallel) Test Execution in PHPUnit 4," December 19, 2007, accessed April 8, 2010, http://sebastian-

bergmann.de/archives/730-Isolated-and-Parallel-Test-Execution-in-PHPUnit-

4.html.

6. _. "PHPUnit Manual," 2010, accessed May 3, 2010,

http://vww.phpunit.de/manual/current/en/index.html.

7. _. Professionelle Softwareentwicklung mit PHP 5: Objektorientierung, Entwurfsmuster, Modellierung und fortgeschrittene Datenbankprogrammierung. dpunkt.verlag, 2005. ISBN 978-3-89864-229-3.

8. _. "Testing Your Privates," February 9, 2010, accessed April 8,

2010, http://sebastian-bergmann.de/archives/881-Testing-Your-Privates.html.

9. Bevan, Nigel. "Quality in use: Meeting user needs for quality," Journal of Systems and Software, Volume 49, Issue 1 (December 1999), Pages 89-96. ISSN 0164-1212.

10. Blair, Neate. "Code Rank: A New Family of Software Metrics," IEEE Software Engineering Conference, 2006. Warwick Irwin, Neville Churcher,

Australia, 2006.

11. Boehm, Barry, Ricardo Valerdi, and Eric Honour. "The ROI of Systems Engineering: Some Quantitative Results for Software-Intensive Systems," Systems Engineering, Volume 11, Issue 3 (August 2008), Pages 221-234. ISSN 1098-1241.

12. Bruntink, Magiel and Arie van Deursen. "Predicting Class Testability using Object-Oriented Metrics," SCAM '04: Proceedings of the Source Code Analysis and Manipulation, Fourth IEEE International Workshop, 2004, Pages

136-145. ISBN 0-7695-2144-4.

13. Carr, James. "TDD Anti-Patterns," November 3, 2006, accessed February 9,2010, http://blog.james-carr.Org/2006/l 1/03/tdd-anti-patterns/.

' 14. Cunningham, Ward. "The WyCash Portfolio Management System," March 26,1992, accessed April 17,2010, http://c2.com/doc/oopsla92.html.

15. Dijkstra, Edsger W. "The humble programmer," Communications of the ACM, Volume 45, Issue 10 (October 1972), Pages 859-866. ISSN 0001 -0782.

16. Duvall, Paul M. Continuous Integration-Improving Software Quality and Reducing Risks. Addison-Wesley, 2007. ISBN 978-0-321-33638-5.

17. Ellis, Tim. "Digg Database Architecture," September 12, 2008, accessed February 9,2010, http://about.digg.comA5log/digg-database-architecture.

18. Evans, Cal. "Fluent Interfaces in PHP," December 20, 2006, accessed February 9,2010, http://devzone.zend.com/article/1362.

19. Fleischer, André. "Metriken im Praktischen Einsatz," ObjektSpektrum, Issue 03/2007. ISSN 0945-0491.

20. Foegen, Malte, Mareike Solbach, and Claudia Raak. Der Weg zur professionellen IT: Eine prak-tische Anleitung für das Management von Veränderungen mit CMMI, ITIL oder SPICE. Springer, 2007. ISBN 978-3-540-72471-1.

21. Fowler, Martin. "Fluent Interface," December 20, 2005, accessed February 9,2010, http://martinfowler.com/bliki/FIuentInterface.html.

22 .-. Patterns flir Enterprise Application-Architekturen. MITP-Verlag,

2003. ISBN 978-3-8-266-1378-4.

23 .-. Patterns of Enterprise Application Architecture. Addison-Wesley,

2002. ISBN 0-3211-2742-0.

24 .-. Refactoring: Improving the Design of Existing Code. Addison-

Wesley, 1999. ISBN 978-0-2014-8567-7.

25. Franz, Klaus. Handbuch zum Testen von Web-Applikationen. Springer, 2007. ISBN 978-3-540-24539-1.

26. Freeman, Steve and Nat Pryce. Growing Object-Oriented Software, Guided by Tests. Addison-Wesley, 2009. ISBN 978-0-321-50362-6.

27. Gartner Inc. "Dynamic Programming Languages Will Be Critical to the Success of Many Next-Generation AD Efforts," 2008, accessed April 10, 2010, http://www.gartner.com/DisplayDocument?ref=g_search&id=832417.

28. Goldberg, David. "What Every Computer Scientist Should Know About Floating-Point Arithmetic," ACM Computing Surveys (CSUR), Volume 23, Issue 1 (March 1991), Pages 5-48. ISSN 0360-0300.

29. Grady, Robert and Deborah Caswell. Software Metrics: Establishing a Company-Wide Program. Englewood Cliffs, Prentice Hall, 1987. ISBN 978-0138218447.Grochtdreis, Jens. "Schöner Navigationstitel," October 18, 2009, accessed February 9, 2010, http://grochtdreis.de/weblog/2009/10/18/schoener-navigationstitel/.

30. High Scalability. "Performance Anti-Pattern," April 4, 2009, accessed February 9, 2010, http://highscalability.com/blog/2009/4/5/performance-anti-pattern.html address.

31. Huggins, Jason. Twitter post, August 22, 2009, accessed April 15, 2010,

http7/twitter.com/hugs/status/3462632802.

32 Huggins, Jason and Jen Bevan. "Extending Selenium Into the Browser and Out to the Grid," Vortrag auf der Google Testing Automatic Conference, New York, 2007, accessed April 9, 2009,

http" //www.youtube.com/watch? v=qxBatJ lN_Og.

33 International Organization for Standardization. "1SO/IEC12207: 2008 Systems and Software Engineering-Software Life Cycle Processes," 2008-0318, Geneva, Switzerland, 2008.

3 4_ "ISO/IEC15504-5: 2006 Information Technology-Process Assessment-Part 5: An Exemplar Process Assessment Model," 2006-03-07, Geneva, Switzerland, 2006.

35 _"ISO/IEC 9126-1: Software Engineering-Product Quality-Part 1:

Quality Model," 2008-07-29, Geneva, Switzerland, 2008.

36 Janzen David S. "Software Architecture Improvement through Test-Driven Development," Companion to the 20th annual ACM SIGPLAN conference on Object-oriented programming, sys-tems, languages, and applications. Association for Computing Machinery, 2005. ISBN 1-59593-193-7.

37. Jeffries, Ron. "Quality vs Speed? I Don't Think So!," April 29, 2010, accessed May 1,2010, http://xprogramming.com/articles/quality/.

38 Kerckhoffs, Auguste. "La cryptographie militate," Journal des sciences militaires, Volume 9, Pages 5-38 (January 1883), Pages 161-191 (February

1883).

39 Khan, R. A. and K. Mustafa. "Metric Based Testability Model for Object Oriented Design (MTMOOD)," SIGSOFT Software Engineering Notes, Volume 34, Issue 2 (March 2009), Pages 1^6. ISSN 0163-5948.

40. Kniberg, Henrik. "Version Control for Multiple Agile Teams," InfoQ, March 31, 2008, accessed April 25, 2010, http://www.infoq.com/articles/agile-

version-control.

41. Kunz, Christopher and Stefan Esser. PHP-Sicherheit. dpunktverlag, 2008. ISBN 978-3-89864-535-5.

42. Lanza, Michele and Radu Marinescu. Object-Oriented Metrics in Practice: Using Software Metrics to Characterize, Evaluate, and Improve the Design of Object-Oriented Systems. Springer, 2006. ISBN 978-3-540-24429-5.

43. Lennartz, Sven. "CSS-Sprites Quellensammlung," 2009, accessed February 9,2010, http://www.drweb.de/magazin/css-sprites-quellensammlung/.

44. Lienberherr, K. J. "Formulations and Benefits of the Law of Demeter," ACM SIGPLAN Notices, Volume 24, Issue 3 (March 1989), Pages 67-8. ISSN 0362-1340.

45. Liggesmeyer, Peter. Software-Qualität: Testen, Analysieren, und Verifizieren von Software. Spektrum Akademischer Verlag, 2009. ISBN 978-3 8274-2056-5.

46. Maciejewski, David and Dirk Jesse. "Performance-Optimierung: Barrierefreiheit beginnt mit Ladezeiten," 2009, accessed February 9, 2010, http://www.slideshare.net/dmacx/performance-optimierung-barrierefreiheit-beginnt-mit-ladezeiten.

47. Marr, Stefan. "Horizontal Reuse for PHP," 2010, accessed April 18,2010, http://wiki.php.net/rfc/horizontalreuse.

48. Martin, Robert C. Agile Software Development: Principles, Patterns, and Practices. Prentice Hall International, 2002. ISBN 978-0-135-97444-5.

49 .-. Clean Code: A Handbook of Agile Software Craftsmanship.

Prentice Hall International, 2008. ISBN 978-0-132-35088-4.

50. Martin, Rett. "How to Discuss 'the Fold' with a Client," January 8, 2010, accessed February 9, 2010, http://www.clockwork.net/blog/2010/01/08/3 72/how_to_discuss_theJbld_with _a_client.

51.McCabe, Thomas J. "A Complexity Measure," IEEE Transactions on Software Engineering Volume 2, No. 4. IEEE Computer Society Press, Los Alamitos, CA, USA, 1976.

52. Meszaros, Gerard. xUnit Test Patterns: Refactoring Test Code. Addison-Wesley, 2007. ISBN 978-0-131-49505-0.

53. Meyer, Eric S. "Unitless line-heights," February 8, 2006, accessed February 9, 2010, http://meyerweb.com/eric/thoughts/2006/02/08/unitless-line-heights/.

54. Nejmeh, Brian A. "NPATH: A Measure of Execution Path Complexity and its Applications," Communications of the ACM, Volume 31, Issue 2 (February 1988), Pages 188-200. ISSN 0001-0782.

55. Nohn, Sebastian. "Continuous Builds with CruiseControl, Ant, and PHPUnit," March 7, 2006, accessed April 28, 2010,

http://nohn.net/blog/view/id/cruisecontrol_ant_and_phpunit.

56. OpenQA FAQ. "OpenQA FAQ-Selenium File Upload with Internet Explorer,"

http-//wiki.openqa.org/display/SEL/Selenium+Core+FAQ\#SeleniumCoreFAQ-Ican\%27tseemtouseSeleniumCoretouploadafile\%3BwhenItrytotypeinthefileupl

oadtextfield\%2Cnothinghappens\%21.

57. Pähl, Dirk. "Automated acceptance tests using Selenium Grid without pa-rallelization," August 17, 2009, accessed February 9, 2010,

http://developer.studivz.net/2009/08/17/automated-acceptance-tests-usmg-

selenium-grid-without-parallelization/.

58. PEAR. "PEAR Manual," PHP Group, 2009, accessed April 15, 2010,

http://pear.php.net/manual/.

59. Potencier, Fabien. "What Is Dependency Injection?," March 26, 2009, accessed February 9, 2010, http://fabien.potencier.org/article/ll/what-is-

dependency-inj ection.

60. Priebsch, Stefan. PHP migrieren: Konzepte und Lösungen zur Migration von PHP-Anwendungen und -Umgebungen. Carl Hanser Verlag, 2008. ISBN

978-3-446-41394-8.

61. Schneider, Kurt. Abenteuer Softwarequalität-Grundlagen und Verfahren für Qualitätssicherung und Qualitätsmanagement, dpunktverlag, 2007. ISBN

978-3-89864-472-3.

62. Selenium Grid. "Selenium Grid," OpenQA 2008, http://selemum-

grid.seleniumhq.org/.

63. Souders, Steve, "don't use ©import," April 9,2009, accessed February 9,

2010, http://www.stevesouders.com/blog/2009/04/09/dont-use-import/.

64'. TIOBE Software BV. "TIOBE Programming Community Index for December 2010," 2010, accessed December, 2010,

http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html.

65. Whitgíft, David. Methods and Tools for Software Confi guration Management. John Wiley & Sons, 1991. ISBN 0-4719-2940-6.

66. Wikipedia. "Big Ball of Mud," 2010, accessed February 9, 2010, http://en.wikipedia.org/wiki/Big_ball_of_mud.

67 .-. "Big Design Up Front," 2010, accessed February 9, 2010,

http://en.wikipedia.org/wiki/Big_Design_Up_Front.

68 .-. "Brotkrtimelnavigation," 2010, accessed February 9, 2010,

http://de.wikipedia.org/wiki/Brotkrmelnavigation.

69 .-. "Gebrauchstauglichkeit," 2010, accessed February 9, 2010,

http://de.wikipedia.org/wiki/Gebrauchstauglichkeit.

70 .-. "K-factor (marketing)," 2010, accessed February 9, 2010,

http://en.wikipedia.org/wiki/K-factor_(marketing).

71 .-. "Lint (Programmierwerkzeug)," 2009, accessed April 21, 2009,

http://de.wikipedia.org/wiki/Lint_(Programmierwerkzeug).

72 .-. "Lock (computer science)," 2010, accessed February 9, 2010,

http://en.wikipedia.org/wiki/Lock_Ccomputer_science).

73 .-. "Model-View-Controller," 2010, accessed February 9, 2010,

http://en.wikipedia.org/wiki/Model-view-controller.

74 .-. "Multiton Pattern," 2010, accessed February 9, 2010,

http://en.wikipedia.org/wiki/Multitonjpattern.

75 .-. "Observer," 2010, accessed February 9, 2010,

http://de.wikipedia.org/wiki/Observer_(Entwurfsmuster).

76 .-. "OSI Model," 2010, accessed February 9, 2010,

http://en.wikipedia.org/wiki/OSI_mode].

77 .-. "PageRank," 2009, accessed April 18, 2009,

http://en.wikipedia.org/wiki/PageRank.

78 .-. "Partition (database)," 2010, accessed February 9, 2010,

http://en.wikipedia.org/wiki/Partition_(database).

79 .-. "Shard (database architecture)," 2010, accessed February 9,

2010, http://en.wikipedia.org/wiki/Sharding.

80 .-. "Visitor," 2010, accessed February 9, 2010,

http://de.wikipedia.org/wiki/Visitor.

81 ._. "Zuständigkeitskette," 2010, accessed February 9, 2010,

http://de.wikipedia.org/wiki/Chain_of_Responsibility.

82. Wirth, Timo. "Nutzerbeteiligung & Kommunikation: Mitmachbarrieren im Web 2.0," 2009, accessed February 9, 2010,

http://www.slideshare.net/aperto/mitmachbarrieren-im-web-20.

83. Yourdon, Edward and Larry Constantine. Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design. Prentice Hall, 1979. ISBN 978-0138544713.

Технический редактор: А.В .Жильцов Подписано в печать 06.07.2011. Формат 60x84 1 /16. Усл. печ. л. 6,0. Тираж 100 экз. Заказ № 307. Тверской государственный университет Редакционно-издательское управление

Адрес: 170100, г. Тверь, ул. Желябова, 33. Тел. РИУ: (4822) 35-60-63