You are on page 1of 67

Сентябрь '05 No 14

Управление
тестированием
Интервью с Ильей Альшанетским
FAQ: Как помочь в развитии PHP?

издается с февраля 2004 года, www.phpinside.ru


PHP Inside №14

Содержание
В фокусе
Исправление ошибок: в нужное время и в нужном месте. Часть I............................................3
Исправление ошибок: в нужное время и в нужном месте. Часть II ........................................10
Функциональное тестирование при помощи Selenium.............................................................16
Люди
Интервью с Ильей Альшанетским..............................................................................................37
FAQ: Как принять участие в развитие PHP?..............................................................................41
Идеи
Библиотека dbtree для работы с деревьями Nested Sets............................................................48
Введение в разработку веб-приложений с Ajax........................................................................ 59
Множественное наследование в ОО модели PHP..................................................................... 64

От редактора
В первом осеннем выпуске PHP Inside мы затронем много раз- Команда номера
личных тем. Темой номера "В Фокусе" является "Управление тести-
рованием" и в рамках темы представлена статья об управлении Авторы и переводчики
ошибками, а так же, Сергей Юдин расскажет об инструменте тести- Кузьма Феськов
Андрей Олищук
рования веб-приложений "Selenium".
Сергей Юдин
Еще одним интересным моментом этого номера стало интер- Денис Баженов
вью с Ильей Альшанетским - активным разработчиком PHP и неко- Антон Довгаль
торых проектов, таких как FUDforum и eGroupware. В интервью он Алексей Борзов
поведал нам о своем опыте работы релиз-менеджером PHP и расска- Александр Войцеховский
Григорий Федоринов
зал о "кухне" выпусков новых релизов этого языка.
Так же мы не смогли пройти мимо материалов, посвященных Редакционная коллегия
интересным и полезным технологиям и алгоритмам: Ajax и Nested Александр Смирнов
Sets. Для сторонников Nested Sets мы публикуем пример работы с Александр Войцеховский
классом dbtree, написанный автором самого класса - Кузьмой Фесь- Андрей Олищук [nw]
ковым. Dbtree получает признание за рубежом (Мануель Лемос, ве- Антон Чаплыгин
Елена Тесля
дущий проекта phpclasses.org сыграл в этом не последнюю роль) и
теперь пришло время познакомить и наших соотечественников (тех
Выпуск номера
кто еще не успел) с этим действительно мощным инструментом
Андрей Олищук [nw]
управления иерархическими структурами в PHP. Денис Зенькович
И в завершении, Денис Баженов в статье "Множественное на- Антон Чаплыгин
следование в ОО модели PHP" рассказал о своих идеях реализации Андрей Махнач
Денис Бесков-Доронин
множественного наследования.
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть I

В фокусе
Исправление ошибок: в нужное время
и в нужном месте. Часть I
Автор: Scott Berkun
Перевод: Андрей Олищук

Управление ошибками – это целая наука, азы ко-


торой преподает Скотт Беркун, автор книги по
управлению проектами

В работе с ошибками есть одно золотое правило: исправ-


лять ошибки нужно в том порядке, который вероятнее всего при-
ближает к успеху. Звучит банально, не правда ли? Неправда. Могу
поспорить, что свыше половины «глючных» и ненадёжных про-
грамм, которые вам приходилось использовать, были такими не
потому, что разработчикам не хватило времени сделать их луч-
ше. Просто они исправляли не те ошибки. Желание исправить
«нужные» ошибки и знание того, каким образом это сделать, --
две разные вещи.
При попытке продуманного подхода к работе с ошибками воз-
никает две основные трудности: первая - необходимо знать, как при-
нять правильное решение по поводу исправления той или иной
ошибки, вторая - создание и поддержание рабочего процесса, кото-
рый обеспечивал бы реализацию этих решений даже в условиях
"дедлайна".
Опытные лидеры и команды знают, что усталость непремен-
но накапливается к концу проекта, этапа или итерации. Им наверня-
ка придётся меньше спать, больше работать и в жутких количествах
поглощать кафеиносодержащие продукты. Думая загодя, умные ли-
деры вводят в действие простые правила и создают неприкосновен-
ные запасы ресурсов на ранних стадиях, и когда становится очень
трудно, у таких лидеров всегда находятся резервы для принятия пра-
вильных решений.
Это эссе, состоящее из двух частей, является своеобразным
учебником для использования таких правил и неприкосновенных
запасов. Даже больше — я поделюсь с вами идеями, на основании
которых вы сможете создавать свои собственные правила. Мои ре-
комендации состоят из четырех уровней — от отрывочных советов
по оказанию "первой помощи" (Уровень 1) до крупномасштабного
планирования (Уровень 4). Но перед тем как начать, рассмотрим
список неправильных подходов, которых следует избегать при при-
нятии решений о правке ошибок.

3
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть I

Вот десять худших способов принятия решений:


1. Править только те ошибки, которые раздражают ва-
шего директора;
2. Править все ошибки (никогда не успеете);
3. Не править ошибок вовсе (зато успеете сдать систему
уже сегодня!);
4. Править только те ошибки, которые раздражают
жену/дочь/хомяка вашего босса;
5. Требовать согласования каждого решения самым на-
доедливым и недалёким человеком в вашей организации (возможно,
пересекается с подходом №1);
6. Начать править случайно выбранную ошибку и, оста-
новившись на полпути, перейти к другой. По завершению повто-
рить;
7. Бояться ошибок. Не править их и свалить все на кого-
нибудь другого;
8. Расставить ошибки в алфавитном порядке и править
их от А до Я, исключая гласные. При соответствующем подходе к
именованию ошибок этот подход может превратиться в №3;
9. Создать сложную избирательную систему представи-
телей, выбираемых двумя третями большинства для написания
проекта регламента по формированию трёх двусторонних комите-
тов, наделённых полномочиями по модерированию будущих дис-
скуссий на тему стратегического управления ошибками;
10. Тратить все имеющееся время на споры о том, похож
ли ваш текущий подход на что либо из только что приведённого
списка.
Будем надеятся, что вы не сталкивались с описанным выше
подходами на практике. Теперь, когда вы предупреждены, будет
легче избежать неправильных решений в будущем. Как только ваш
менеджер предложит нечто подобное, то пожалуйста встаньте, тихо
повернитесь и бегите изо всех сил.

1. Первая помощь
В лучших веб-мастерских и программистских цехах процесс
управления исправления ошибок очень напоминает медицинскую
очередность оказания помощи. Кто-нибудь берёт на себя основную
роль по просмотру всех поступающих ошибок и раскладыванию их
на три или четыре кучи.
Это называется сортировкой, разбором ошибок или управле-
нием дефектами. Как и в случае с любыми другими вещами, кото-
рые у вас возможно есть, например CD-дисками, книгами, долгами,
подружками, — с ошибками лучше всего справляться, объединяя их
в высокоуровневые группы. Таким образом становится легче понять
ситуацию, обсудить ее с другими и найти подходящего квалифици-
рованного "врача".

4
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть I

Это универсальное правило — гораздо проще работать с тре-


мя-четырьмя группами, чем с сотнями или тысячами отдельных ве-
щей.
Поэтому, если ошибки ставят весь проект вверх дном, то пер-
вым делом необходимо отказаться от всех действий, сесть и начать
сортировку по степеням важности. Тест: если вы не помните, когда
в последний раз занимались разборкой ошибок, то остановитесь и
немедленно приступите к разбору.
Вы не можете просто взмахнуть волшебной палочкой над ба-
зой ошибок, чтобы они самостоятельно упорядочились. Здесь нужен
смельчак (вроде вас), который не побоится грязной работы, засучит
рукава и отсортирует их. Можете мне поверить — иначе не получит-
ся. Если вы достаточно дисциплинированы, то сможете проводить
разборку регулярно, ежедневно в течение всего хода проекта, не
позволяя ситуации ни на минуту выйти из-под контроля. Как вари-
ант - вы можете попытаться мотивировать своих программистов си-
стематически разбирать собственные ошибки. Это здорово, но какой
бы подход не применялся, дело должно быть сделано.
Прежде чем вы пропустите эти строки со словами «Я знаю о
сортировке, но не делаю её по такой-то и такой-то причине», учтите,
что сортировка необходима для оздоровления в процессе оказания
первой помощи в любом из смыслов — медицинском или техниче-
ском. Нет смысла лепить лейкопластырь на коленку больного, если
у него из спины торчит полдюжины отравленных стрел. Без сорти-
ровки вы не будете точно знать, на что потратить энергию команды
наилучшим образом. В отличие от пациента, исступленными жеста-
ми привлекающего ваше внимание к своей спине с незваными стре-
лами, ваша база кода не скажет, где у нее болит больше всего. Вам
нужно определить это самостоятельно.
Усилия по сортировке важности ошибок имеют еще и
несколько полезных последствий. Разбираясь с ошибками, лидер
вынуждает всех улучшать своё видение проекта вцелом. Он обнару-
живает множество дублирующихся ошибок, уже исправленные
ошибки, нехватку данных (которые нужно отправить на уточнение
тестерам), ошибки, которыми можно пренебречь, и даже нечто неле-
пое, вроде жалобы на то, что веб-сайт не предсказывает номера вы-
игрышных лотерейных билетов. Практика показывает, что после
первой сортировки количество ошибок обычно уменьшается на 30
процентов, что, конечно же, здорово прибавляет настроения. Однако
вам не удастся так легко добиться цели без выполнения всей рабо-
ты.

Очень простая сортировка


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

5
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть I

Согласно простейшей схеме, все ошибки можно разделить на


три группы: “необходимо исправить”, “надо бы исправить”, “не
нужно исправлять”. Перебирая ошибки, относите их к одной из этих
категорий. При этом чем больше ошибок будет отнесено в «надо бы
исправить» и «не нужно исправлять», тем более эффективной будет
сортировка, поскольку эти группы представляют собой чёткие реше-
ния.
Чего вам точно не нужно - так это чтобы 99 процентов ваших
ошибок попадало в «необходимо исправить». Такой подход к сорти-
ровке называется «трусливым».
Если все ошибки нужно исправить, то это равносильно тому,
что они равнозначны, но это бессмысленно. Иными словами, вы
просто сбежали от решения проблемы. Запомните золотое правило:
чтобы добиться успеха, расставьте ошибки в порядке их приоритета.
Если приоритет у всех ошибок один, значит, нет порядка и, следова-
тельно, нет успеха.
Сортируя ошибки, стремитесь к тому, чтобы 50 процентов по-
пало в «необходимо исправить», а все остальное в «надо бы испра-
вить» и «не нужно править». Затем остановитесь и серьезно взгляни-
те на ошибки. Оцените оставшиеся ресурсы (время и людей) и ре-
шите, что наиболее важно для проекта (заказчиков и бизнеса).
В конце проекта нет задачи важнее, чем активное и упреждаю-
щее управление ошибками и обработка проблемных ситуаций. Стре-
митесь к 50 процентам «необходимо исправить» и жмите, пока есть
силы. И только когда дальнейшее сжимание станет действительно
невозможным, знайте: вы провели настоящую сортировку.
Врачи первой помощи не выбрасывают белый флаг и не про-
сят таймаутов, когда им приходится выбирать, кого из пострадав-
ших спасать в первую очередь. Если они могут делать ТАКОЙ вы-
бор ради человеческих жизней, то уж вы точно справитесь с расста-
новкой приоритетов для ошибок. Не прячьтесь за невежеством «тру-
сливой сортировки» — будьте тверды, упрямы и ведите свою ко-
манду вперед!

Правила очень простой сортировки


Вот основные правила для трех групп ошибок:
1. Если ошибку «должны исправить», то это означает,
что она важнее любой ошибки из группы «надо бы исправить» и «не
будем править»;
2. Если ошибку «надо бы исправить», значит, она менее
важна, чем любая из группы «должны исправить», и более важна,
чем из группы «не будем править».
3. Если ошибку «не будем править», значит, она менее
важна, чем любая другая ошибка в категории «надо бы исправить».
Несомненно, все решения по поводу правки ошибок являют-
ся относительными. Здесь нет абсолютов. Определение степени важ-
ности включает много факторов, и отнести ошибку к той или иной
группе может быть очень сложно.

6
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть I

Сообразительные команды создают четко определенные мар-


керы (критерии), которые помогают выработать правильное реше-
ние (мы вернемся к этим критериям во второй части). Не пережи-
вайте, что у вас будет много спорных моментов. Я гарантирую, что
вызывать споры о правильности размещения будет минимальное ко-
личество ошибок в каждой группе.
Сфокусируйте коллектив на позитивном, на тех ошибках, с
важностью которых все согласны. Если наберется 50 таких ошибок,
то пройдет несколько рабочих дней или недель до тех пор, пока воз-
никнет необходимость вернуться к дебатам по поводу оставшихся.
Группа ошибок «должны исправить» — вот над чем нужно
работать программистам. Они ничего не должны трогать в «надо бы
исправить», за исключением тех случаев, когда они помогают с сор-
тировкой. Смысл прост: не трогай того, что может понадобиться, до
тех пор, пока это не понадобится на самом деле. Другими слова-
ми — не беспокойся о десерте, если еще не ясно, что будет на обед.
Всегда будет возникать соблазн прихватить ошибок для ис-
правления из группы «надо бы исправить». Никоим образом нельзя
отступаться от принятых ранее решений, даже если среди отодвину-
тых на второй план есть ошибки, которые раздражают всю команду,
портят любимый функционал или просто очень интересны для ис-
правления. Приоритеты должны стать самой главной вещью для
проекта и заказчика. Приверженность команды целям проекта, а не
другим желаниям — это и есть разница между качественными и низ-
копробными проектами.

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

Уровень 2. Более детальная сортировка


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

7
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть I

Качественные описания ошибок и ситуаций, при которых они


возникали, могли быть сделаны еще несколько дней или недель
раньше.
Все, что вам для этого нужно, — большая база ошибок, кото-
рая переливается всеми цветами радуги, хорошо организованный ка-
бинет, а не населенный крысами и привидениями паутинчатый чер-
дак. Программисты могут входить в него, говорить, чего им не хва-
тает, и возвращаться к работе.
База ошибок требует постоянного сопровождения и усердия
со стороны тех, кто открывает ошибки. Чем выше качество инфор-
мации в базе ошибок, тем меньше времени и сил вы потратите на
сортировку, и тем больше ваша команда проведет времени в реаль-
ной правке ошибок.
Внимание: качественное описание ошибки (или отсутствие та-
кового) могут быть первым критерием для сортировки ошибок по их
важности.
Чтобы улучшить качество информации об ошибке, прежде
всего необходимо детализировать группы, на которые эти ошибки
делятся. Помимо групп (Должны/Надо бы/Не будем), нужно ввести
еще два понятия: «Приоритет» и «Серьезность».
Смысл приоритетов прост: вместо «Должны сделать» назови-
те это «Приоритет 1», вместо «Надо бы сделать» — «Приоритет 2»,
и вместо «Не будем делать» — «Приоритет 3». Некоторые команды
идут еще дальше и добавляют приоритет 4. Таким образом, Приори-
тетом 3 становится «вероятно, не будем делать», а Приоритетом 4 —
«Не будем делать, пока не остынет преисподняя, потом снова не
запылает и опять не остынет». Я не встречал успешных команд, у
которых градация приоритетов имела бы более четырех уровней.
Создание 15 различных приоритетов — это совершенно лишняя за-
тея.
«Серьезность» определяет, насколько серьезной будет ошиб-
ка для заказчика, если она случится. Отделение серьезности от прио-
ритетности исправления дает лучшее видение ошибки, так как появ-
ляется возможность разделить последствия выполнения ошибки от
возможности ее появления.
К примеру, перед вами может быть ошибка, которая приводит
к взрыву монитора пользователя (Серьезность 1), но возникает она
только после тройного клика на пункт меню при напевании австра-
лийского национального гимна на немецком языке, что маловероят-
но (Приоритет 3).
Чтобы это заработало, кто-то должен сесть и определить раз-
ницу между уровнями серьезности 1, 2 и 3. Хорошо, если при этом
будут приводиться примеры реальных ошибок, чтобы люди могли
выбрать правильный уровень. После этого, как только будет откры-
ваться новая ошибка, степень серьезности будет заполняться надле-
жащим образом. Кому-то, вероятно, вам, придется добавить степени
серьезности и к старым ошибкам.

8
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть I

Вот пример системы определения серьезности. Рекомендую


вашей команде собраться вместе и оценить следующее:
• Уровень серьезности 1. Потеря данных. Заказчик теряет дан-
ные или серьезно повреждает их. Восстановление данных может
быть невозможно или требуется переустановка приложения (или на-
жатие кнопки «обновить» в браузере»);
• Уровень серьезности 2. Часть функционала не работает или
работает с большими проблемами. Большая часть возможностей не
работает или работает не так, как ожидалось, поэтому рабочие зада-
чи нельзя решить или приходится искать обходные пути;
• Уровень серьезности 3. Раздражение. Второстепенные функ-
ции работают не так, как ожидалось. Обходные пути существуют,
но их сложно найти или они раздражают и разочаровывают.
Используя эти два критерия (приоритет и серьезность), вы мо-
жете отсортировать оставшиеся ошибки более мудро. Вместо того,
чтобы работать просто с тремя большими группами ошибок, вы мо-
жете задать дополнительные уточняющие вопросы и отсортировать
ошибки внутри каждой группы в зависимости от того, насколько
они серьезны. Это один из быстрых способов расставить ошибки по
местам внутри группы с одинаковым приоритетом.
Третий важный критерий, который нужно учитывать при ра-
боте с ошибками, — это часть проекта, на которую ошибки воздей-
ствуют. Чем больше ваша команда, тем важнее учитывать этот кри-
терий. Необходимо понять, какая часть проекта страдает от ошибки:
возможность печати или поисковый механизм? Разбейте весь проект
на четыре-пять частей и учитывайте их в базе ошибок. Это даст вам
новый взгляд на проект — вы сможете определять, какие части
проекта наиболее «сырые» или какие части наиболее важны для вас
и ваших заказчиков. Если каждый программист отвечает за конкрет-
ную часть проекта, то такой подход позволит распределять ошибки
для правки.
Существует большое количество других критериев для сорти-
ровки ошибок. Наиболее часто встречаются такие, как качественное
описание ошибки и способов ее воспроизведения, версия ПО, в ко-
торой ошибка была найдена, по уникальному ID и по человеку, ее
обнаружившему. Каждый проект уникален и информация, которая
вам может понадобиться, — тоже уникальна.

9
PHP Inside №14

Исправление ошибок: в нужное время


и в нужном месте. Часть II
Автор: Scott Berkun
Перевод: Андрей Олищук

Вторая часть статьи об управлении ошибками

В предыдущей части, когда мы обсуждали ошибки в про-


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

Уровень 3. Критерии выхода


Как вы определяете, что завершили работу над чем либо?
Легко определить когда кончаются шоколадные печенья или лаза-
нья - их просто не остается на тарелке. Но более сложные вещи, та-
кие как программное обеспечение, не так прозрачны (и не так вкус-
ны). Вы должны заранее запланировать те критерии, по которым вы
определите, что работа завершена. Если вы не сделаете этого, то по-
тратите дополнительные часы на споры о том, все ли сделано, и на
лишний кодинг. Если у вас достаточно трезвого ума, и вы заранее
определите критерии выхода, то ваша команда израсходует время на
достижение цели, а не на споры.
Критерием выхода может являться любое утверждение об
окончании разработки, которое будет истинным до того, как разра-
ботка программы будет завершена. Простейшим примером критерия
выхода является время. Вы определяете точное время и дату завер-
шения проекта не обращая внимания на оставшиеся ошибки или не-
выполненные задачи.
Этот критерий наиболее прост для достижения, но он не имеет
никакого отношения к качеству программного продукта. Вы уложи-
тесь в сроки поставки вне зависимости от того, реализовано у вас 10
или 90 процентов функционала. Но если смотреть с точки зрения ка-
чества, то дата выпуска продукта - это всего лишь произвольная точ-
ка времени.
Другой очевидный, но плохой критерий - мнение. К примеру,
вице-президент вашей фирмы может решить, что программа готова,
когда ему покажется, что она готова.

10
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть II

Даже если у вас золотой вице-президент, это все равно плохой


фактор. Если он случайно попадет под автобус, то ваш проект по-
следует за ним. Вам нужно нечто зафиксированное на бумаге, чтобы
ускорить взаимодействие. Если все будет хранится в чьей-то голове,
то за ней выстроится длинная очередь из ожидающих.
Настоящие критерии выхода базируются прежде всего на из-
мерениях качества. Какое количество дефектов, какого типа прием-
лемо для программы? Если вы читали первую часть, то должны
быть знакомы с терминами "Приоритет" и "Серьезность" - простей-
шими способами классификации ошибок. Но что, если вы не опре-
делили, какие задачи должны иметь приоритет над другими?
Должны ли быть исправлены все ошибки с приоритетом №1,
или должны быть исправлены все ошибки с приоритетом №1, но
только для какой -то конкретной части проекта? Если этого не знае-
те вы, то не знает и ваша команда, и вы обречены на споры по пово-
ду каждой ошибки. Определение критериев выхода - это одно из
проявлений лидерства. Для этого необходима постоянная забота о
проекте, что сможет произвести эффект катализатора для всей орга-
низации.
Ключевой вопрос таков: как много ошибок, какого типа, в ка-
ких частях проекта допустимы? Вот несколько наводящих вопросов,
которые могут помочь:
• Чем вы были заняты в последнее время? Вам необходимо
выпустить несколько релизов. Их количество случайно, но это даст
вам точку отсчета. Вы можете увеличивать или понижать качество
относительно чего-либо, и это позволит вам выразить то, что вы
имеете ввиду. Если вы никогда ранее не делали этого, то поставьте
перед собой цели. Решите, какие действия вам необходимо проде-
лать, чтобы выпустить версию 2.0. Периодически вы сможете опре-
делять качество в сравнении с предыдущими версиями и направлять
работу в нужном русле.
• Какие части проекта наиболее важны? Составьте упорядо-
ченный список частей проекта/функциональных возможностей. Те
элементы, которые наиболее важны для заказчика, должны иметь
больший вес. Вы можете определить одни критерии выхода для
функций А, Б и В и другие - для Г, Д и Е. Используйте основные ре-
сурсы для наиболее используемых функций.
• Какие тесты (тест-кейсы) вы используете? Критерии выхода
не должны основываться на количестве ошибок. Если вы используе-
те тест-кейсы для каждой функции своего ПО, то вы можете устано-
вить критерий выхода в процентном соотношении пройденных те-
стов. Если вы до сих пор не использовали тестов, то пришло время
начать делать это (http://en.wikipedia.org/wiki/Test_case). Они могут
инкапсулировать много различных критериев в один, зачастую авто-
матизированный, тест. Если вы будете их делать, то тесты помогут
вам определить не только критерии выхода, но и просто помогут в
завершении конкретной работы. Каждый чек-ин (занесение части
кода в общую базу) может выполняться по прохождению теста. Это
поможет определять проблемы до того, как они превратятся в
большое бедствие.

11
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть II

• Какие параметры производительности должны быть достиг-


нуты? Определены ли параметры производительности, к достиже-
нию которых вы стремитесь? Время загрузки, сохранения, закачки?
Сфокусируйтесь на параметрах, которые очевидны для ваших заказ-
чиков, - в таком случае вы будете править ошибки, имеющие значе-
ние именно для заказчика. Проблемы с производительностью - одни
из самых неприятных для заказчика. В придачу ко всему, они имеют
тенденцию быть сложными для правки (подсказка: пытайтесь опре-
делить их на ранних стадиях). Если вы не имеете понятия о том, ка-
кие показатели производительности должны быть у вашей прграм-
мы, последуйте данному ранее совету - выстройте цепочку релизов
и делайте лучше, чем в прошлый раз.
Любой критерий выхода, который вы определите, должен
иметь достаточную спецификацию. Каждая функциональная воз-
можность или дизайн должны включать раздел с описанием, по ко-
торому разработчики смогут определить, завершена работа или нет.
Запишите, какие тесты должны быть пройдены, какие параметры
производительности должны быть достигнуты, каким критериям
юзабилити программа должна соответствовать, или какие типы
ошибок должны быть поправлены. Если вы не можете этого опреде-
лить, значит, вы недостаточно осмыслили функционал, над которым
работаете и который нужен заказчику.
Чтобы критерии выхода оставались простыми, вам может по-
надобиться две совокупности: ряд нумерованных релизов и допол-
нительных критериев, специфичных для функционала. С таким под-
ходом вам останется определить критерии выхода, большие или
меньшие показателей из предыдущих релизов. Совет для лентяев:
вся работа по определению параметров того или иного релиза может
быть возложена на одного ответственного человека.
Помните: вы всегда можете корректировать критерии выхода
в течение проекта. Им не обязательно быть одинаковыми от начала
и до конца проекта. Но помните, фиксируя их, вы даете команде
инструмент для определения качества ее работы. Хорошо, когда
критерии выхода периодически улучшаются (но не просто изменя-
ются). Это может происходить после дополнительных переговоров с
заказчиком и командой. Даже если аргументы возникают в процессе
обсуждения этих изменений, люди будут спорить о влиянии на каче-
ство с точки зрения проекта (и заказчика), что более эффективно,
чем обсуждение уровней ошибок.

Уровень 4. Раннее планирование


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

12
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть II

Вам необходимо взглянуть на более фундаментальные вещи,


которые вы можете улучшить для повышения качества вашего про-
граммного обеспечения.
Простейший вид раннего планирования - взглянуть на преды-
дущий проект (или последнюю неделю текущего проекта). Спросите
себя и других, что было сделано не так и продолжает вас преследо-
вать в текущем проекте? Это плохие привычки, неэффективные
инструменты или простое взаимонепонимание? Составьте список
сфер для улучшения и разместите их в грубом порядке по принципу
срочности или значимости.
Решения проблем могут заключаться в покупке лучших си-
стем управления ошибками, постановке конкретных задач по сорти-
ровке ошибок, обучении членов команды или улучшении способов
управления ошибками. Возможно, хорошим решением будет назна-
чение определенного человека для переговоров с заказчиком по по-
воду ошибок.
Другой вариант: вы можете составить анкету для заказчика,
которая поможет ему лучшим образом составлять отчеты об ошиб-
ках, что повысит качество описания ошибок, вводимых в систему.
Подходящий способ обеспечить все это состоит в выделении чело-
века для выполнения им задач по контролю качества. Если таковой
человек в вашей команде уже есть, обеспечьте его большими воз-
можностями
(http://www.macdevcenter.com/pub/a/mac/2005/07/08/dev_team.html).
Другой подход - сфокусироваться на раннем определении
проектов. Если у вас есть возможность, наймите проектировщика
или инженера по юзабилити, чтобы они помогли вам правильно ор-
ганизовать проект с самого начала. Они помогут вам определить
наиболее важные направления и избавят вашу команду от траты вре-
мени на разработку второстепенных функций.
Если вы вырабатываете список частей проекта для улучше-
ния, не забудьте ранжировать их. Расставьте их в порядке наи-
большей значимости для вас и проекта, а затем начните решение
первой задачи из этого списка. Не первых пяти или десяти, а именно
самой первой. До тех пор, пока вы и каждый член вашей команды не
будут способны добиться позитивных изменений, вы должны доби-
ваться этих изменений. Помогите всем объединиться вокруг улуч-
шения ситуации, позвольте каждому высказывать свои предложения
и участвовать в их реализации. И не забывайте о критериях выхода,
иначе как вы узнаете, что какой-либо процесс уже улучшен?

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

13
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть II

• Мораль: правка ненавистных или очевидных ошибок, вне


зависимости от их приоритета, может позволить программисту по-
чувствовать гордость и повысить моральный дух. Это допустимо,
если вы знаете, ради чего идете на это. Если такие вещи проделыва-
ются часто, то смените критерии для расстановки приоритетов 1, 2 и
3. Если вам приходится обходить правило, смените это правило.
• Убедитесь, что программисты не путают свое время с рабо-
чим временем. Низкоприоритетные ошибки и те ошибки, которые
интересны лично им, они могут править только в свободное время.
При этом убедитесь, что им известно, будут ли им начислены бону-
сы за работу над проектом в нерабочее время.
• Качество: исправление ошибок схоже с любой другой рабо-
той - чем больше времени вы на это тратите, тем выше качество ис-
правлений. При этом возьмите за правило, что время, затраченное на
исправление, должно соответствовать весу ошибок. Вы ведь не бу-
дете навешивать пластмассовую дверь на золотые петли без веской
причины?

Часто задаваемые вопросы


• Кто должен быть вовлечен в сортировку ошибок (Уро-
вень 1)?
Тот, кто громче всех кричит. Не важно, разработчик ли это, те-
стер или менеджер проекта. Если один человек может взять на себя
сортировку и фильтрацию базы ошибок, то не стоит поручать эту ра-
боту троим. Если у вас есть представитель заказчика, попросите его
плотно поучаствовать в этом процессе (но будьте готовы переводить
ошибки в его термины). Все специалисты, технические писатели,
юзабилити-инженеры, маркетинг должны быть приглашены, но наи-
лучший способ сэкономить время каждого из них - это договориться
с ними о телефонных консультациях, когда их помощь будет нужна.
Дайте им знать, какая из ошибок требует их внимания, и они подска-
жут вам, насколько серьезна данная ошибка.
• Что мне делать, если я не могу добиться согласия босса
на определение критерия выхода (окончания проекта)?
Изучайте гипноз. Ну, хорошо, возьмите этот критерий для ра-
боты над пилотным проектом. Обратитесь к людям, которые уже
участвовали в таких проектах и попробуйте выработать критерии
вместе с ними. Затем, когда проект, по вашему мнению, подойдет к
концу, совместно с боссом рассмотрите, насколько ваши критерии
выхода приемлемы в данной ситуации. Если они приемлемы, босс
все равно скажет нет. Если не приемлемы, то что вы еще ожидали от
босса? Повторяйте эту процедуру, пока вы не найдете что-либо, поз-
воляющее вам закончить проект.
• Приведите пример самого неудачного случая в вашей
практике управления ошибками.
Что ж, это история одного менеджера по контролю качества,
который сформулировал критерии выхода за два дня до срока окон-
чания проекта.

14
PHP Inside №14 Исправление ошибок: в нужное время и в нужном месте. Часть II

Он был счастлив своему прогрессу, но когда я взглянул на то,


что он записал, я не был удивлен, что он просто задокументировал
текущий билд. Вы можете догадаться о качестве этого релиза. Прав-
да, это была не совсем ошибка менеджера. Вице-президент оставил
критерии выхода только у себя в памяти и не сообщил о них мене-
джеру.
• Почему управление ошибками не преподается как часть ин-
форматики в университетах? Возможно, их учебные планы сами
полны ошибок? Если быть честным, то здесь достаточно и меньшего
времени, чем учеба в университете. Я полагаю, что исправление
ошибок - это узкая прикладная дисциплина, которая с академиче-
ской точки зрения больше является прикладной, нежели относится к
теории вычислительной техники. В большинстве училищ (в амери-
канской системе образования - trade scool, прим. переводчика), где
учебный процесс сфокусирован на конкретных прикладных науках,
можно встретить факультативные занятия по контролю качества и
искусству отладки.

Полезные ссылки
Тестирование программного обеспечения. Это огромная тема.
Начать можно отсюда:
http://en.wikipedia.org/wiki/Software_testing
Безболезненный учет ошибок: замечательное эссе от Джоеля о
простом учете ошибок.
http://www.joelonsoftware.com/articles/fog0000000029.html
Примеры сортировки ошибок. Здесь есть примеры различной
организации сортировки ошибок. Ссылки разнообразны:

• Bugzilla: https://bugzilla.mozilla.org/page.cgi?id=etiquette.html
• OSAF:
http://wiki.osafoundation.org/bin/view/Journal/KatieParlante20040726
• Gnome:
http://developer.gnome.org/projects/bugsquad/triage/faq.html
• Microsoft video of a triage session:
http://channel9.msdn.com/ShowPost.aspx?PostID=26948#26948
• Software quality assurance FAQ:
http://www.softwareqatest.com/qatfaq1.html

Функциональное тестирование при


помощи Selenium

15
PHP Inside №14 Функциональное тестирование при помощи Selenium

Функциональное тестирование при


помощи Selenium
Автор: Сергей Юдин

Любое качественное веб-приложение нуждается в


тестировании. Selenium — инструмент для тести-
рования веб-приложений

Любое приложение нужно тестировать. Как говорил очень


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

Что такое функциональное тестирование


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

16
PHP Inside №14 Функциональное тестирование при помощи Selenium

Что ж, вы можете гарантировать, что один и тот же сайт мож-


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

Автоматизированное функциональное тестирование


Автоматизированным функциональным тестированием назы- Автоматизированные
вают такое тестирование системы (в нашем случае это будет web- функциональные тесты
сайт), когда тестирование ведется в полностью автоматическом ре- в идеале не должны
жиме, без непосредственного участия человека, без знания деталей знать ничего про
реализации этой системы на соответствие требованиям заказчика. архитектуру системы, из
Функциональное тестирование поэтому называют также тестирова- каких компонентов она
нием “черного ящика”. состоит, на каком языке
Автоматизированные функциональные тесты в идеале не программирования
должны знать ничего про архитектуру системы, из каких компонен- реализована
тов она состоит, на каком языке программирования реализована и
т.д. Это не позволит менять архитектуру, язык приложения, базу
данных, но гарантирует, что мы сможем всегда быстро проверить,
что система все еще работоспособна.
Тестирование должно проводится так, как если бы реальный
пользователь работал с системой.
Приемочные тесты можно использовать для любых приложе-
ний (размера и качества реализации). Их написание не требует
много времени и серьезных навыков.
Приемочные тесты можно писать как до реализации тестируе-
мой системы, так и после. Функциональные тесты бывают очень по-
лезны при рефакторинге legacy-систем (ранее написанных), если
они не были до этого покрыты модульными тестами. В этом случае
мы еще до рефакторинга производим создание набора функциональ-
ных тестов, чтобы у нас имелась возможность быстро проверить це-
лостность системы и ее функциональности после внесения измене-
ний.
Функциональное тестирование для web-сайтов подразумевает
эмуляцию работы пользователя с браузером, а именно: открытие
страниц, переход по ссылкам, заполнение и отправка форм, про-
верка значений полей форм, наличие определенного текста на стра-
ницах, получение почты, отправка файлов и т.д. При этом все дей-
ствия по осуществлению манипуляции браузером должны произво-
дить сами тесты, в автоматическом режиме, без участия человека.

При помощи чего производится функциональное тестирование.


В настоящий момент в среде разработчиков ни один продукт
не используется как стандарт для функционального тестирования
web-приложений. Существует 2 типа продуктов для функционально-
го тестирования:

17
PHP Inside №14 Функциональное тестирование при помощи Selenium

• Продукты, эмулирующие поведение браузера, написанные на


языке высокого уровня, обычно на том же языке, что и приложе-
ние (но не обязательно). В качестве примеров можно привести
httpUnit, JWebUnit, WebTester из SimpleTest и другие.
• Продукты реализованные на JavaScript и реализующие проверки
непосредственно средствами браузера. В качестве примеров мож-
но привести Watir и Selenium.
Эмуляторы хорошо себя зарекомендовали и стали достаточно
популярны. В первую очередь из-за того, что они появились раньше
программ второго типа, кроме этого эмуляторы могут использовать
код, который написан для приложения, правильно и оптимально со-
здавать фикстуры (формировать среду для правильной работы те-
стов), немаловажен и такой показатель как высокая скорость испол-
нения. Но эмуляторы браузеров не могут на 100% справиться с од-
ной задачей – они не могут выполнять JavaScript код, который ис-
пользуется на страницах сайтов.
Это накладывает ограничения или на процесс тестирования,
или на процесс создания сайтов. То есть невозможно проверить ра-
боту сайта, как если бы это делал реальный пользователь, так как
эмуляция работы браузера – вовсе не эквивалентна реальной работе
браузера. Из-за этого автоматизированное тестирование приходится
дополнять тестированием ручным.
Этого недостатка лишены продукты для функционального те-
стирования второго типа. Они контролируют работу браузера, вы-
полняют команды и делают проверки при помощи JavaScript-кода,
что практически гарантирует 100% адекватность автоматизирован-
ного тестирования ручному. К сожалению, до недавнего времени у
разработчиков не было возможности пользоваться хорошим, удоб-
ным, а главное бесплатным продуктом второго типа. Но теперь есть
Selenium!

Selenium – краткое описание


Selenium как проект был начат относительно недавно (в июне
2004 года, стал открытым в декабре 2004 года) и ведется под патро-
нажем компании ThoughtWorks. Кстати в этой компании работает
Мартин Фаулер, а это о многом говорит. Selenium выпускается под
лиценцией Apache License, Version 2.0.
Selenium доступен по этому адресу:
http://selenium.thoughtworks.com/
По этому находится JIRA для Selenium (аналог Trac):
http://jira.public.thoughtworks.org/browse/SEL
Что собой представляет Selenium? Это объектно-ориентиро-
ванное JavaScript приложение, которое может анализировать файлы
определенной структуры для того, чтобы находить в них команды
для манипуляции браузером и команды для выполнения определен-
ных проверок.

18
PHP Inside №14 Функциональное тестирование при помощи Selenium

В поставке Selenium входит документация, которая может по-


мочь быстро разобраться в использовании этой системы для функ-
ционального тестирования.
Для того чтобы попробовать Selenium в работе, его необходи-
мо скачать, разархивировать, положить в папку, доступную для
web-сервера и набрать в строке браузера приблизительно такой
путь: http:/localhost/selenium/TestRunner.htm. Никаких дополнитель-
ных настроек не требуется.
Вот так должно выглядеть окно вашего браузера после запус-
ка Selenium-а:

Рабочее окно Selenium поделено на несколько областей. Снизу


находится рабочая область браузера. Здесь будут отображаться стра-
ницы тестируемого сайта. В левом верхнем углу находится список
тестовых случаев (TestCase-ов), которые могут быть выполнены для
текущего приложения. Это список тестовых случаев называется
TestSuite.
В центральной верхней части находится содержимое текущего
TestCase-а, в правой верхней части – управляющие кнопки для
запуска тестов, а также статистика выполнения тестов.
Для запуска тестов нажмите кнопку AllTests и Selenium на-
чнет проверку самого себя при помощи набора тестов, который по-
ставляется вместе с ним. Selenium может выполнять тесты в трех
скоростных режимах: run, walk и step. Run выполняет тесты с макси-
мальной скоростью.
Walk делает паузу в 0.5 сек (по-умолчанию) на каждой ко-
манде, что может быть очень удобно при визуальной проверке пра-
вильности работы сайта. Step – вы самостоятельно приказываете
Selenium-у выполнить следующую команду.

19
PHP Inside №14 Функциональное тестирование при помощи Selenium

Содержимое TestCase-а
TestCase-ы Selenium – это обычные html-страницы с одной та-
блицей, содержащей команды. Каждая строка таблицы содержит 3
колонки. Первая из них является действием или проверкой (action и
assertion/check), вторая – именем элемента (target), к которому при-
меняется команда, и третья – значением (value). Вот пример файла
TestCase-а Selenium-а, назовем его Test Login:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=win-1251" http-equiv="content-type">
<title>Test Login</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<tbody>
<tr>
<td rowspan="1" colspan="3">Test Login<br>
</td>
</tr>
<tr>
<td>open</td>
<td>/login</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>type</td>
<td>login</td>
<td>vasa</td>
</tr>
<tr>
<td>type</td>
<td>password</td>
<td>super_admin</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>login_button</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>verifyLocation</td>
<td>/</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>verifyTextPresent</td>
<td>Wellcome, Vasa Pupkin</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>
</body>
</html>

Разберем по порядку, что же делает Selenium, выполняя дан-


ный TestCase. Стоит сразу указать, что Selenium пропускает первую
строку таблицы. Здесь вы можете поместить краткое описание, что
же будет осуществляться в данном тесте.

20
PHP Inside №14 Функциональное тестирование при помощи Selenium

Мы покажем ниже, как можно вставлять комментарии в тесто-


вый код другими способами. Итак,
<tr>
<td>open</td>
<td>/login</td>
<td>&nbsp;</td>
</tr>

Выполняет действие open. Selenium дает команду браузеру


перейти на указанную страницу - /login. Колонка для значения здесь
не используется. Допустим в результате выполнения у нас откры-
лась страница, содержащая форму авторизации:
<form id='login_form' name='login_form' action='' method='post'>
Login : <input id='login' name='login' type='text' size='40'><br>
Password : <input id='password' name='password' type='password' size='40'>
<input id='login_button' type='submit' value='Login'>
</form>

Теперь Selenium при помощи действий type заполнит поля


формы определенными значениями. Вы визуально увидите, что поля
получили соответствующие значения.
<tr>
<td>type</td>
<td>login</td>
<td>vasa</td>
</tr>
<tr>
<td>type</td>
<td>password</td>
<td>super_admin</td>
</tr>

После этого действием clickAndWait Selenium нажмет кнопку


для отправки формы и будет дожидаться ответа.
<tr>
<td>clickAndWait</td>
<td>login_button</td>
<td>&nbsp;</td>
</tr>

После этого при помощи проверки verifyLocation Selenium


проверит текущее положение браузера. После аутентификации мы
должны оказаться на главной странице сайта
<tr>
<td>verifyLocation</td>
<td>/</td>
<td>&nbsp;</td>
</tr>

И, наконец, после успешной аутентификации на должны уви-


деть приглашение на главной странице.

21
PHP Inside №14 Функциональное тестирование при помощи Selenium

Selenium проверяет наличие соответствующего текста на стра-


нице при помощи проверки verityTextPresent
<tr>
<td>verifyTextPresent</td>
<td>Wellcome, Vasa Pupkin</td>
<td>&nbsp;</td>
</tr>

Чуть ниже мы разберем, какие команды и проверки есть в


Selenium, как указывать определенные элементы на странице, а так-
же как расширить набор доступных команд и проверок.

Организация тестов
Как уже было указано, TestCase-ы организованы в список, ко-
торый называется TestSuite.
TestSuite также является обыкновенной html страницей, при-
близительно такого содержания:
<html>
<head>
<meta content="text/html; charset=win-1251"
http-equiv="content-type">
<title>Test Suite</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<tbody>
<tr><td><b>Test Suite</b></td></tr>
<tr><td><a href="./TestOpen.html">TestOpen</a></td></tr>
<tr><td><a href="./TestLogin.html">TestLogin</a></td></tr>
</tbody>
</table>
</body>
</html>

Теперь объясним чуть поподробнее то, как организуются те-


сты и как они хранятся на диске. В базовой поставке все файлы
TestCase-ов и TestSuite-ов являются обычными текстовыми html
файлами и лежат в папке /selenium/tests. При запуске Selenium пыта-
ется найти файл TestSuite.html в этой папке и загружает список те-
стов из TestSuite в левое верхнее окно. При щелчке на одну из ссы-
лок файла TestSuite.html в окно текущего TestCase-а загружается со-
держимого соответствующего файла с командами.
Вот в принципе и все. Для расширения набора тестов какого-
либо приложения мы должны создать соответствующий TestCase
html файл и поместить на него ссылку в TestSuite.html. Это, правда,
самый простейший вариант использования Selenium-а, однако
большинство разработчиков он устроит.
Отметим здесь же, что хотя Selenium воспринимает TestCase-
ы только в виде html страниц, это вовсе не значит, что сами
TestCase-ы должны быть html файлами. Они могут генериться при
помощи PHP, JSP, CGI, в общем, при помощи любой технологии,
которая вам может показаться удобной.

22
PHP Inside №14 Функциональное тестирование при помощи Selenium

Однако нельзя не согласиться, что Selenium – это одна из са-


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

Инсталляция Selenium для тестирования сайтов


Существует 2 способа инсталляции Selenium:
• Отдельный web-сервер, который тестирует другие сайты.
• Инсталляция Selenium-а по тому же адресу, что и тестируемый
сайт.
Мы не будем рассматривать первый случай, так как здесь воз-
никают некоторые проблемы с так называемыми cross-site scripting
limitations, то есть отказ браузера исполнять код, если он требует
модификации содержимого страницы, пришедшей с другого хоста.
Вы можете самостоятельно поискать решение этих проблем, благо в
списке рассылки Selenium-а такие вопросы время от времени появ-
ляются.
Самым простым способ установки Selenium является распа-
ковка его в папку проекта, удаление родных тестов, подчистка
TestSuite.html – вот и все. Но данный метод связан с копированием
файлов Selenium в каждый проект, кому это может понравиться?
Ниже мы покажем, как можно легко и быстро настроить Selenium на
использование одной инсталляции несколькими проектами.
Итак, допустим, мы разархивировали selenium в папку /
var/external/selenium/.
При помощи Selenium мы желаем протестировать 2 проекта: /
var/dev/project1/ и /var/dev/project2/. Что нам нужно сделать, что
того, чтобы использовать одну инсталляцию на работы с обоими
проектами.
При запуске Selenium-а мы указывали в пути файл
TestRunner.html в папке /var/external/selenium/. Посмотрим на его со-
держимое:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<meta content="text/html; charset=ISO-8859-1" http-equiv="content-type" />

<title>Selenium Functional Test Runner</title>


<script language="JavaScript" type="text/javascript" src="html-xpath/html-xpath-
patched.js"></script>
<script language="JavaScript" type="text/javascript" src="selenium-
browserbot.js"></script>
<script language="JavaScript" type="text/javascript" src="selenium-
api.js"></script>
<script language="JavaScript" type="text/javascript" src="selenium-
commandhandlers.js"></script>
<script language="JavaScript" type="text/javascript" src="selenium-
executionloop.js"></script>
<script language="JavaScript" type="text/javascript" src="selenium-
fitrunner.js"></script>
<script language="JavaScript" type="text/javascript" src="selenium-
logging.js"></script>
<script language="JavaScript" type="text/javascript"
src="htmlutils.js"></script>
<script language="JavaScript" type="text/javascript" src="xpath.js"></script>

23
PHP Inside №14 Функциональное тестирование при помощи Selenium

<script language="JavaScript" type="text/javascript" src="user-


extensions.js"></script>
<script language="JavaScript" type="text/javascript">
function openDomViewer() {
var autFrame = document.getElementById('myiframe');
var autFrameDocument = getIframeDocument(autFrame);
var domViewer = window.open('domviewer.html');
domViewer.rootDocument = autFrameDocument;
return false;
}
</script>
<link rel="stylesheet" type="text/css" href="selenium.css" />
</head>

<body onload="start();">
<table class="layout">
<form action="" name="controlPanel">

<!-- Suite, Test, Control Panel -->

<tr class="selenium">
<td width="25%" height="30%" rowspan="2"><iframe name="testSuiteFrame"
id="testSuiteFrame" src="./tests/TestSuite.html"></iframe></td>
[… опустим остальную часть …]
</body>
</html>

Наша задача – сделать копию этого файла в наших проектах и


немного изменить его содержимое. Создадим в наших проектах
папки:
* /var/external/project1/tests/selenium/
* /var/external/project1/tests/selenium/cases
* /var/external/project2/tests/selenium/
* /var/external/project2/tests/selenium/cases
Скопируем в /var/external/project1/tests/selenium/ файл
TestRunner.html и немного поменяем его содержимое:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<meta content="text/html; charset=win-1251" http-equiv="content-type" />

<title>Selenium Functional Test Runner</title>


<script language="JavaScript" type="text/javascript" src="/seleniumSrc/html-
xpath/html-xpath-patched.js"></script>
<script language="JavaScript" type="text/javascript" src="/seleniumSrc/selenium-
browserbot.js"></script>
<script language="JavaScript" type="text/javascript" src="/seleniumSrc/selenium-
api.js"></script>
<script language="JavaScript" type="text/javascript" src="/seleniumSrc/selenium-
commandhandlers.js"></script>
<script language="JavaScript" type="text/javascript" src="/seleniumSrc/selenium-
executionloop.js"></script>
<script language="JavaScript" type="text/javascript" src="/seleniumSrc/selenium-
fitrunner.js"></script>
<script language="JavaScript" type="text/javascript" src="/seleniumSrc/selenium-
logging.js"></script>
<script language="JavaScript" type="text/javascript"
src="/seleniumSrc/htmlutils.js"></script>
<script language="JavaScript" type="text/javascript"

24
PHP Inside №14 Функциональное тестирование при помощи Selenium

src="/seleniumSrc/xpath.js"></script>
<script language="JavaScript" type="text/javascript" src="user-
extensions.js"></script>
<script language="JavaScript" type="text/javascript">
function openDomViewer() {
var autFrame = document.getElementById('myiframe');
var autFrameDocument = getIframeDocument(autFrame);
var domViewer = window.open('/seleniumSRC/domviewer.html');
domViewer.rootDocument = autFrameDocument;
return false;
}
</script>
<link rel="stylesheet" type="text/css" href="/seleniumSRC/selenium.css" />
</head>

<body onload="start();">
<table class="layout">
<form action="" name="controlPanel">

<!-- Suite, Test, Control Panel -->

<tr class="selenium">
<td width="25%" height="30%" rowspan="2">
<iframe name="testSuiteFrame" id="testSuiteFrame"
src="./cases/TestSuite.html"></iframe></td>
[… опустим остальную часть …]
</body>
</html>

Основная идея должна быть ясна – все инклюды JavaScript


осуществлять из папки /seleniumSrc/, а тесты искать в папке ./cases/.
Теперь в настройках web-сервера (Apache) нужно настроить алиас
на /seleniumSrc/, который бы указывал на путь до инсталляции
Selenium, например, так:
#project1
<VirtualHost 127.0.0.1>
DocumentRoot /var/dev/project1
ServerName project1
ErrorLog logs/ project1-error_log
CustomLog logs/ project1-access_log common
Alias /seleniumSrc/ "/var/external/selenium/"
</VirtualHost>

Теперь нужно создать файл TestSuite.html (будем считать, что


мы пока не используем никаких технологий для генерации
TestSuite-ов и TestCase-ов), а также необходимое количество
TestCase.html-файлов. Примеры смотрите выше.
Ту же самую процедуру нужно сделать и над другим проек-
том. Вот в принципе и все. Обратите внимание, что мы не меняли
путь до файла user-extenstions.js – обычно в этом файле хранятся
расширения к Selenium, характерные для текущего проекта. Мы уде-
лим внимание расширениям Selenium-а чуть позже.

Составляющие команд Selenium-а


В Selenium существует несколько основных понятий:
• действия

25
PHP Inside №14 Функциональное тестирование при помощи Selenium

• проверки
• локаторы

Действия
Действия (actions) используются для того, чтобы управлять
браузером из-под Selenium. Набор действий достаточно широк.
Ниже приведем список наиболее часто используемых действий. В
скобках будет дана форма применения в формате wiki-table. Итак
вот некоторые из действий Selenium:
• open – указывает браузеру открыть страницу по определенному
адресу (|open|location||)
• click – указывает браузеру щелкнуть по ссылке или по элементу
формы, например, для пометки checkbox-а (|click|target||)
• type – указывает браузеру ввести новое значение в элемент фор-
мы (|type|element|value|)
• select – указывает браузеру выбрать определенное значение
<option> внутри <select> тега формы(|select|element|value)
• selectWindow – указывает браузеру переключить фокус на другое
окно (|selectWindow|window_name||)
• goBack – указывает браузеру вернуться на предыдущую страницу
• close – указывает браузеру закрыть текущее окно
Полный список и примеры использования действий смотрите
в документации, поставляемой вместе с Selenium.

Проверки
Проверки (checks) используются для проверки правильности
поведения приложения. Любая проверка в Selenium представлена
двумя типами – assert и verify. Если в тесте стоит проверка
assertSomething и она не выполняется, тест прекращает свою рабо-
ты, а если verifySomething – то выдается сообщение об ошибке, но
тест продолжает работу.
Вот список наиболее часто используемых проверок:
• verifyLocation / assertLocation – проверяет текущий URL окна
браузера (|verifyLocation|url_needed||)
• verifyTitle / assertTitle – проверяет заголовок окна (|verifyTitle|
title_needed||)
• verifyValue / assertValue – проверяет, что поле формы имеет ука-
занное значение (|verifyValue|field|value_needed|)
• verifyTextPresent / assertTextPresent – проверяет, что текст стра-
ницы содержит указанный текст (|verifyTextPresent|text_needed||)
• verifyElementPresent / assertElementPresent – проверяет, что эле-
мент присутствует на текущей странице (|verifyElementPresent|
element_needed||)

26
PHP Inside №14 Функциональное тестирование при помощи Selenium

Полный список и примеры использования проверок смотрите


в документации, поставляемой вместе с Selenium.

Локаторы Selenium или как находятся элементы, к которым применяются


команды
Локаторы используются для нахождения элементов, к кото-
рым относятся команды. Список локаторов Selenium достаточно
большой:
• id – используется атрибут id (идентификатор) элемента
• name – используется атрибут name элемента
• identifier – используется атрибут id элемента, если по id-у эле-
мент не найден, то поиск будет вестись по атрибуту name
• dom – используется для поиска элемента по DOM выражению,
которое должно начинаться с document.
• xpath – используется для поиска элемента по XPath выражению,
которые должно начинаться с //
• link – используется для нахождения ссылок с указанным текстом.
Разберем, как используются локаторы в командах, на примере,
чтобы все стало понятно. Возьмем знакомую форму аутентифика-
ции.
<form id='login_form' name='login_form' action='' method='post'>
Login : <input id='login' name='login' type='text' size='40'><br>
Password : <input id='password' name='password' type='password' size='40'>
<input id='login_button' type='submit' value='Login'>
</form>

Для заполнения поля login формы login_form можно восполь-


зоваться следующими командами:
<tr>
<td>type</td>
<td>id=login</td>
<td>vasa</td>
</tr>

Использовали локатор id.


<tr>
<td>type</td>
<td>name=login</td>
<td>vasa</td>
</tr>

Использовали локатор name.


<tr>
<td>type</td>
<td>identifier=login</td>
<td>vasa</td>
</tr>

27
PHP Inside №14 Функциональное тестирование при помощи Selenium

Использовали локатор identifier.


<tr>
<td>type</td>
<td>xpath=//input[@name=’login’]</td>
<td>vasa</td>
</tr>

Использовали локатор xpath.


<tr>
<td>type</td>
<td>dom=document.forms[‘login_form’].login</td>
<td>vasa</td>
</tr>

Использовали локатор dom.


Если в команде не указано явно название типа локатора,
например не dom=document.forms[‘login_form’].login, а просто
document.forms[‘login_form’].login , тогда Selenium последовательно
пытается найти элемент при помощи следующих типов локаторов:
• identifier
• dom
• xpath
Обратите внимание, что для тегов <select> есть особые лока-
торы, впрочем, как и специальные действия и проверки. Мы не бу-
дем уделять этому внимание, так как все это достаточно хорошо
описано в документации.

Использование XPath локаторов


Использование XPath локаторов может значительно упростить
жизнь тестировщику, особенно если у него нет прав доступа менять
исходный код web-приложения, которое он тестирует. Стоит сразу
отметить, что при работе Selenium с IE даже версии 6.0 при исполь-
зовании XPath выражений, были замечены некоторые проблемы, од-
нако в FireFox никаких нареканий нет.
Вот некоторые примеры использования XPath локаторов:
• //a[contains(text(), 'partial text')] – поиск ссылки по частичному
совпадению
• //input[@type='submit'] – поиск кнопки для отправления формы по
типу
• //input[@value='Button Description'] - поиск кнопки по надписи на
ней
• //table[@id='yourTable']//tr[td='uniqueCustomerCode']/td//a/img
[@alt='Edit'] – поиск ссылки по картинки, которая находится в
определенной ячейке таблицы.

28
PHP Inside №14 Функциональное тестирование при помощи Selenium

Расширение Selenium-а
Добавление новых действий, проверок и локаторов
Так как Selenium сделан в отличном ООП стиле на JavaScript-
е, то ничего не стоит его расширить. Разработчики приложили мак-
симум усилий, чтобы это было очень легко. Даже разработчику с
минимальными знаниями о DOM и JavaScript удастся быстро
разобраться, что к чему.
Действия (Actions)
Итак, действия (actions) Selenium-а – это определенные мето-
ды класса Selenium (правильнее будет сказать прототипа). Все ме-
тоды, вида doSomething интерпретируются как действия. То есть
прототип Selenium содержит методы doOpen, doClose, doClick и т.д.
Здесь и далее привены примеры расширения Selenium, взятые из по-
ставляемой документации. Например, мы добавим действие
typeRepeated, которое дублирует текущее значение указанного эле-
мента:
Selenium.prototype.doTypeRepeated = function(locator, text) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);

// Create the text to type


var valueToType = text + text;

// Replace the element text with the new text


this.page().replaceText(element, valueToType);
};

Проверки (Asserts/Checks)
Методы прототипа Selenium вида assertSomething становятся
проверками (assert-ами). Вот пример добавления нового assert-а, ко-
торый удостоверяется, что указанный текст повторяется в значении
элемента два раза (дублируется).
Selenium.prototype.assertValueRepeated = function(locator, text) {
// All locator-strategies are automatically handled by "findElement"
var element = this.page().findElement(locator);

// Create the text to verify


var expectedValue = text + text;

// Get the actual element value


var actualValue = element.value;

// Make sure the actual value matches the expected


this.assertMatches(expectedValue, actualValue);
};

Теперь в тестах можно использовать verifyValueRepeated или


assertValueRepeated.

29
PHP Inside №14 Функциональное тестирование при помощи Selenium

Локаторы (locators)
Локаторы формируются из методов прототипа PageBot или
его потомков, например, MozillaPageBot, IEPageBot, вида
localeSomething. Ниже дан пример введения нового локатора для по-
иска элемента, у которого есть повторяющийся текст.
// The "inDocument" is a the document you are searching.
PageBot.prototype.locateElementByValueRepeated = function(text, inDocument) {
// Create the text to search for
var expectedValue = text + text;

// Loop through all elements, looking for ones that have a value === our
expected value
var allElements = inDocument.getElementsByTagName("*");
for (var i = 0; i < allElements.length; i++) {
var testElement = allElements[i];
if (testElement.value && testElement.value === expectedValue) {
return testElement;
}
}
return null;
};

user-extensions.js
Когда мы модифицировали файл TestRunner.html для того,
чтобы использовать одну инсталляцию Selenium для нескольких
проектов, мы оставили user-extentions.js подключаться непосред-
ственно из проекта. Обычно в файл user-extentions.js кладут расши-
рения Selenium, характерные для текущего проекта. Никто не меша-
ет вам сделать свои «user-extentions.js» и подключать их в
TestRunner.html по мере необходимости.

Некоторые моменты использования Selenium


Вставка комментариев в TestCase
Иногда бывает очень полезным вставить в код комментарий,
чтобы было понятно, что же на самом деле тестируется. Первая
идея, которая приходит на ум – это использование html комментари-
ев, то есть:
<!-- описание теста -->

Однако у этого подхода есть один недостаток – он не отобра-


жается в окне текущего TestCase-а.
Еще одной идеей было использование четвертой колонки те-
стовой таблицы для комментирования тестовых команд. В настоя-
щее время Selenium игнорирует все остальные колонки, кроме пер-
вых трех, поэтому вы можете использовать дополнительные колон-
ки по своему разумению.
<tr>
<td>open</td>
<td>/index.php?id=10</td>
<td>&nbsp;</td>
<td>Open Login Page</td>

30
PHP Inside №14 Функциональное тестирование при помощи Selenium

</tr>

Но это не совсем удобно, к тому же разработчики Selenium не


обещают, что четвертая колонка не будет задействована в очеред-
ном релизе.
Наконец, еще одним вариантом будет добавить в Selenium че-
рез user-extension.js команду comment:
/**
* Allow addition of comments to tests
*/
Selenium.prototype.doComment = function() {
// Don't do anything. This is to facilitate comments in tests.
}

Тогда в TestCase-е можно будет пользоваться такими


конструкциями:
<tr>
<td>comment</td>
<td>Создадим нового пользователя и попытаемся залогиниться под ним</td>
<td>&nbsp;</td>
</tr>

Этот вариант выглядит наиболее предпочтительно.

Работа с popup-окнами
По умолчанию Selenium применяет все команды к текущему
окну браузера. Если у вас в приложении есть ссылки на popup-окна,
то для того, чтобы команды применялись к новому окну – исполь-
зуйте действие selectWindow(name). После того, как вы выполнили
команду, которая закрывает popup-окно, выполните команду
selectWindow(null) для того, чтобы вернуться в главное окно.
Допустим, у вас есть такой html код:
<html>
<head>
<script type="text/javascript">
function openWindow() {
myPopupWindow = window.open('/path_to_popup_window', 'popup_window',
'height=200,width=500,top=400,left=50');
}
</script>
</head>
<body>
<button id="popup_link" onclick="openWindow();">Click here to open a popup
window</button>
</body>
</html>

Тогда ваш тест будет выглядеть приблизительно так:


<tr>
<td>click</td>
<td>popup_link</td>
<td>&nbsp; </td>
</tr>
<tr>

31
PHP Inside №14 Функциональное тестирование при помощи Selenium

<td>selectWindow</td>
<td>popup_window</td>
<td>&nbsp; </td>
</tr>
[… команды для popup-окна]
<tr>
<td>click</td>
<td>close_popup</td>
<td>&nbsp; </td>
</tr>
<tr>
<td>selectWindow</td>
<td>null</td>
<td>&nbsp; </td>
</tr>
[… команды для главного окна]

Однако при тестировании реальных приложений с popup-ок-


нами можно столкнулся с проблемой, что Selenium не ждет, пока но-
вое окно полностью загрузится, а сразу начинает выполнять ко-
манды для нового окна. Конечно, можно попробовать использовать
действие pause, например, так:
<tr>
<td>click</td>
<td>popup_link</td>
<td>&nbsp; </td>
</tr>
<tr>
<td>selectWindow</td>
<td>popup_window</td>
<td>&nbsp; </td>
</tr>
<tr>
<td>pause</td>
<td>2000</td>
<td>&nbsp; </td>
</tr>
[… команды для popup-окна]

Но это сильно замедляет общее выполнение тестов, если таких


окон много. В списке рассылки было предложено следующее реше-
ние данной проблемы. Суть ее сводится к введению новой команды
waitForElementPresent. Сразу оговорюсь, что это решение не работа-
ет с версией 0.5.0, только с SVN версией Selenium (см. ссылку на ре-
позиторий ниже).
Итак, вот код для нового действия:
Selenium.prototype.doWaitForElementPresent = function (locator) {
var self = this;
testLoop.waitForCondition = function () {
try {
var pagebot = self.page();
pagebot.findElement(locator);
return true;
} catch (e) {
return false;
}
};
};

32
PHP Inside №14 Функциональное тестирование при помощи Selenium

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


<tr>
<td>click</td>
<td>popup_link</td>
<td>&nbsp; </td>
</tr>
<tr>
<td>selectWindow</td>
<td>popup_window</td>
<td>&nbsp; </td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>create_form</td>
<td>&nbsp; </td>
</tr>
[… команды для popup-окна]

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

Иерархия тестов и сборка тестов из компонентов


Как уже было сказано выше, для написания тестов для
Selenium можно использовать любые средства, которые в конечном
итоге дают простую html таблицу команд. Вы можете писать тесты в
XML, а затем при помощи XSLT приводить их в понятный для
Selenium-а формат.
Немного поразмыслив, мы, например, решили попробовать
использовать шаблонизатор WACT с простейшим php-скриптом, ко-
торый выбирает указанный в запросе шаблон, компилирует, испол-
няет его и выдает Selenium-у. Также планируется написать несколь-
ко тегов для того, чтобы описывать таблицу команд при помощи
простой wiki-разметки и иметь возможность вставить комментарии
в тесты, так, чтобы шаблонизатор автоматически преобразовывал их
в comment действия.
Если говорить об более высокой степени вложенности тестов,
то здесь нужно сказать, что TestRunner.html может принимать атри-
бут test для безусловной загрузки TestSuite, например:
http://yoursite/selenium/TestRunner.html?test=suites/AllTheTests.
html
Таким образом, вы сможете создавать у себя группы тестов и
при помощи какой-нибудь технологии, PHP например, формировать
любой набор тестов, который вам необходим и выдать Selenium-у
тот TestSuite, какой вам нужен в данный момент.
Вам только нужно будет сделать index.html страницу, где бы
располагались ссылки на различные наборы тестов. Вот в принципе
и все.

33
PHP Inside №14 Функциональное тестирование при помощи Selenium

setUp/tearDown
В Selenium нет отдельных setUp и tearDown команд для созда-
ния и удаления фикстур. Первое решение, которое приходит в голо-
ву, это использование обычных команд open для посещения опреде-
ленных страниц. Для фикстур можно создать отдельную папку в
проекте и положить туда 2 php скрипта. Тест будет выглядеть при-
близительно так:
Некоторый тест
Open ./fixtures/Project1SetUp.php
[Другие команды]
Open ./fixtures/Project1TearDown.php

Внутри Project1SetUp.php мы заполняем базу данных данны-


ми, которые могут потребоваться при тестировании, а в
Project2TearDown.php – очищаем ее. Единственная проблема, кото-
рая есть при таком подходе – это отсутствие какой-либо гарантии,
что tearDown все-таки будет выполнен, поэтому в Project1SetUp.php
приходится предварительно очищать базу данных, чтобы не возни-
кало ситуации дублирования данных.

Рекомендации по формированию html-кода


страниц тестируемого web-приложения для
облегчения тестирования
Функциональные тесты могут быть довольно хрупкими. Все
зависит от того, как вы пишите ваши тесты. Сравните:
<tr>
<td>click</td>
<td>login</td>
<td>&nbsp;</td>
</tr>
с
<tr>
<td>click</td>
<td>document.links[4]</td>
<td>&nbsp; </td>
</tr>

Первый пример намного более понятный, гораздо стабильнее,


так как не зависит от дизайна (до определенной степени).
Основная проблема web-приложений заключается в том, что
их делают очень сложными для автоматизированного тестировани.
То есть эти приложения делаются для того, чтобы с ними хорошо
работал человека, а не машина, например, в лице Selenium.
После введения в рабочий процесс функционального тестиро-
вания, вы будете уделять этому гораздо большее внимание.
Большинство значимых тегов получат уникальные идентификаторы,
гораздо больше станет таких атрибутов, как id, title, alt и т.д.

34
PHP Inside №14 Функциональное тестирование при помощи Selenium

Это в значительной степени облегчает и написание тестов, и


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

Что полезно знать о Selenium


Автоматическое выполнение тестов и сохранение результатов
тестирования.
TestRunner.html понимает такой атрибут, как auto=true. Если
этот параметр указан, то есть строка запроса выглядит следующим
образом http://project1/tests/selenium/TestRunner.html?auto=true, то
Selenium сразу же начнет выполнение всех тестов в режиме run.
После выполнения тестов он попытается сделать запрос по
пути /postResults, куда он передаст результаты выполнения тестов.
Значение /postResults можно изменить, передав в запрос параметр
resultsUrl. Вы можете написать свое приложение на php, которое бу-
дет сохранять и интерпретировать результаты выполнения тестов.
Все это позволяет использовать Selenium как часть автоматизиро-
ванного build-а приложения.

Driven-mode или Selenium может управляться из стороннего приложения


Driven Selenium – это режим выполнения тестов, когда брау-
зер находится под контролем другого приложения. Это может быть,
например, xUnit тест, который посылает браузеру определенные ко-
манды и получает на них ответы.
В настоящее время такое возможно только для языков Java, .
Net, Ruby и Python. К сожалению PHP пока в этом списке нет, хотя
разработчики утверждают, что driven-mode может использоваться с
любым языком, который умеет работать с http. Возможно, что в ско-
ром времени кто-то и возьмется за написание модуля для работы с
Selenium из php.
При использовании Driven-mode мы можем успешно решить
все проблемы с фикстурами, однако это затрудняет написание те-
стов людьми, не знакомыми с языками программирования.
Вот пример использования Java Driven Selenium:
public void testOKClick() {
selenium.verifyTitle("First Page");
selenium.open("/TestPage.html");
selenium.click("OKButton");
selenium.verifyTitle("Another Page");
}

Так как журнал у нас называется PHP-inside, на этом разгово-


ры о Driven-mode пока прекратим.

35
PHP Inside №14 Функциональное тестирование при помощи Selenium

Полезные ссылки, касающиеся Selenium и


функциональных тестов.
http://selenium.thoughtworks.com/ - основной сайт Selenium.
http://jira.public.thoughtworks.org/browse/SEL - здесь находятся
все feature requests и tickets.
svn://selenium.codehaus.org/selenium/scm/trunk/code/javascript –
javascript репозиторий Selenium. Вы можете выкачать весь репозито-
рий этого проекта, но это около 30 мегабайт.
http://www.oslutions.com/docs/selenium/user-extensions.js -
здесь можно найти довольно таки интересный extension к Selenium,
который упрощает процесс написания тестов людьми, далекими от
программирования.
http://www.oslutions.com/blog/archives/2005/06/selenium_extent.
html - здесь находится описание вышеуказанного расширения.
http://www.geocities.com/softwarepr/projects/seleniumassist.html
- небольшой проект, который позволяет частично автоматизировать
процесс написания тестов для больших форм.
http://www.sqa-test.com/w_paper1.html - полезная статья на-
счет функционального тестирования.
http://selenium.opiumpost.org/wiki/published/ - здесь test-case-ы,
которые генерятся из instiki-wiki.
http://www.xprogramming.ru/XPRules/AcceptanceTests.html -
немного информации насчет тестирования и функционального те-
стирования на русском языке.
http://wtr.rubyforge.org/ - альтернативный проект, который ра-
ботает только под IE. Написан на Ruby.

Интервью с Ильей Альшанетским

36
PHP Inside №14 Интервью с Ильей Альшанетским

Люди
Интервью с Ильей Альшанетским
Интервьюировал: Андрей Олищук

Один из активистов разработки PHP, автор


FUDforum и eGroupware дал эксклюзивное ин-
тервью нашему журналу

Илья Альшанетский (Ilia Alshanetsky) известен нам больше


всего как один из ведущих разработчиков PHP, автор набирающе-
го популярность движка для форумов FUDforum
(http://fudforum.org), участник многих открытых проектов. Илья
любезно согласился дать интервью специально для нашего журна-
ла.
nw: Как вы пришли в PHP? Это произошло по чистой слу-
чайности или было вашим обоснованным выбором?
Ilia: Я начал использовать PHP в начале 1998 года, когда при-
шло время для перехода в веб. Тем не менее, мой опыт веб-програм-
мирования начался отнюдь не с PHP. Вначале я был ярым поклонни-
ком Perl и терроризировал провайдера своими скриптами, которые
нагружали процессор и работали с натугой. В попытке справиться
со мной администратор посоветовал обратить внимание на PHP, ко-
торый был предположительно более быстрым и лучше структуриро-
ванным. Администратор знал о моих жалобах на то, что Perl ужасно
беспорядочен, и поэтому надеялся, что его доводы меня убедят. Та-
ким образом я и попробовал эту вещь, называемую PHP, и с тех пор
не возвращался к старому. Если мне не изменяет память, тогда это
была версия 3.0.3.
Примерно в 1999 – 2000 годах я начал ковыряться уже в ис-
ходниках – писал патчи, собственные функции, и так продолжалось
до тех пор, пока я не написал первое расширение под названием
shmop. После этого я в основном участвовал в коммерческих PHP-
разработках, которые заключались в написании новых расширений,
функций и “перебирании” PHP в целях увеличения его производи-
тельности. В 2003 году я снова вернулся к активной работе над са-
мим языком и с тех пор являюсь одним из нескольких активных раз-
работчиков.
nw: Вы были релиз-менеджером в PHP 4.3.3+. Каковы
функции релиз-менеджера в PHP? Расскажите также о процессе
разработки и функциях команды контроля качества. В целом
разработчики PHP - это хаотичное сообщество или хорошо орга-
низованная команда?

37
PHP Inside №14 Интервью с Ильей Альшанетским

Ilia: В задачу релиз-менеджера PHP входит, как это понятно


уже из названия, выпуск новых версий PHP в свет. Звучит довольно
просто, но на самом деле это очень комплексный процесс, самое
сложное в котором – это социальная составляющая, а вовсе не тех-
ническая. Линус очень метко сказал по этому поводу, что управлять
командой разработчиков, а тем более, волонтеров, работающих
над проектом Open Source, это словно пасти стаю котов.
В PHP нет специальной политики по выпуску новых релизов,
поэтому каждый релиз-менеджер имеет свой подход и собственное Управлять командой
видение, в зависимости от ситуации. Именно поэтому я могу опи- разработчиков, а тем
сать процесс только со своей точки зрения. более, волонтеров,
работающих над
С PHP 4.3 я работал по простой схеме. Решения, стоящие за проектом Open Source,
«давайте выпустим новый релиз», принимались на основании коли- это словно пасти стаю
чества и природы исправленных ошибок. Если количество исправле- котов
ний приближалось к тридцати, я начинал пинать разработчиков, что-
бы они посмотрели, нет ли каких-либо критических или важных ис-
правлений, которые еще ждут своего часа. Если таковых не оказыва-
лось, то обычно в течение недели появлялся RC1 (релиз кандидат 1)
и далее выходило еще несколько последующих RC, пока не достига-
лась определенная стабильность версии.
О выходе каждого релиз-кандидата сообщалось в лист рассыл-
ки с той целью, чтобы люди могли потестировать новый релиз и
дать возможность поправить ошибки по горячим следам. Самый по-
следний релиз-кандидат выходил в режиме «CVS lock», который
означал «НИЧЕГО НЕ ДОБАВЛЯТЬ». Таким образом новая функ-
циональность приносилась в жертву стабильности релиза. Позволя-
лось добавлять только патчи для повышения уровня безопасности
или улучшения обратной совместимости. Если такие патчи вноси-
лись, то выпускался очередной релиз-кандидат.
Ветка 4.3 была закрыта для добавления новых возможностей –
принимались только изменения, состоящие из исправлений ошибок
или улучшений в оптимизации. Все остальное отклонялось.
Обычно новые возможности обсуждаются во внутреннем ли-
сте рассылки, но иногда просто добавляются в CVS и, если никто не PHP - это, скорее,
будет протестовать, остаются там. Очевидный факт – в последнее контролируемый хаос,
время лист рассылки становится все менее продуктивным. который до сих пор
Большинство предпочитает обсуждать новые возможности между показывал себя
собой по электронной почте, интернет-пейджеру или даже IRC. И довольно
если там они приходят к согласию, то новые возможности добав- работоспособным
ляются в CVS.
Функции QA Team (команды контроля качества), которая на-
считывает примерно пять активных участников, очень многочислен-
ны. Сюда входит и тестирование релизов, правка ошибок, удаление
ложных отчетов из системы управления ошибками (система органи-
зована в основном стараниями Jani Taskinen), раздача пинков разра-
ботчикам, которые что-либо поломали. В придачу к этому мы также
поддерживаем работу тестового комплекта для PHP.
Будучи одним из управляющих проектом, могу сказать, что
PHP - это, скорее, контролируемый хаос, который до сих пор пока-
зывал себя довольно работоспособным.

38
PHP Inside №14 Интервью с Ильей Альшанетским

nw: Популярность вашего FUDforum в настоящее время


растет. Но в самом начале почему вы взялись за создание ново-
го движка для форумов, ведь тогда уже существовали многие
популярные скрипты?
Ilia: Причины для разработки своего собственного форума
были довольно просты: я работал над проектом, которому был ну- Причины для разработки
жен движок форума, способный выдерживать ОЧЕНЬ большой своего собственного
траффик. Я не планировал браться за самостоятельную разработку и форума были довольно
хотел подобрать готовое решение. Однако после продолжительных просты: я работал над
тестов я определил, что подходящих мне форумов не существует, проектом, которому был
так как одни были ни к черту медленными, вторые имели очень нужен движок форума,
ограниченный функционал, в исходниках третьих можно было сло- способный выдерживать
мать ногу, а четвертые представляли собой комбинацию всего выше ОЧЕНЬ большой
перечисленного. Тогда я и решил написать что-то свое, что соответ- траффик
ствовало бы моим требованиям, а по завершению работ было реше-
но выложить проект в публичный доступ.
nw: В чем заключается изюминка FUDforum? Почему
люди выбирают именно его?
Ilia: FUDforum расшифровывается как Fast (быстрый)
Uncompromising (устойчивый) Discussion Forum (форум для дискус-
сий). Собственно, это он собой и представляет. Я думаю, что при-
влекательность FUDforum для многих людей кроется в его широком
функционале, который включает такие возможности, как встроенная
проверка орфографии, интеграция с NNTP/листами рассылки, под-
держка RSS/XML, возможность генерировать из топиков форума
PDF-документы без установки дополнительных расширений, гибкая
система контроля доступа, поддержка различных СУБД, i18n (ин-
тернационализация) и многое другое.
Несмотря на обилие функций, FUDforum остается крайне бы-
стрым – гораздо быстрее других форумов, о которых я знаю. Он вы- FUDforum очень
держивает 100+ запросов в секунду на большинстве страниц. И в безопасен – за три года
конце концов, FUDforum очень безопасен – за три года его суще- его существования было
ствования было найдено только 4 ошибки в системе безопасности, и найдено только 4
только одна из них, обнаруженная в 2002 году, была критической. ошибки в системе
безопасности
nw: Как много времени вы тратите на проекты с откры-
тым кодом? Если представить диаграмму pie-chart (пирог) ва-
шего рабочего времени, то какую часть будут занимать PHP,
FUDforum, eGroupware и другие Open Source проекты?
Ilia: Что ж, я трачу ОЧЕНЬ много своего времени на
FUDforum, так как большая часть моей оплачиваемой работы связа-
на с оказанием техподдержки и услуг по его настройке и кастомиза-
ции.
Если представить мой усредненный день, то около шести ча-
сов я трачу на работу, связанную с FUDforum'ом, в то время как на
PHP у меня уходит примерно час такого дня. Над eGroupware я сей-
час практически не работаю, поэтому здесь поставим ноль. Другие
открытые проекты занимают примерно 20-30 минут, в зависимости
от ситуации. В основном мое участие в проектах Open Source заклю-
чается в работе с багами и выпуске патчей.

39
PHP Inside №14 Интервью с Ильей Альшанетским

nw: Как такой успешный IT-специалист, как вы, проводит


свободное время?
Ilia: Свободное время? Что это? В несколько часов, которые
удается ухватить, я стараюсь куда-нибудь выбраться из дома – схо-
дить в кино с друзьями, немного пофотографировать и позаниматься
в спортзале.
nw: Я не могу не спросить: у вас такие русские имя и фа-
милия, ваши корни идут из России, или это какая-то слу-
чайность?
Ilia: Я родился в стране, которая раньше называлась СССР, а
точнее, теперь в независимом государстве – на Украине. Я немного
говорю по-русски, но большую часть жизни я прожил вне
Украины/СССР.

Ссылки
Сайт движка форумов: FUDforum: http://fudforum.org
Домашняя страничка Ильи: http://ilia.ws

FAQ: Как принять участие в развитии


PHP?

40
PHP Inside №14 FAQ: Как принять участие в развитии PHP?

FAQ: Как принять участие в развитии


PHP?
Авторы:
Антон Довгаль [tony2001]
Алексей Борзов [SadSpirit]
Александр Войцеховский [young]
Андрей Олищук [nw]

Если вы хотите помочь PHP, у вас могут возник-


нуть вопросы. Пришло время на них ответить

PHP, как и практически любой другой Open Source проект,


не развивается сам по себе. Ядро языка, его модули, классы PEAR,
документация - ничего не появляется из ниоткуда по мановению
волшебной палочки. Все это - труд множества людей, труд, ре-
зультатами которого мы с вами можем пользоваться бесплатно
во свое благо.
Многие из нас с вами могут помочь в развитии языка. В этой
статье, построенной в виде списка часто задаваемых вопросов
(FAQ), мы расскажем, как лично ВЫ можете принять участие. PHP
ждет вас!

1. Тестирование ядра и модулей (расширений)


1.1. Я хочу принять участие в тестировании. Где взять
нужный релиз для тестирования?
http://snaps.php.net, либо из CVS ( http://php.net/anoncvs.php).
1.2. Я нашел баги, куда мне сообщать?
http://bugs.php.net.
1.3. Что я должен указать в баг-репорте (отчете о найденой
ошибке)?
http://bugs.php.net/how-to-report.php - тут все указано. Язык
сообщений - только английский.

41
PHP Inside №14 FAQ: Как принять участие в развитии PHP?

2. Разработка ядра и модулей (расширений)


2.1. У меня есть опыт программирования на C. Я хочу по-
мочь в развитии PHP, но не знаю, с чего начать (что именно пи-
сать). Куда мне обратиться за советом?
Можно взять любой открытый баг-репорт и начать его иссле-
довать. Заодно можно немного узнать внутренности PHP и понять
причины бага. Возможно, даже сделать патч или хотя бы найти до-
полнительную информацию (уточнение по ошибке) и добавить в ре-
порт. Это самый лучший путь для начала.
2.2. У меня есть патч или новое расширение. Куда я могу
его отправить?
Смотря какой патч и какое расширение. Патч для core (ядра) -
в лист рассылки php-internals, патч для модулей PECL - в pecl-dev. Можно взять любой
Важное замечание: не чините то, что не поломано! Новые модули - открытый баг-репорт и
тоже в pecl-dev, но тут важный вопрос - лицензирование. PECL не начать его исследовать -
может распространять GPL-licensed модули или модули, которые это самый лучший путь
линкуются с GPL-либами (что, на самом деле, одно и то же). Мо- для начала
дуль желательно подробно описать и приложить к письму URL на
его исходники. Подписаться на листы рассылки можно здесь:
http://www.php.net/mailing-lists.php
2.3. Нужно ли мне предварительно обсуждать мои патчи
или расширения в сообществе и доводить их до стабильного со-
стояния?
Стадию обсуждения обойти никак нельзя. Что касается ста-
бильности, то она не обязательна, но, конечно же, для начала нужен
какой-то работающий код.
2.4. Есть ли какие-то требования к новому коду?
Да, есть стандарты кодирования, которые можно найти здесь:
http://cvs.php.net/php-src/CODING_STANDARDS
2.5. Кроссплатформенность нового кода обязательна?
Если есть возможность, то код желательно сделать таковым.
С другой стороны, существуют такие модули, которые работают
только на одной ОС (к примеру - COM).

3. Пакеты PEAR
3.1. У меня есть полезный класс / пакет классов, и я хотел
бы включить его в PEAR. Каким требованиям он должен соот-
ветствовать? Какова процедура предложения нового пакета?
Для того, чтобы пакет был принят, он должен набрать сумму
не менее 5 голосов при голосовании в системе PEPr (PEAR Proposal
System, http://pear.php.net/pepr/pepr-overview.php).
Прежде чем дело дойдёт до голосования, впрочем, пакет
придётся подготовить.
• Выберите ему название: не стоит слишком долго раздумы-
вать, если вы выберете неудачное название, вам на это укажут в про-
цессе обсуждения более матёрые разработчики.

42
PHP Inside №14 FAQ: Как принять участие в развитии PHP?

• Выберите лицензию: список возможных лицензий опублико-


ван на сайте PEAR: http://pear.php.net/group/docs/20040402-la.php.
Отмечу: все эти лицензии позволяют использовать пакеты в коммер-
ческих приложениях.
• Учтите следующую очевидную вещь: мало кто будет с лупой
в руках изучать, как работает ваш код. Но для того, чтобы увидеть,
что код неряшлив и плохо документирован, лупа не нужна.
• Приведите код в соответствие со стандартами кодирования
PEAR: http://pear.php.net/manual/en/standards.php. Не стоит начинать Мало кто будет с лупой в
свою карьеру разработчика PEAR с флейма о том, что стандарты ко- руках изучать, как
дирования PEAR - отстой. Менять их всё равно не будут, а осадок работает ваш код, но
останется. для того, чтобы увидеть,
что код неряшлив и
• Ваш код должен содержать комментарии в формате плохо документирован,
phpDocumentor. Краткое описание их синтаксиса есть в документа- лупа не нужна
ции PEAR: http://pear.php.net/manual/en/standards.header.php, значи-
тельно более подробное - в документации самого phpDocumentor:
http://manual.phpdoc.org/
• Подготовьте примеры использования вашего пакета. Запу-
стить пару скриптов-примеров гораздо проще, чем разбираться с
тем, как пакет теоретически должен работать.
• Опубликуйте свой пакет в интернете. Желательно в двух эк-
земплярах: в архиве, который можно установить инсталлятором
pear, и в виде файлов .phps
Всё, теперь можно вносить в PEPr новое предложение. Преж-
де всего, если у вас нет учётной записи на сайте PEAR, вам потребу-
ется зарегистрироваться на http://pear.php.net/account-request.php и
дождаться одобрения регистрации.
Сам процесс внесения предложения хорошо описан в доку-
ментации http://pear.php.net/manual/en/newmaint.proposal.php, добав- Задача, которую вы
лю лишь следующее: если вы решите облегчить себе задачу и не вы- решаете, должна
полнить какой-либо из вышеописанных пунктов, то в процессе обсу- возникать в различных
ждения вашего предложения вам обязательно на это укажут и на- приложениях, а не
стойчиво попросят исправиться. только в одном
конкретном вашем!
Теперь, наконец, перейдём к вопросу о функциональности и о
том, какие пакеты будут иметь проблемы с прохождением голосова-
ния. В первую очередь: пакеты PEAR должны предоставлять доста-
точно абстрактное решение достаточно общей задачи.
Другими словами, задача, которую вы решаете, должна возни-
кать в различных приложениях (а не только в одном конкретном ва-
шем!), а решение её должно быть легко интегрируемо в чужие при-
ложения (если ваш пакет выводит HTML, он не должен выводить
его только красными буквами и только на зелёном фоне).
Если ваш пакет не удовлетворяет этим условиям, у него прак-
тически нет шансов быть принятым в PEAR.
Вторая проблема заключается в том, что разработчики PEAR
(которые и будут голосовать) традиционно не любят дублирование
функциональности.

43
PHP Inside №14 FAQ: Как принять участие в развитии PHP?

Поэтому, если ваш пакет дублирует функциональность уже


существующего, будьте готовы к вопросам:
• Чем ваш пакет лучше существующего?
• Что мешает встроить дополнительную функциональность в
существующий пакет? Если у вас нет удовлетворительных ответов
на эти вопросы, то у вашего пакета, опять же, не слишком много
шансов пройти голосование. Подумайте лучше о том, как скоопери-
роваться с разработчиком / разработчиками имеющегося пакета.
Напоследок, как водится, проповедь. Включение вашего паке-
та в PEAR - лишь первый шаг. Дальше потребуется его поддержка и
развитие, поэтому оцените, будет ли у вас на это время? Если нет, то
лучше не предлагать пакет вообще.
3.2. Я использовал пакет PEAR в своём проекте и накопил
неплохой опыт. Как я могу помочь с расширением и улучшени-
ем документации этого пакета?
В первую очередь как разработчик должен сказать, что напи-
сание документации является одним из последних по увлекательно- Написание
сти занятием, поэтому любая помощь в этом вопросе будет принята документации является
с благодарностью. одним из последних по
увлекательности
Перечислю варианты этой помощи в порядке увеличения тру- занятием, поэтому
доёмкости. любая помощь в этом
• Написание примеров использования; вопросе будет принята с
благодарностью
• Написание статей о пакете;
• Обновление официальной документации.
Под примерами использования здесь понимаются файлы .php,
устанавливаемые вместе с пакетом. Естественно, предполагается,
что вы не вырвете с мясом кусок из своего приложения (который бу-
дет невозможно понять вне контекста этого приложения), а приду-
маете некий характерный пример, представляющий одну из граней
функциональности пакета.
Что касается статей, здесь нельзя сказать ничего определённо-
го, всё зависит от вас: вы можете опубликовать их где угодно и в ка-
ком угодно формате.
С официальной документацией несколько сложнее; докумен-
тация PEAR (как и документация языка PHP) пишется в формате
DocBook XML ( http://www.docbook.org), и для того, чтобы сгенери-
ровать HTML-версию документации и соответственно проверить
правильность своих изменений, вам придётся установить набор ути-
лит для работы с DocBook. Это тривиально под *nix, под Windows
вам придётся использовать Cygwin, http://cygwin.com/.
Если вас всё же не пугает изучение нового формата и работа с
ним, то рекомендую в первую очередь ознакомиться с PHP
Documentation Howto, http://www.php.net/manual/howto/

44
PHP Inside №14 FAQ: Как принять участие в развитии PHP?

Вне зависимости от того, какую документацию вы подготови-


ли, для связи с разработчиками пакетов лучше всего использовать
баг-трекер PEAR http://pear.php.net/bugs/, который позволяет кроме
сообщений об ошибках заводить и предложения по улучшению па-
кетов. Заведите новое предложение, указав в нём ссылку на подго-
товленную вами документацию (баг-трекер PEAR не позволяет
присоединять файлы, а вставленный в сообщение код не слишком
легко читать).
Для обсуждения вопросов, связанных с документацией PEAR,
существует специальный список рассылки pear-doc@lists.php.net.
Если вы хотите посвятить документированию более-менее продол-
жительное время, в первую очередь подпишитесь на него. Там вам
ответят на все возникающие вопросы.

4. Написание и перевод документации и статей


4.1. Я имею опыт в определенной области PHP-разработки
и готов им поделиться, написав/переведя статью. Что необходи-
мо сделать, чтобы мою статью прочитало много заинтересован-
ных людей?
Если ваша статья действительно представляет интерес, то она
не пропадет. На начальной стадии, вы можете обсудить ее на каком-
либо популярном PHP-форуме (к примеру, на http://phpclub.ru или
http://php.com.ua). Затем предложите опубликовать статью в проекте
«Детали» PHPClub (для этого напишите письмо с URL статьи на
young@phpclub.net или постучитесь в ICQ: 86887211). Также вы мо-
жете обратиться в редакцию нашего журнала «PHP Inside» и предло-
жить свою статью для публикации в одном из ближайших номеров.
Для этого напишите письмо с URL статьи (или приложите ар-
хив со статьей к письму) на адрес: nw@phpinside.ru или постучитесь Все обсуждения ведутся
в ICQ: 223-658-175. Конечно, в сети существует и множество других исключительно на
проектов, которым вы можете предложить написанный вами текст, английском языке, не
но указанных двух будет достаточно для того, что бы о вашем мате- смотря на присутствие
риале узнало подавляющее большинство отечественных PHP-разра- среди подписчиков
ботчиков. большого количества
русскоязычных
4.2. Я хочу помочь с переводом официальной документа- разработчиков
ции PHP (мануала) на русский язык. К кому мне обратиться и
как происходит процесс работы?
Обратиться можно в список рассылки: doc-ru@lists.php.net,
при этом необходимо учитывать, что все обсуждения ведутся ис-
ключительно на английском языке, не смотря на присутствие среди
подписчиков большого количества русскоязычных разработчиков.
Чтобы узнать, как происходит процесс работы по подготовке
официальной документации, ознакомтесь с информацией отсюда:
http://doc.php.net/php/dochowto/ — там вы найдете ответы на
большинство вопросов.
4.3. Я могу переводить тематические статьи. Как мне вы-
брать статью для перевода?

45
PHP Inside №14 FAQ: Как принять участие в развитии PHP?

Это не сложно. Для выбора статьи можно придерживаться


нескольких правил:
• Перевести статью, которую вы читаете для своих нужд. Если
вам интересна та или иная статья на английском языке и вы читаете
ее для решения своих задач, запишите перевод и поделитесь им с со-
обществом.
В этом случае рекомендуем предварительно списаться с адми-
нистратором проекта, на котором вы планируете разместить пере-
вод. В любом случае не забывайте про авторские права, которые
распространяются не только на перепечатку оригинала, но и на его
перевод.
• Многие проекты имеют список материалов, которые они хо-
тели бы опубликовать в переведенном виде.
К примеру, у нашего журнала существует соответствующий
раздел форума: http://phpinside.ru/?q=node/113. Здесь вы сможете не
только обсудить перевод, но и предложить ссылку на интересную
статью для перевода другими.
• Напишите администратору проекта, на котором вы хотите
опубликовать будущий перевод. Он вам наверняка предложит
несколько интересных вариантов.

5. Разработка PHP-приложений с открытым


кодом
5.1. Как мой открытый проект, написанный на PHP, мо-
жет помочь в развитии самого языка?
Помните, что разрабатывая и продвигая свои открытые
проекты, вы положительно влияете и на продвижение самого PHP. Помните, что
Операционная система Windows сейчас пользуется огромной попу- разрабатывая и
лярностью не потому, что она гениально написана, а потому что продвигая свои
именно к этой ОС существует множество разнообразных приложе- открытые проекты, вы
ний. положительно влияете и
на продвижение самого
То же самое и с PHP. Подробнее о том, как организовать и PHP
продвигать свой Open Source проект, можно прочитать в одной из
статей PHP Inside #13 (http://phpclub.ru/detail/magazine/2005/08/)
5.2. У меня нет собственного открытого проекта, но я го-
тов помочь в разработке/тестировании/локализации/документи-
ровании других проектов. Как мне присоединиться к одному из
них?
Здесь существует несколько путей.
• Попробуйте начать с тех проектов, которые вы уже исполь-
зуете. К примеру, если вы активно используете какую либо CMS, то
наверняка находили в ней ошибки или недоработки, которые можно
поправить. Или вам уже приходилось писать к ней модули и расши-
рения? Поделитесь ими с авторами проекта.

46
PHP Inside №14 FAQ: Как принять участие в развитии PHP?

В любом случае желательно не просто писать разработчикам


этого проекта: «Чем я могу помочь?», - а сразу прислать патч/мо-
дуль/языковой файл или прочее. В исходниках большинства откры-
тых проектов уже имеются файлы ToDo или Roadmap, в которых
указаны направления желаемого развития проекта.
• Посетите раздел «Project Help Wanted» на сайте
http://sourceforge.net/people/. Там можно подобрать посильную для
вас задачу. Участие в таких проектах позволяет накопить некоторый
опыт и, возможно, известность в определенных кругах.

Библиотека dbtree для работы с


деревьями Nested Sets

47
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

Идеи
Библиотека dbtree для работы с
деревьями Nested Sets
Автор: Кузьма Феськов

При разработке веб-приложений программисты


часто сталкиваются с управлением иерархиче-
скими структурами. Dbtree облегчит жизнь

Класс базируется на основе библиотеки Максима Полтора-


ка phpDBTree 1.4 (http://dev.e-taller.net/dbtree/). Версию dbtree 2.1.
можно загрузить отсюда:
http://php.russofile.ru/resources/archives/0000/dbtree_2_01.zip. По-
следнюю версию библиотеки и документации всегда можно найти
на сайте http://php.russofile.ru/ru/authors/sql/nestedsets01/. В прило-
жении к данному номеру журнала вы сможете найти как саму
библиотеку, так и демонстрационный скрипт, запустив который,
вы сможете попробовать работу библиотеки в действии.

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

48
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

Примеры работы
Инициализация класса
Синтаксис использования библиотеки очень прост:
<?php
requare_once('dbtree.class.php');
$dbtree = new dbtree($table, $prefix, $db);
?>

После чего, переменная $dbtree содержит объект для работы с


деревьями.
Давайте рассмотрим параметры конструктора: $table – содер-
жит название таблицы, в которой хранится интересующее нас дере-
во; $prefix - содержит уникальный префикс для всех полей таблицы
с данными, например, если ваше поле называется user_id, то уни-
кальный префикс будет user; $db – это объект ADODB или объект
прилагаемого к классу демонстрационного драйвера, или объект на-
писанного вами драйвера.

Конкретные примеры
В качестве практического примера я рассмотрю возможность
хранения в рамках одной таблицы нескольких деревьев. Также мы
предположим, что структура нашего сайта хранится в виде дерева
Nested Sets. Мы посмотрим, какие возможности предоставляет нам
библиотека для работы с сайтом и для его обслуживания.
Итак, приступим. Исходные данные: мы будем использовать
дерево для хранения структуры сайта (то есть перечень его разде-
лов). Чтобы усложнить задачу, предположим, что сайт у нас поддер-
живает 2 языка. Это значит, что структур сайта у нас будет 2. Чтобы
не множить таблицы, мы оба дерева (русское и английское) будем
хранить в одной таблице.
В этом конкретном примере мы с вами посмотрим, какие воз-
можности библиотека предоставляет создателю сайта, и каким об-
разом вы можете облегчить свою жизнь.
Структура таблицы Table sections
CREATE TABLE `sections` (
`section_id` bigint(20) NOT NULL default '0',
`section_left` bigint(20) NOT NULL default '0',
`section_right` bigint(20) NOT NULL default '0',
`section_level` int(11) default NULL,
`section_lang` varchar(2) NOT NULL default '',
`section_name` varchar(255) NOT NULL default '',
`section_path` varchar(255) NOT NULL default '',
`section_full_path` varchar(255) default NULL,
PRIMARY KEY (`section_id`),
UNIQUE KEY `section_id` (`section_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

49
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

Первые 4 поля имеют отношения непосредственно к структу-


ре дерева и напрямую в них вмешиваться не следует.
section_lang служит семафором, указывая на принадлежность
элемента к определенному дереву. section_name – имя раздела для
вывода его пользователю. section_path – минимальный элемент пути
к разделу, например, sample. section_full_path будет содержать пол-
ный путь по сайту до указанного элемента, например,
my_site/sample/.
Настраиваем драйвер базы данных
Я приведу свои настройки для примера. В дальнейшем буду
считать, что вы эти действия уже выполнили и буду их опускать,
считая, что объект драйвера бызы данных будет у вас в переменной
$db.
Настраиваем ADODB:
<?php
/** Указываем путь до библиотеки ADODB */
define('ADODB_DIR', $_SERVER['DOCUMENT_ROOT'] . '/libs/adodb');

/** Название драйвера базы данных ADODB */


define('DB_DRIVER', 'mysql');

/** DB Host */
define('DB_HOST', 'localhost');

/** DB имя пользователя */


$DB_USER = 'user';

/** DB пароль пользователя */


$DB_PASSWORD = 'password';

/** DB название базы данных */


define('DB_BASE_NAME', 'dbtree_test');

/** Глобально управляем кэшированием */


define('DB_CACHE', TRUE);

/** Подключаем ADODB библиотеку */


require_once(ADODB_DIR . '/adodb.inc.php');

/** Настраиваем ADODB */


$ADODB_FETCH_MODE = 2; // ASSOC
$ADODB_CACHE_DIR = ADODB_DIR . '/ADOdbcache';
$db = &ADONewConnection(DB_DRIVER);
$db->Connect(DB_HOST, $DB_USER, $DB_PASSWORD, DB_BASE_NAME);

/** Настраиваем клиента на использование кодировки UTF-8 */


if ('mysql' == DB_DRIVER) {
$sql = 'SET NAMES utf8';
$res = $db->Execute($sql);
}
?>

Настраиваем драйвер-пример для MySql:


<?php
/** DB Host */
define('DB_HOST', 'localhost');

/** DB user name */

50
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

$DB_USER = 'root';

/** DB user password */


$DB_PASSWORD = '';

/** DB name */
define('DB_BASE_NAME', 'russofileru_php');

/** Управление кэшированием класса */


define('DB_CACHE', FALSE);

require_once('db_mysql.class.php');

$db = new db(DB_HOST, $DB_USER, $DB_PASSWORD, DB_BASE_NAME);

$sql = 'SET NAMES utf8';


$db->Execute($sql);
?>

Первоначальная инициализация таблицы


Далее, нам необходимо заполнить таблицу первоначальными
инициализационными данными.
<?php
$id = $db->GenID('sections_seq', 1);
$sql = 'INSERT INTO sections (section_id, section_left, section_right,
section_level, section_lang, section_name, section_path, section_full_path)
VALUES (' . $id . ', 1, 2, 0, 'ru', 'Root', '', '')';
$db->Execute($sql);

$id = $db->GenID('sections_seq', 1);


$sql = 'INSERT INTO sections (section_id, section_left, section_right,
section_level, section_lang, section_name, section_path, section_full_path)
VALUES (' . $id . ', 1, 2, 0, 'en', 'Root', '', '')';
$db->Execute($sql);
?>

Теперь наша таблица содержит все первоначальные данные и


мы можем начинать работу.
Добавление элементов в структуру сайта
Какие этапы нам нужно пройти?
Во-первых, получить все имеющееся дерево для нужной нам
языковой версии сайта.
Во-вторых, вывести его на экран пользователю, чтобы он вы-
брал, у какого раздела сайта мы создаем подраздел.
В-третьих, получить данные о новом разделе от пользователя.
В-четвертых, создать новый раздел в структуре дерева сайта.
Разумеется, есть еще подэтапы проверки введенных пользова-
телем данных, но мы их опустим, как опустим 3 этап, потому что он
не имеет отношения к решаемой проблеме. Мы могли бы опустить и
2 этап, но так как нам он часто будет нужен, я приведу его 1 раз и
далее буду ссылаться на этот пример.
<?php
// Первый и второй этапы
// Инициализируем класс

51
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

$dbtree = new dbtree('sections', 'section', $db);

// Получаем все дерево для русской версии сайта


$dbtree->Full(array('section_id', 'section_level', 'section_name',
'section_lang'), array('and' => array('section_lang = 'ru'')));

// Проверяем, возникли ли какие-либо ошибки в процессе работы класса


if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
} else {
// Обрабатываем полученное дерево
while ($item = $dbtree->NextRow()) {
// Делаем отступы (лесенка) согласно уровню вложенности
$item['spacer'] = str_repeat(' ', 6 * $item['section_level'];
$sections[] = $item;
}
}

// Выводим массив $sections на экран,


// чтобы дать возможность пользователю выбрать,
// у какого раздела создать подраздел.
.......
?>

Третий этап мы пропускаем, потому что принятие данных от


пользователя или из другого источника не является предметом мате-
риала, это вы и сами прекрасно реализуете.
А мы переходим к 4 этапу. Данные получены, проверены и
нам надо на их основе добавить новый раздел. Давайте перечислим,
какие данные нам нужны, чтобы добавить новый раздел: section_id –
номер раздела К КОТОРОМУ добавляем новый подраздел,
section_name – имя раздела, section_lang – идентификатор языковой
версии дерева, section_path – отрезок пути для данного конкретного
раздела.
При добавлении нового раздела нам необходимо рассчитать
section_full_path. Для этого нам необходимо получить всех роди-
телей добавляемого раздела и сложить их section_path в единый
путь.
Разместим все эти данные для удобства в массиве $section:
$section['section_id'], и так далее.
<?php
$dbtree = new dbtree('sections', 'section', $db);
// Получаем всех родителей раздела
$dbtree->Parents($section['section_id'], array('section_level', 'section_path'),
array('and' => array('section_lang = '' . $section['section_lang'] . ''')));
if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
} else {
// Обрабатываем полученную часть дерева
while ($item = $dbtree->NextRow()) {
// Получаем section_full_path
if (0 <> $item['section_level']) {
$path .= $item['section_path'] . '/';
}
}
// Заносим данные в массив нового раздела
$section['section_full_path'] = $path . $section['section_path'] . '/';
// Добавляем новый раздел
$id = $section['section_id'];

52
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

unset($section['section_id']);
$id = $dbtree->Insert($id, array('and' => array('section_lang = '' .
$section['section_lang'] . ''')), $section);
if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
}
}
?>

К этому моменту, если не произошло ошибок, у нас в русском


языковом дереве появился новый раздел.
Давайте заодно рассмотрим удаление раздела.
<?php
// Удаляем раздел. На входе section_id удаляемого раздела
// и section_lang
$dbtree = new dbtree('sections', 'section', $db);
$dbtree->Delete($section_id, array('and' => array('section_lang = '' .
$section_lang . ''')));
?>

Перемещение раздела
Перемещение раздела к другому родителю с данным классом
также не представляет особой проблемы.
Какие этапы нам необходимо пройти?
Во-первых, выбрать узел (также будут перемещаться и все его
дети), который хотим переместить.
Во-вторых, требуется выбрать нового родителя (родитель не
может находиться в пределах ветки, выбранной для перемещения).
В-третьих, на основании этих данных переместить ветку к но-
вому родителю.
Давайте посмотрим как это делается на практике. Первые два
этапа мы опустим, поскольку они сводятся к получению всего дере-
ва и выводу его пользователю для определения нужных section_id
разделов. Замечу, что перемещать узлы можно только в пределах од-
ного и того же дерева, то есть, если вы выбрали русскую версию де-
рева, то и новый родитель должен принадлежать этому же дереву.
Когда section_id перемещаемого узла и section_id нового роди-
теля получены, действуем следующим образом:
<?php
$dbtree = new dbtree('sections', 'section', $db);
$dbtree->MoveAll($old_id, $new_id, array('and' => array('section_lang = '' .
$section_lang . ''')));
if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
}
?>

Все, ваш раздел успешно перенесен. Не забывайте обрабаты-


вать section_full_path, задавая им новые значения с учетом изменив-
шегося пути.

53
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

Таким же образом легко поменять позицию у двух узлов (при


этом дети не перемещаются).

<?php
$dbtree = new dbtree('sections', 'section', $db);
$dbtree->ChangePosition($id1, $id2, array('and' => array('section_lang = '' .
$section_lang . ''')));
if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
}
?>

Таким образом мы поменяем местами узлы с номерами $id1 и


$id2 соответственно.
Как получить номер раздела, в котором находится пользо-
ватель?
В общем случае, у нас есть URL, который показывает нам
путь по которому шел пользователь (скрипту совершенно не важ-
но – реальный он или виртуальный). На основе URL мы попробуем
вычислить номер текущего раздела.
И так, у нас есть URL следующего вида:
http://mysite.ru/en/food/fruits/apples/2005-09-30/
Где http://mysite.ru/ - это название вашего сайта, en/ – это код
языка, с которым в данный момент работает пользователь,
food/fruits/apples/ - это собственно путь раздела, а 2005-09-30/ - это
дополнительный параметр для скрипта, не имеющий отношения к
пути следования пользователя.
Наша задача по пути раздела получить его section_id, а пара-
метр 2005-09-30 отдать на обработку соответствующего скрипта.
<?php
// Получаем код языка
$lang = substr($_SERVER['REQUEST_URI'], 0, 2);
// Получаем URL без языка
$uri = substr($_SERVER['REQUEST_URI'], 4, strlen($_SERVER['REQUEST_URI']) - 4) .
'/';

// Преобразуем URL в массив


$uri_array = explode('/', $uri);

// По отрезку пути получаем section_id родителя текущего раздела


if (empty($uri_array[0])) {
$sql = 'SELECT section_id FROM sections WHERE section_level = 0 AND
section_lang = '' . $lang . ''';
} else {
$sql = 'SELECT section_id FROM sections WHERE section_level = 1 AND
section_path = '' . addslashes($section_path) . '' AND section_lang = '' . $lang
. '' ORDER_BY section_left';
}
$section_id = $db->GetOne($sql);
if (FALSE === $section_id) {
// Ошибка 404 – страница не найдена
} else {
$dbtree = new dbtree('sections', 'section', $db);
// Получаем всю ветку текущего главного раздела
$dbtree->Branch($section['section_id'], '', array('and' => array

54
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

('section_lang = '' . $section['section_lang'] . ''')));


if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
}
// Организуем цикл для подбора текущего раздела по section_full_path
$flag = FALSE;
$rewrite_params = array();
while (FALSE === $flag) {
// Вычисляем section_id нужного раздела
while ($item = $dbtree->NextRow()) {
if ($uri == $item['section_full_path']) {
$current_id = $item['section_id'];
$section = $item;
$flag = TRUE;
}
}
// Все дополнительные параметры URL складываем
// В отдельный массив
if (!isset($current_id)) {
$rewrite_params[] = $uri_array[count($uri_array) - 2];
unset($uri_array[count($uri_array) - 2]);
$uri = implode('/', $uri_array);
}
}
if (!empty($rewrite_params)) {
$max = count($rewrite_params);
for ($i = 0;$i < $max;$i++) {
$page_params[] = $rewrite_params[count($rewrite_params) - $i - 1];
}
}
}
?>

В результате работы указанного выше кода вы получаете:


$current_id – номер раздела, в котором сейчас находится пользова-
тель, $page_params содержит массив с дополнительными параметра-
ми, переданными через URL (в нашем случае - 2005-09-30).
Отмечу, что этот механизм позволяет игнорировать в URL
ошибки пользователя. Это значит, что если пользователь ошибется в
любом элементе пути, ему будет показана страница, которая следо-
вала до этого ошибочного этапа. И только если URL вообще не бу-
дет подобран, будет выдана ошибка 404.
Разные вкусности для пользователя сайта
Современный сайт не мыслим без таких удобств, как навига-
тор (когда пользователь видит дорожку следования по сайту) или
подробного меню, предлагающего ему все разнообразие возможных
действий. В этой части нашего курса мы с вами посмотрим, какие
возможности для реализации подобного предлагает моя библиотека.
Давайте начнем с простого – построим навигатор по сайту.
Чно нам для этого понадобится? Да ничего особенного, все данные
к этому моменту у нас уже должны быть. Нам нужен только номер
раздела, в котором сейчас находится пользователь. По идее, вы
должны были его получить, когда определяли, какую страницу по-
казывать пользователю на основании section_full_path.
Раз все данные у нас уже есть, остается только построить на-
вигатор и вывести его на эран в нужном месте.

55
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

<?php
$dbtree = new dbtree('sections', 'section', $db);
// Получаем всех родителей раздела
$dbtree->Parents($section['section_id'], array('section_name',
'section_full_path'), array('and' => array('section_lang = '' . $section
['section_lang'] . ''')));
if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
} else {
// Обрабатываем полученную часть дерева
while ($item = $dbtree->NextRow()) {
// Строим цепочку навигатора
}
}
?>

Перейдем к построению меню, для подсказки пользователю


возможных действий. Библиотека предлагает вам минимум 3 вари-
анта построения меню.
Первый вариант – самый простой, мы считываем с вами все
дерево и показываем его пользователю в виде «Карты сайта», отме-
чая, скажем, жирным шрифтом, тот узел, где пользователь в данный
момент находится. Получать полное дерево сайта мы с вами уже
умеем. Согласитесь, этот прием очень не гибок, особенно, если у вас
слишком много разделов. Также он не совсем подходит, если вы в
дереве храните не структуру сайта, а, скажем, рубрикатор товаров
вашего магазина. Поэтому мы переходим ко второму варианту ме-
ню.
Второй вариант меню – это меню в виде приоткрытого дерева.
Что представляет из себя приоткрытое дерево? Это когда в данный
конкретный момент пользователь видит только цепочку, ведущую
до ближайших детей раздела, в котором находится пользователь.
Все остальные цепочки разделов в это время «закрыты» и представ-
лены только своими первоначальными родителями.
Для иллюстрации приведу пример. Вот так выглядит все наше
дерево:
MainSection 1
Section
Section
Section
Section
MainSection 2
Section
MainSection 3
Section
Section <<<<<
Section
Section
MainSection 4
Section
Section

Приоткрытое дерево для выделенного элемента будет выгля-


деть так:
MainSection 1
MainSection 2

56
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

MainSection 3
Section
Section
Section
Section
MainSection 4

Надеюсь, что эти примеры достаточно наглядны.


Итак, давайте построим подобное дерево. В исходных данных
нам требуется все тот же section_id текущего раздела, в котором на-
ходится в данный момент пользователь.
<?php
$dbtree = new dbtree('sections', 'section', $db);
// Получаем всех родителей раздела
$dbtree->Ajar($section['section_id'], array('section_name', 'section_level',
'section_full_path'), array('and' => array('section_lang = '' . $section
['section_lang'] . ''')));
if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
}
?>

На основе section_level вы легко можете отобразить ветку в


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

Давайте посмотрим, каким образом построить такое дерево.


Исходные данные – теже, что и в предыдущих примерах.
<?php
$dbtree = new dbtree('sections', 'section', $db);
// Получаем всех родителей раздела
$dbtree->Branch($section['section_id'], array('section_name', 'section_level',
'section_full_path'), array('and' => array('section_lang = '' . $section
['section_lang'] . ''')));
if (!empty($dbtree->ERRORS_MES)) {
// обрабатываем ошибки
}
?>

Выводим на экран лесенкой, как описано в предыдущих при-


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

57
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

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


му у нас есть реальная возможность снять нагрузку с сервера, произ-
водя кэширование запросов к базе данных. ADODB предлагает, а
мой класс поддерживает, кэширование запросов к базе.
Если ADODB вам по каким-то пречинам не подходит, вы
вполне можете реализовать кэширование сами. Для этого вам необ-
ходимо переписать соответствующие функции в предлагаемом с
библиотекой драйвере базы данных.
Разумеется, вам не стоит использовать кэширование в админи-
стративном интерфейсе вашего сайта, чтобы видеть все изменения в
реальном времени, но для показа сайта пользователю кэширование
оправдано и полезно!
Для глобального включения кэширования необходимо
константе DB_CACHE задать значение TRUE.
При вызове функций класса, которые поддерживают кэширо-
вание (смотрите ниже полное описание всех функций библиотеки),
вам необходимо последним параметром передавать время жизни
кэша в микросекундах. Например, 3600, составляет 1 час. В течение
указанного срока, ADODB будет обращаться к базе данных, только
если данный запрос проходит впервые. Если запрос уже был сделан
раньше – ADODB возьмет его результаты из кэша, что значительно
ускоряет работу вашего сайта и снимает нагрузку с сервера базы
данных.

58
PHP Inside №14 Библиотека dbtree для работы с деревьями Nested Sets

Введение в разработку веб-


приложений с Ajax
Автор: Jonathan Fenocchi
Перевод: Григорий Федоринов

С Ajax получать новые данные можно без переза-


грузки страницы

Раньше веб-приложения были ограничены в своих возможно-


стях, так как для добавления новых данных приходилось переза-
гружать веб-страницу (или загружать на ее место другую).
Другие методы были доступны (без перезагрузки страницы),
но техника оставляла желать лучшего и имелась тенденция к появ-
лению ошибок при использовании таких приложений. В последние
месяцы техника, которая широко не поддерживалась в прошлом,
стала доступна большинству серферов, дав тем самым свободу про-
граммистам для разработки приложений.
Приложения могут асинхронно получать XML-данные посред-
ством JavaScript. Такие приложения известны как "Ajax-приложе-
ния" (Асинхронные Javascript и XML-приложения). В этой статье я
объясню, как получить удаленный XML-файл через технологию
Ajax для обновления информации на веб-странице. В последующих
статьях я расскажу о более продвинутой методике использования
Ajax, что позволит вывести ваши веб-приложения на более высокий
уровень.
Первым шагом будет создание XML-файла с данными. Мы
назовем этот файл data.xml (прим. переводчика – файл нужно сохра-
нять в кодировке UTF-8). Это простой XML-файл, в реальных при-
ложениях они, конечно, намного сложнее и ухищреннее, но для яс-
ности примеры будут простыми и краткими.
<?xml version="1.0" encoding="UTF-8"?>
<root>
<data>
Это некоторые данные. Они хранятся в XML-файле и будут получены с помощью
JavaScript.
</data>
</root>

Теперь давайте создадим простенькую веб-страничку, содер-


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

59
PHP Inside №14 Введение в разработку веб-приложений с Ajax

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"


"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title> Разработка веб-приложений с помощью технологии Ajax </title>
</head>
<body>
<h1>Разработка веб-приложений с помощью технологии Ajax</h1>
<p>Эта страница демонстрирует использование асинхронных Javascript и XML
(Ajax)
технологий для обновления содержания страницы посредством чтения удаленного
файла динамически –
без перезагрузки страницы. Обратите внимание: эта операция невозможна при
отключенном JavaScript</p>
<p id="xmlObj">
Это некоторые данные. Они являются данными по умолчанию <a href="data.xml"
title="Обзор данных XML-файла." onclick="ajaxRead('data.xml');
this.style.display='none'; return false">Обзор XML данных.</a>
</p>
</body>
</html>

Обратите внимание: мы вставили ссылку к файлу data.xml для


пользователей с отключенным JavaScript. Для пользователей с вклю-
ченным JavaScript будет вызвана функция "ajaxRead", ссылка яв-
ляется скрытой и не переадресовывает к файлу data.xml. Функция
"ajaxRead" не описана, так что, если вы запустите этот пример, он
выдаст ошибку в JavaScript. Давайте продолжим наши эксперимен-
ты и в заголовке опишем эту функцию. Теперь вы сможете увидеть,
как работает технология Ajax. Следующий скрипт должен быть по-
мещен между тегов :
<script type="text/javascript"><!--
function ajaxRead(file){
var xmlObj = null;
if(window.XMLHttpRequest){
xmlObj = new XMLHttpRequest();
} else if(window.ActiveXObject){
xmlObj = new ActiveXObject("Microsoft.XMLHTTP");
} else {
return;
}
xmlObj.onreadystatechange = function(){
if(xmlObj.readyState == 4){
updateObj('xmlObj', xmlObj.responseXML.getElementsByTagName('data')[0].
firstChild.data);
}
}
xmlObj.open ('GET', file, true);
xmlObj.send ('');
}
function updateObj(obj, data){
document.getElementById(obj).firstChild.data = data;
}
//--></script>

Так как скрипт небольшой, то сразу же опишу все функции.


Первую функцию "ajaxRead" мы вызываем из нашей ссылки "Обзор
данных XML-файла".

60
PHP Inside №14 Введение в разработку веб-приложений с Ajax

В функции мы описываем переменную xmlObj – это будет


прослойкой между клиентом (пользователь, просматривающий веб-
страницу) и сервером (вашим сайтом). Мы описываем этот объект в
структуре if/else:
if(window.XMLHttpRequest){
xmlObj = new XMLHttpRequest();
} else if(window.ActiveXObject){
xmlObj = new ActiveXObject("Microsoft.XMLHTTP");
} else {
return;
}

Это проверка доступности методов, которые могут использо-


вать браузеры, так как различные браузеры описывают объект
XMLHttpRequest по-разному. Теперь, когда мы описали "xmlObj"
как объект XMLHttpRequest, мы можем реализовать его в зависимо-
сти от версии браузера. Если невозможно создать объект
XMLHttpRequest, мы выходим из функции командой "return" чтобы
избежать ошибок. В большинстве случаев этот код работает и созда-
ет объект XMLHttpRequest, однако могут быть исключения для бо-
лее ранних версий браузеров (например, это работает в IE 5.01, но
не работает в Netscape 4). Следующим идет этот блок:
xmlObj.onreadystatechange = function(){
if(xmlObj.readyState == 4){
updateObj('xmlObj', xmlObj.responseXML.getElementsByTagName('data')[0].
firstChild.data);

Каждый раз при изменении состояний объекта


XMLHttpRequest вызывается событие "onreadystatechange". Исполь-
зуя конструкцию "xmlObj.onreadystatechange = function(){ … }", мы
строим и запускаем функцию, "исполняемую на лету" каждый раз,
когда состояние объекта XMLHttpRequest изменяется. Всего может
быть 5 состояний, которые нумеруются от 0 до 4.
0. uninitialized (не инициализирован) (перед началом работы
объекта XMLHttpRequest)
1. loading (загрузка) (однажды, когда идет инициализация
объекта XMLHttpRequest)
2. loaded (загружен) (однажды XMLHttpRequest, когда полу-
чен ответ от сервера)
3. interactive (доступен) (пока объект XMLHttpRequest соеди-
нен с сервером)
4. complete (завершен) (после того, как объект
XMLHttpRequest завершил выполнение всех задач)
Пятое состояние (номер 4) наступает, когда мы уверены что
данные доступны, однако мы проверяем xmlObj.readyState на значе-
ние "4", для того чтобы убедиться, что данные доступны. Если это
так, мы запускаем функцию обновления. Эта функция имеет 2 пара-
метра: ID элемента в веб-странице (обновляемый элемент в текущей
веб-странице) и данные для заполнения этого элемента. О том, как
работает эта функция, будет описано позднее.

61
PHP Inside №14 Введение в разработку веб-приложений с Ajax

Наша веб-страница содержит тег P, который имеет ID =


"xmlData". Этот параграф и будет обновляться. Данные берутся из В последние месяцы
XML-файла, но это немного сложнее. Ниже описано, как это работа- техника, которая широко
ет. не поддерживалась в
прошлом, стала
Свойство xmlObj.responseXML является объектом DOM – это доступна большинству
немного похоже на "document" объект, за исключением того, что серферов, дав тем
оно используется для удаленного XML-файла. Другими словами, самым свободу
xmlObj.responseXML - это объект "document", если бы вы запускали программистам для
скрипт в файле data.xml. Так как мы знаем это, мы можем отыскать разработки приложений
любой XML-узел через "getElementsByTagName" метод.
Данные, содержащиеся в узле XML, как раз названы "<data>"
поэтому наша задача проста: получить первый узел с данными (и
только один). Это делает конструкция
xmlObject.responseXML.getElementsByTagName("data")[0], возвра-
щающая первый узел <data> из XML-файла.
Обратите внимание: эта конструкция возвращает только узел
из XML-файла, а не данные – данные должны быть выбраны по-
средством обращения к свойствам этого узла, это является следую-
щим шагом.
После этого надо просто обратиться к свойству этого объекта
через конструкцию "firstChild.data" (firstChild обращается к тексту
узла, который содержится в узле <data> , и данными является факти-
чески текст этого узла).
xmlObj.open ('GET', file, true);
xmlObj.send ('');

Это последний кусок в нашей функции ajaxRead. Что он дела-


ет? Метод "open" объекта xmlObj открывает соединение на сервер (в
данном примере указан метод "GET" – вы можете использовать как
"POST" так и другие методы, это допускается), получает файл (в на-
шем случае переменная "file", которая была отослана как параметр
функции ajaxRead – data.xml), и JavaScript должен обработать этот
запрос синхронно (false) или асинхронно (true по умолчанию). Так
как мы используем технологию асинхронных Javascript и XML, мы
будем использовать асинхронный метод – синхронный метод в на-
шем случае не будет работать.
В последней строке нашей функции мы просто отсылаем пу-
стую строку на сервер. Без этой строчки состояние объекта xmlObj
никогда не достигнет состояния «завершено», то есть не будет рав-
ным 4, и вы больше не сможете обновить вашу страничку. Метод
send можно использовать и для других вещей, но в этом примере мы
только получаем данные с сервера и не отсылаем их. Не хотелось бы
вдаваться в подробности метода в этом примере.
function updateObj(obj, data){
document.getElementById(obj).firstChild.data = data;
}

Теперь настало время объяснить функцию updateObj немного


подробнее: эта функция записывает в любой специфицированный
элемент на данной странице новое значение.

62
PHP Inside №14 Введение в разработку веб-приложений с Ajax

Ее первый параметр "obj," просто описывает ID элемента на


данной веб-странице – объекта, который будет обновляться; второй
параметр - "data," это строка, которая описывает новые данные, ко-
торые должны быть помещены в обновляемый объект ("obj").
Обычно делают проверку того, присутствует ли на текущей
странице элемент ID, указанный в "obj", но эта проверка в данном
случае не является необходимостью. Функция updates работает
подобно способу получения данных из узла "data" в XML-файле –
она ищет элемент, который хочет обновить (на этот раз вместо ID
элемента используется имя тега и индекс на странице), и устанавли-
вает данные первого потомка (текст узла) этого элемента в новую
данную. Если вы захотите обновить объект с HTML, надо использо-
вать следующую конструкцию: document.getElementById(obj).
innerHTML = data. Это все, что есть по этому примеру.
Концепция проста, и код не очень сложный. Вы хотите читать
файл откуда-либо без возможности перезагружать страницу. Эта
технология достаточно гибка и позволяет вам делать различные
вещи, включая отправку данных из форм без перезагрузки страницы
и использовать скрипты на стороне сервера для формирования
XML-файлов динамически. Если вы хотите сделать шаг вперед, за-
помните что Google - ваш друг. В следующей статье я объясню, как
вы можете использовать Ajax вместе с сервер-исполняемыми скрип-
тами для того, чтобы сделать более мощные веб-приложения.

Об авторе
Джонатан Феноччи - веб-разработчик, специализирующийся в
области веб-дизайна, написания скриптов, исполняемых на клиент-
ской стороне, и PHP-программирования. Его сайт расположен здесь:
www.slightlyremarkable.com
Оригинал статьи:
http://www.webreference.com/programming/javascript/jf/column12/

Множественное наследование в ОО
модели PHP

63
PHP Inside №14 Множественное наследование в ОО модели PHP

Множественное наследование в ОО
модели PHP
Автор: Денис Баженов

Множественное наследование в объектной моде-


ли PHP может решить некоторые проблемы

Подсунь свою мечту врагам, может быть, они погибнут при ее реализации

Возможно, у кого-то название этой статьи вызвало «от-


торжение», а в голове пронеслась мысль, о том, что ее (статью)
надо было бы назвать «Мартышка и очки». Действительно, а за-
чем оно нам надо? В последнее время многие языки (но не все)
отказываются от множественного наследования реализации (НЕ
интерфейса!) как от рудимента ООП. Я не стану брать на себя
ответственность и судить о том, верно ли это.
Однако я знаю, что в некоторых случаях множественное на-
следование позволяет предотвратить комбинаторный рост числа В некоторых случаях
классов, когда вы пытаетесь, например, охарактеризовать объекты с множественное
точки зрения принадлежности категориям. Возьмем такой пример. В наследование позволяет
частности я как человек подпадаю под множество категорий (или, предотарвтить
как говорят социологи, социальных ролей). комбинаторный рост
числа классов
Рассмотрим несколько из них. Я сын своих родителей, я про-
граммист, я гражданин РФ и т.д. В контексте каждой категории я
могу выполнить различные действия. Скажем, как сын родителей я
могу требовать от отца алименты, если родители в разводе. А как
гражданин Российской Федерации я могу голосовать на выборах.
При отсутствии механизмов множественного наследования
вам пришлось бы плодить ненужное множество лишних классов или
воспользоваться наследованием интерфейсов (благо ZE2 это позво-
ляет). Однако в случае использования интерфейсов вы ограничивае-
те себя тем, что не сможете наследовать реализацию, которая будет
идентична в подавляющем большинстве случаев, что приведет к
многократному повторению кода.

От теории к практике
Код, который реализует некоторое подобие множественного
наследования, вы сможете найти в приложении к журналу. Это
класс multipleInheritance, который, по сути, является прокси-объек-
том к набору классов.

64
PHP Inside №14 Множественное наследование в ОО модели PHP

Наверное, именно так и стоило бы назвать решение, так как


истинное множественное наследование - это гораздо более сложный
инструмент, который должен быть реализован на уровне языка.
Давайте посмотрим, что можно «выжать» из этого класса.
Вы можете использовать оператор -> для вызова методов как
дочернего класса, так и методов родительских классов. То же самое
относится к свойствам классов. Если метод реализуется более чем
одним родительским классом, а в дочернем классе этот метод не
перегружается, то генерируется исключительная ситуация.
Методы дочерних классов могут использовать оператор :: для
вызова методов родительского класса. При этом метод будет вызван
с контекстом дочернего класса. Это позволяет родительскому клас-
су иметь доступ к элементам дочернего. Количество наследуемых
классов и их имена могут определяться на стадии выполнения.
На этом плюсы заканчиваются и начинаются минусы! Необхо-
димо понимать, что создаваемые объекты – это разные объекты со
своими контекстами. Отсюда вытекает следующее.
При использовании оператора -> вы не сможете вызвать из
родительского класса методы дочернего, потому как они находятся
в разных контекстах. При использовании оператора :: $this будет
указывать на контекст дочернего класса, а не родительского, что мо-
жет привести к самым интересным и непредсказуемым последстви-
ям.
Если несколько родительских классов (скажем, два) наследу-
ют реализацию от одного класса (дедушки), то в итоге у вас будут
два дедушки по одному на каждый родительский объект. То есть,
изъясняясь терминами C++, нет поддержки виртуальных функций.
Методы и свойства родительского класса должны быть объявлены
как public, иначе вы не сможете получить к ним доступ из-за «разно-
сти» контекстов.
Несмотря на все эти недочеты, в простых случаях данный
класс все же может быть применен. Особенно для реализации «под-
мешанных» (mix-in) классов.

Использование
Возьмем для примера два класса, которые не связаны между
собой по смыслу предметной логики. Первый класс (forumMessage)
– это класс, управляющий сообщениями в форуме. Второй класс
(Observable) является реализацией паттерна Observer (наблюдатель)
(более подробно об этом паттерне вы можете узнать на phpPatterns).
Необходимо связать эти два класса так, чтобы не пришлось
нарушать текущие зависимости классов (то есть мы не можем насле-
довать forumMessage от Observer по ряду причин), и при этом ис-
пользовать их как один объект.
class forumMessage
{
public $sender;
public $text;

public function add($sender, $text)

65
PHP Inside №14 Множественное наследование в ОО модели PHP

{
$this->sender = $sender;
$this->text = $text;
// Добавление сообщения в хранилище
}
}

class Observable
{
// Зарегистрированные наблюдатели
public $observers;

public function addObserver(Observer $observer)


{
// Регистрируем наблюдателя
}

public function notify($event)


{
// Генерируем уведомление и передаем его всем зарегистрированным
наблюдателям
}
}

Реализации подобного решения требует создать класс, кото-


рый при создании экземпляра будет являться прокси-объектом для
двух наших классов (forumMessage и Observable). Такой класс мож-
но создать, наследуя его от класса multipleInheritance. Делается это
следующим образом.

class observableForumMessage extends multipleInheritance


{
public function __construct()
{
multipleInheritance::__construct('forumMessage', 'Observable');
}

public function addMessage($sender, $text)


{
$this->add($sender, $text); // Вызывается forumMessage::add
()
$this->notify('new message'); // Вызывается Observable::notify()
}
}

Я думаю, исходный код не нуждается в комментариях.


Если вы взглянете на код класса multipleInheritance, то пой-
мете, что там все до смешного просто. Используются средства
overloading, которые начали нормально работать на «пятерке».

Вывод
Да, безусловно, этот метод - это dirty hack (а может быть, он
даже и этого звания не достоин :) ).
PHP Inside №14 Множественное наследование в ОО модели PHP

Однако он может отлично подойти для реализации, скажем,


совмещения некоторых паттернов с классами бизнес логики.
Аналогичное решение можно получить, используя функции
семейства Object Aggregation, но они свойственны только
«четверке». В PHP5 функции Object Aggregation и ClassKit были за-
мещены расширением RunKit, которое позволяет реализовать то же
самое, но более качественно и гибко.
Однако я не смог заставить работать это расширение на своей
площадке. При обработке запроса Apache сообщал о критической
ошибке и умирал, ссылаясь на ошибку в ntdll.dll. Похоже, сказыва-
ется злополучная «сырость» PHP 5.1 (Статья была написана на
этапе бета-версии PHP 5.1 - прим. редактора). Думаю, на 5.0 все
должно работать.