Автоматизированное тестирование с использованием Zend Framework
Метки: веб-разработка, модульное тестирование, zend framework
Автоматизоване тестування з використанням Zend Framework
Automated Testing Using Zend Framework
| ← Интеграция FCKeditor в Zend_Form | Паттерн кэширования для моделей → |
Автоматизированное тестирование вашего веб-приложения является важным шагом для уверенности в качестве и отсутствии ухудшения, при внесении изменений в ваше приложение. С тестирующим фреймворком от Zend Framework (построен с PHPUnit) вы можете составить блоки тестовых случаев для вашего веб-приложения без малейших препираний.
В этой статье предоставлена вся базовая информация, которая понадобится вам при написании автоматизированных тестов для приложений Zend Framework.
А теперь, давайте перейдем к делу
В нижеприведенном примере я буду использовать действительный контроллер одного из моих проектов. Этот контроллер управляет действиями, связанными с учетными записями, такими как вход, выход, регистрация и подтверждение. Мы будем использовать тестовую базу данных со схемой, которая клонирует нашу базу данных продукции, с Doctrine для управления ORM (прости, Zend_Db :() Я предполагаю, что вы используете вышеуказанную схему проектов Zend Framework (1.6+), и что вы знакомы с Zend_Config и используете плагин контроллера Initializer (созданный по умолчанию, если вы используете Zend Studio for Eclipse 6.1).
Подготовка вашего приложения
Первым шагом для создания автоматизированного тестирования является правильная подготовка окружения и настроек вашего приложения. В зависимости от ваших установок, это может включать установку глобальных переменных, изменение связей базы данных, или перенастройка путей. К счастью для нас, это легко делается с помощью у Zend_Config и плагина контроллера Initializer.
Zend_Config позволяет назначать «разделы», которые могут наследоваться из другого раздела. Это позволяет менять конфигурацию для разных условий без дублирования установок в разных файлах (и таким образом помогает нам убедиться, что мы ничего не забыли!). В нашем тестовом проекте нам понадобится изменить только строку соединения с базой данных, значит, мы используем тестовую базу данных.
Убрать подсветку кода
Обратите внимание, как мы можем наследовать дочерние атрибуты: хотя мы назначили узел, нам не потребовалось назначать все, что ниже него.
Сейчас, когда все наши настройки в порядке, нам необходимо что-то для управления ими, а также выключение, основанного на среде, в которой мы работаем. Это роль нашего плагина Initializer, который принимает среду для инициализации как параметр конструктора.
Простой пример
Давайте начнем с простого фреймворка тестирования контроллера. Если вы используете Zend Studio для Eclipse, вы можете легко создать эту структуру щелчком правой кнопки на контроллере в PHP Explorer, перейти в New > Zend Framework Item и затем выбрать Zend Controller Test Case. Потом просто убедитесь, что контроллер, который вы хотите протестировать, выбран, и нажмите finish.
Убрать подсветку кода
Как вы можете видеть, в строке 21 мы используем наш инициализатор для установки тестовой среды до запуска тестов. До запуска каждого теста, PHPUnit будет вызывать наш метод setup(), который был запрограммирован на запуск нашего метода appBootstrap. Это гарантирует нам, что мы используем чистую конфигурацию и среду перед каждым тестом, так же, как если бы каждый тест был отдельным процессом. Когда все тесты пройдены, вызывается метод tearDown(). Это место для любого кода, который удаляет ресурсы или сбрасывает любые изменения, которые могли сделать тесты. Нам пригодится это позже в наших продвинутых примерах.
Строка 45 содержит скелет тестового условия, который будет обеспечивать, что диспетчеризация результатов ‘/index/index’ в контроллере названном ‘index’ и действии под названием ‘index’, были запущены последними. Это может казаться тривиальным, но это поможет обнаружить ошибки связанные с вашими контроллерами. Если будет обнаружено непредусмотренное исключение, утверждение контроллера будет ошибочным, поскольку он будет запущен последним.
Запуск ваших тестов
Чтобы статья была сфокусирована, я решил удалить этот раздел и рассказать только о написании тестов. Если вам нужна помощь в создании Блоков Теста и запуске тестов с командной строки, смотрите документацию PHPUnit, особенно раздел о запуске тестов с командной строки и организации блоков теста.
Расширение функциональности тестирования
Сейчас, когда мы рассмотрели основы, давайте перейдем к тестированию наших контроллеров учетных записей. Есть пара дополнительных требований для тестирования контроллера учетных записей. Во-первых, нам нужно протестировать весь процесс создания учетной записи так, как будто пользователь действительно регистрируется. Когда мы закончили тестирование, необходимо избавиться от всех возможных данных так, чтобы мы могли запускать тест столько раз, сколько потребуется, и не беспокоились об увеличении базы данных. Во-вторых, нам нужно способ симулировать действия настоящего пользователя, ровным счетом, как и проверять настоящий ли он.
Поскольку эти операции довольно общие, и есть возможность их повторного использования, давайте поместим их в отдельный класс и позволим тестовым условиям их унаследовать.
Одноразовые модели тестирования
Есть два способа убедиться в том, что данные, которые создаются на протяжении теста, удаляются после его выполнения. Некоторые предпочитают создавать базы данных на лету, используя изначальные данные.
Поскольку я использую Doctrine для моих проектов и работаю напрямую с моделью (без грубых запросов) в тестах, я решил, что просто удаление данных будет лучшим решением. Для осуществления этого, все что нам необходимо это «назначить» нашу модель для удаления, после того как она была создана (или загружена).
Убрать подсветку кода
Эта функция просто дает ссылку на модель, и хранит ее в массиве, который позже будет управляться нашей функцией tearDown():
Убрать подсветку кода
Мы просто прогоняем через все наши «назначенные» модели и удаляем их. Это должно быть сделано в tearDown() и не внутри любого из методов теста, так как это единственный способ быть уверенным, что оно случится. Когда утверждение ошибочно или случается неожиданное исключение в тесте, этот метод прекращает выполняться. Если мы пытаемся поместить наши модели после утверждения (assert), это может никогда не произойти.
Таким же образом, мы явно не можем располагать моделью до ассерта, если эта модель необходима для ассерта (и зачем она могла бы существовать, если в ней нет необходимости?).
Поддержка аутентификации
Всего 3 вещи, которые мы должны иметь возможность сделать поочередности для полного теста аутентификации.
* Создать фальшивый идентификатор
* Установить нашу среду в состояние эквивалентное пользователю, который осуществил вход в систему
* Независимо от изменения состояния среды, утвердить состояние произведенного входа
Наш пример контроллера использует адаптер базы данных для аутентификации и поиск идентификатора, значит, генерация фальшивого идентификатора означает для нас создание (или загрузку) записи в нашу таблицу учетных записей, и возврат идентификатора данных, который мы обычно получаем.
Убрать подсветку кода
Учетная запись это наша модель, тип Doctrine_Record. Мы просто создаем случайную учетную запись и возвращаем эти данные как наш идентификатор. Обратите внимание, что мы также установили эту модель для удаления (как показано выше). Сейчас нам необходимо найти способ установить нашу среду в состояние «залогинен», так как это фальшивый пользователь.
Убрать подсветку кода
В нашем примере приложения, если Zend_Auth имеет идентификатор, значит, пользователь произвел вход в систему. Следовательно, все, что нам нужно сделать, это хранить идентификатор в хранилище адаптера Zend_Auth, и считаться вошедшими в систему. Это делает ассерт входа настолько же простым, как и проверку на идентификатор.
Убрать подсветку кода
Этот простой ассерт дает уверенность, что мы произвели (или не произвели) вход в систему.
Складываем все вместе
Складывая все это вместе, мы теперь имеем базовый класс, который обеспечивает все наши случаи теста с функциональностью, которая нам необходима.
Убрать подсветку кода
Написание теста контроллера
Теперь, когда наш фундамент заложено, мы можем, наконец, начать писать тест контроллера.
Наша первая установка случая теста будет покрывать следующее требование: «когда пользователи регистрируются, они должны подтверждать свои e-mail адреса до того, как они получат доступ к своей учетной записи». Для этого нам необходимо симулировать как пользователь отправляет свои корректные регистрационные данные в наш контроллер, и проверять чтобы созданная учетная запись не была подтверждена. Затем мы должны протестировать, что отправляемый логин для неподтвержденной учетной записи, не приведет к авторизации пользователя.
Убрать подсветку кода
Наш первый тест просто использует глобальную переменную $_POST для симуляции отправки нашей регистрационной формы с некоторыми тестовыми данными. После dispatch(), мы используем Doctrine_Table для поиска модели созданной AccountController::registerAction(), затем утверждаем, что запись была найдена и что она не была помечена как подтвержденная.
Второй тест работает ручной вставкой записей, которые не подтверждены и дают уверенность, что пользователи не пройдут авторизацию, когда пытаются произвести вход с данной информацией по учетной записи. Как дополнительный бонус, мы также используем assertNotRedirect() чтобы убедиться, что наш контроллер не делает перенаправление. Наш контроллер должен делать перенаправление только в случае успешного входа, в остальных случаях это вводило бы пользователя в заблуждение.
Оригинал: Automated Testing Using Zend Framework
В этой статье предоставлена вся базовая информация, которая понадобится вам при написании автоматизированных тестов для приложений Zend Framework.
А теперь, давайте перейдем к делу
В нижеприведенном примере я буду использовать действительный контроллер одного из моих проектов. Этот контроллер управляет действиями, связанными с учетными записями, такими как вход, выход, регистрация и подтверждение. Мы будем использовать тестовую базу данных со схемой, которая клонирует нашу базу данных продукции, с Doctrine для управления ORM (прости, Zend_Db :() Я предполагаю, что вы используете вышеуказанную схему проектов Zend Framework (1.6+), и что вы знакомы с Zend_Config и используете плагин контроллера Initializer (созданный по умолчанию, если вы используете Zend Studio for Eclipse 6.1).
Подготовка вашего приложения
Первым шагом для создания автоматизированного тестирования является правильная подготовка окружения и настроек вашего приложения. В зависимости от ваших установок, это может включать установку глобальных переменных, изменение связей базы данных, или перенастройка путей. К счастью для нас, это легко делается с помощью у Zend_Config и плагина контроллера Initializer.
Zend_Config позволяет назначать «разделы», которые могут наследоваться из другого раздела. Это позволяет менять конфигурацию для разных условий без дублирования установок в разных файлах (и таким образом помогает нам убедиться, что мы ничего не забыли!). В нашем тестовом проекте нам понадобится изменить только строку соединения с базой данных, значит, мы используем тестовую базу данных.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" encoding="UTF-8"?> <config> <production> <db> <dsn>mysql://dbowner:password@localhost/maindb</dsn> <attributes> <model_loading>conservative</model_loading> </attributes> </db> </production> <test extends="production"> <db> <dsn>mysql://dbowner:password@localhost/maindb_test</dsn> </db> </test> </config> |
Обратите внимание, как мы можем наследовать дочерние атрибуты: хотя мы назначили узел, нам не потребовалось назначать все, что ниже него.
Сейчас, когда все наши настройки в порядке, нам необходимо что-то для управления ими, а также выключение, основанного на среде, в которой мы работаем. Это роль нашего плагина Initializer, который принимает среду для инициализации как параметр конструктора.
Простой пример
Давайте начнем с простого фреймворка тестирования контроллера. Если вы используете Zend Studio для Eclipse, вы можете легко создать эту структуру щелчком правой кнопки на контроллере в PHP Explorer, перейти в New > Zend Framework Item и затем выбрать Zend Controller Test Case. Потом просто убедитесь, что контроллер, который вы хотите протестировать, выбран, и нажмите finish.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | require_once 'Zend/Test/PHPUnit/ControllerTestCase.php'; require_once 'application/Initializer.php'; require_once 'application/default/controllers/IndexController.php'; class AccountControllerTest extends Zend_Test_PHPUnit_ControllerTestCase { /** * Подготовка среды перед запуском теста */ protected function setUp() { $this->bootstrap = array ($this, 'appBootstrap' ); parent::setUp (); // TODO Auto-generated FooControllerTest::setUp() } /** * Подготовка среды перед запуском теста */ public function appBootstrap() { $this->frontController->registerPlugin ( new Initializer( 'test' ) ); } /** * Очистка среды после запуска теста */ protected function tearDown() { // TODO Автогенерация FooControllerTest::tearDown() parent::tearDown (); } /** * Создание условий теста */ public function __construct() { // TODO Автогенерация конструктора } /** * Тесты FooController->barAction() */ public function testIndexAction() { // TODO Автосгенерированный // FooControllerTest->testBarAction() $this->dispatch ( '/index/index' ); $this->assertController ( 'index' ); $this->assertAction ( 'index' ); } } |
Как вы можете видеть, в строке 21 мы используем наш инициализатор для установки тестовой среды до запуска тестов. До запуска каждого теста, PHPUnit будет вызывать наш метод setup(), который был запрограммирован на запуск нашего метода appBootstrap. Это гарантирует нам, что мы используем чистую конфигурацию и среду перед каждым тестом, так же, как если бы каждый тест был отдельным процессом. Когда все тесты пройдены, вызывается метод tearDown(). Это место для любого кода, который удаляет ресурсы или сбрасывает любые изменения, которые могли сделать тесты. Нам пригодится это позже в наших продвинутых примерах.
Строка 45 содержит скелет тестового условия, который будет обеспечивать, что диспетчеризация результатов ‘/index/index’ в контроллере названном ‘index’ и действии под названием ‘index’, были запущены последними. Это может казаться тривиальным, но это поможет обнаружить ошибки связанные с вашими контроллерами. Если будет обнаружено непредусмотренное исключение, утверждение контроллера будет ошибочным, поскольку он будет запущен последним.
Запуск ваших тестов
Чтобы статья была сфокусирована, я решил удалить этот раздел и рассказать только о написании тестов. Если вам нужна помощь в создании Блоков Теста и запуске тестов с командной строки, смотрите документацию PHPUnit, особенно раздел о запуске тестов с командной строки и организации блоков теста.
Расширение функциональности тестирования
Сейчас, когда мы рассмотрели основы, давайте перейдем к тестированию наших контроллеров учетных записей. Есть пара дополнительных требований для тестирования контроллера учетных записей. Во-первых, нам нужно протестировать весь процесс создания учетной записи так, как будто пользователь действительно регистрируется. Когда мы закончили тестирование, необходимо избавиться от всех возможных данных так, чтобы мы могли запускать тест столько раз, сколько потребуется, и не беспокоились об увеличении базы данных. Во-вторых, нам нужно способ симулировать действия настоящего пользователя, ровным счетом, как и проверять настоящий ли он.
Поскольку эти операции довольно общие, и есть возможность их повторного использования, давайте поместим их в отдельный класс и позволим тестовым условиям их унаследовать.
Одноразовые модели тестирования
Есть два способа убедиться в том, что данные, которые создаются на протяжении теста, удаляются после его выполнения. Некоторые предпочитают создавать базы данных на лету, используя изначальные данные.
Поскольку я использую Doctrine для моих проектов и работаю напрямую с моделью (без грубых запросов) в тестах, я решил, что просто удаление данных будет лучшим решением. Для осуществления этого, все что нам необходимо это «назначить» нашу модель для удаления, после того как она была создана (или загружена).
Убрать подсветку кода
1 2 3 4 5 | protected function _setDisposable( Doctrine_Record $model ) { $this->_disposables[] = $model; } |
Эта функция просто дает ссылку на модель, и хранит ее в массиве, который позже будет управляться нашей функцией tearDown():
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 | protected function tearDown() { parent::tearDown(); foreach ( $this->_disposables as $model ) { if ( $model instanceof Doctrine_Record ) { $model->delete(); } unset( $model ); } } |
Мы просто прогоняем через все наши «назначенные» модели и удаляем их. Это должно быть сделано в tearDown() и не внутри любого из методов теста, так как это единственный способ быть уверенным, что оно случится. Когда утверждение ошибочно или случается неожиданное исключение в тесте, этот метод прекращает выполняться. Если мы пытаемся поместить наши модели после утверждения (assert), это может никогда не произойти.
Таким же образом, мы явно не можем располагать моделью до ассерта, если эта модель необходима для ассерта (и зачем она могла бы существовать, если в ней нет необходимости?).
Поддержка аутентификации
Всего 3 вещи, которые мы должны иметь возможность сделать поочередности для полного теста аутентификации.
* Создать фальшивый идентификатор
* Установить нашу среду в состояние эквивалентное пользователю, который осуществил вход в систему
* Независимо от изменения состояния среды, утвердить состояние произведенного входа
Наш пример контроллера использует адаптер базы данных для аутентификации и поиск идентификатора, значит, генерация фальшивого идентификатора означает для нас создание (или загрузку) записи в нашу таблицу учетных записей, и возврат идентификатора данных, который мы обычно получаем.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * Генерация фальшивого идентификатора, используется для симуляции входа пользователя в систему * @return StdClass an identity */ protected function _generateFakeIdentity() { $identity = new stdClass(); $account = new Account(); $account->username = 'AutoTest' . time(); $account->emailAddress = 'autotest@example.org'; $account->password = md5( 'password' ); $account->confirmed = true; $account->enabled = true; $account->save(); $this->_setDisposable( $account ); foreach( $account->toArray() as $key => $val ) { $identity->$key = $val; } unset( $identity->password ); return $identity; } |
Учетная запись это наша модель, тип Doctrine_Record. Мы просто создаем случайную учетную запись и возвращаем эти данные как наш идентификатор. Обратите внимание, что мы также установили эту модель для удаления (как показано выше). Сейчас нам необходимо найти способ установить нашу среду в состояние «залогинен», так как это фальшивый пользователь.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 | /** * Установка текущего состояния как пользователь, который осуществил вход * @param object $identity - используется identity, иначе оно будет сгенерировано * @return void */ protected function _doLogin( $identity = null ) { if ( $identity === null ) { $identity = $this->_generateFakeIdentity(); } Zend_Auth::getInstance()->getStorage()->write( $identity ); } |
В нашем примере приложения, если Zend_Auth имеет идентификатор, значит, пользователь произвел вход в систему. Следовательно, все, что нам нужно сделать, это хранить идентификатор в хранилище адаптера Zend_Auth, и считаться вошедшими в систему. Это делает ассерт входа настолько же простым, как и проверку на идентификатор.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 | public function assertNotLoggedIn() { $this->assertFalse( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' ); } public function assertLoggedIn() { $this->assertTrue( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' ); } |
Этот простой ассерт дает уверенность, что мы произвели (или не произвели) вход в систему.
Складываем все вместе
Складывая все это вместе, мы теперь имеем базовый класс, который обеспечивает все наши случаи теста с функциональностью, которая нам необходима.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | require_once 'Zend/Test/PHPUnit/ControllerTestCase.php'; class BaseControllerTest extends Zend_Test_PHPUnit_ControllerTestCase { /** * Включает модель, которая должна уничтожаться в дальнейшем * * @var array */ protected $_disposables = array(); protected function tearDown() { parent::tearDown(); foreach ( $this->_disposables as $model ) { if ( $model instanceof Doctrine_Record ) { $model->delete(); } unset( $model ); } } /** * Устанавливает модель как одноразовую, таким образом, teardown удалит ее автоматически * * @param Doctrine_record $model */ protected function _setDisposable( Doctrine_record $model ) { $this->_disposables[] = $model; } /** * Установка текущего состояния как пользователь, который осуществил вход * @param object $identity - используется identity, иначе оно будет сгенерировано * @return void */ protected function _doLogin( $identity = null ) { if ( $identity === null ) { $identity = $this->_generateFakeIdentity(); } Zend_Auth::getInstance()->getStorage()->write( $identity ); } /** * Генерация фальшивого идентификатора, используется для симуляции входа пользователя в систему * @param boolean $unique * @return StdClass an identity */ protected function _generateFakeIdentity( $unique = false ) { $identity = new stdClass(); $account = new Account(); $account->username = 'AutoTest' . time(); $account->emailAddress = 'autotest' . time() . '@example.org'; $account->password = md5( 'password' ); $account->confirmed = true; $account->enabled = true; $account->save(); $this->_setDisposable( $account ); foreach( $account->toArray() as $key => $val ) { $identity->$key = $val; } unset( $identity->password ); return $identity; } public function assertNotLoggedIn() { $this->assertFalse( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' ); } public function assertLoggedIn() { $this->assertTrue( Zend_Auth::getInstance()->hasIdentity(), 'Login assertion failed' ); } } |
Написание теста контроллера
Теперь, когда наш фундамент заложено, мы можем, наконец, начать писать тест контроллера.
Наша первая установка случая теста будет покрывать следующее требование: «когда пользователи регистрируются, они должны подтверждать свои e-mail адреса до того, как они получат доступ к своей учетной записи». Для этого нам необходимо симулировать как пользователь отправляет свои корректные регистрационные данные в наш контроллер, и проверять чтобы созданная учетная запись не была подтверждена. Затем мы должны протестировать, что отправляемый логин для неподтвержденной учетной записи, не приведет к авторизации пользователя.
Убрать подсветку кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | public function testRegisterCreatesNewUnconfirmedAccount() { $email = 'autotest' . time() . '@example.org'; $data = array( 'emailAddress' => $email, 'password' => 'testpassw0rd', 'passwordconfirm' => 'testpassw0rd' ); $_POST = $data; $this->dispatch( '/account/register' ); //пытаемся найти учетную запись $table = Doctrine_Table::create( 'Account' ) ; $account = $table->findOneByEmailAddress( $email ); $this->_setDisposable( $account ); $this->assertNotNull( $account ); $this->assertFalse( $account->confirmed, 'Учетная запись не была помечена как неподтвержденная' ); } /** * Утверждает, что пользователь, который не был подтвержден, не может осуществить вход в систему */ public function testUnconfirmedUserCannotLogin() { $email = 'autotest' . time() . '@example.org'; $account = new Account(); $account->username = $email; $account->password = md5( 'password' ); $account->emailAddress = $email; $account->confirmed = false; $account->enabled = true; $account->save(); $this->_setDisposable( $account ); $_POST['username'] = $email; $_POST['password'] = 'password'; $this->dispatch( '/account/login' ); $this->assertFalse( Zend_Auth::getInstance()->hasIdentity() ); $this->assertNotRedirect(); } |
Наш первый тест просто использует глобальную переменную $_POST для симуляции отправки нашей регистрационной формы с некоторыми тестовыми данными. После dispatch(), мы используем Doctrine_Table для поиска модели созданной AccountController::registerAction(), затем утверждаем, что запись была найдена и что она не была помечена как подтвержденная.
Второй тест работает ручной вставкой записей, которые не подтверждены и дают уверенность, что пользователи не пройдут авторизацию, когда пытаются произвести вход с данной информацией по учетной записи. Как дополнительный бонус, мы также используем assertNotRedirect() чтобы убедиться, что наш контроллер не делает перенаправление. Наш контроллер должен делать перенаправление только в случае успешного входа, в остальных случаях это вводило бы пользователя в заблуждение.
Оригинал: Automated Testing Using Zend Framework
Рейтинг:




<< Вы можете поставить оценку этой статьеПодобные статьи:
6 инструментов чтобы быть эффективным Web-разработчиком
Понимание области видимости в объектно-ориентированном JavaScript
Введение в искусство модульного тестирования в PHP
Интеграция FCKeditor в Zend_Form
Zend Framework и перевод