Тестирование моделей в CakePHP

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

В поисках подходящего the fucking manual я набрёл на Testing Models with CakePHP 1.2 test suite на bakery.cakephp.org.
И всё очень хорошо расписано, но, блин, не работает и всё. Поэтому не читайте то, что там написано. Я тщательно изучив исходники методом научного тыка сделал так, чтобы всё работало.

Идеология тестирования моделей в CakePHP
Модели работают с базой данных и соответственно надо проверить насколько хорошо это получается. Я сторонник fat Models, thin Controllers и стараюсь переносить всю логику работы с данными в модели. Поэтому проверять надо много.

Можно указать отдельное подключение для тестовой базы данных в app/config/database.php в переменной $test. Обычно подходят те же значения, что и в $default. Если вы уберёте $test, то CakePHP сам установит её такой же как и $default. В любом случае, не волнуйтесь, потому что для тестовых таблиц будет добавлен префикс test_suite.

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

Создание Fixtures
Как говорится "сначала было слово", вот мы и создадим fixture для модели Word.

app/test/fixtures/word_test_fixture.php

PHP:
  1. <?
  2.  
  3. class WordTestFixture extends CakeTestFixture {
  4.     var $name = 'WordTest';
  5.     var $useTable = 'words';
  6.     var $import = 'Word';
  7.  
  8.     var $records = array(
  9.         array('id'=>1, 'name'=>'Table'),
  10.         array('id'=>2, 'name'=>'boy'),
  11.         array('id'=>3, 'name'=>'girl'),
  12.     );
  13. }
  14.  
  15. ?>

Эта fixture создаст таблицу test_suite_words ($useTable), и возьмёт поля с таблицы, которую использует модель Word ($import). После этого она заполнит их записями из $records.

Обычно лучше вносить свои тестовые данные, но если надо брать данные из существующей таблицы то надо $import определить так

PHP:
  1. var $import = array('model' => 'Word', 'records' => true);

Тогда $records указвать не нужно. Вместо model можно указать table. Также можно указать connection.

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

PHP:
  1. <?
  2.  
  3. class WordTestFixture extends CakeTestFixture {
  4.     var $name = 'WordTest';
  5.     var $useTable = 'words';
  6.     var $fields = array(
  7.         'id' => array('type'=>'integer', 'key'=>'primary'),
  8.         'name' => array('type'=>'string', 'length'=>255, 'null'=>false),
  9.         'created' => 'datetime',
  10.     );
  11.  
  12.     var $records = array(
  13.         array('id'=>1, 'name'=>'Table'),
  14.         array('id'=>2, 'name'=>'boy'),
  15.         array('id'=>3, 'name'=>'girl'),
  16.     );
  17. }
  18.  
  19. ?>

Опции для $fields:

  • type: VARCHAR - string, TEXT - text, INT - integer, FLOAT - float, DATETIME - datetime, TIMESTAMP - timestamp, TIME - time, DATE - date, BLOB - binary
  • key: primary, если поле AUTO_INCREMENT и PRIMARY KEY
  • length: если надо указать длину
  • null: если true, то разрешить NULL, если false, то запретить NULL
  • default: значение по умолчанию

Если не надо задавать дополнительных опций, то можно писать одной строкой, не создавая массив. Так например сделано в предыдущем примере для created.

Созание теста
Загружаем тестируемую модель

PHP:
  1. loadModel('Word');

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

PHP:
  1. class WordTest extends Word {
  2.     var $name = 'WordTest';
  3.     var $useTable = 'words';
  4.     var $useDbConfig = 'test_suite';
  5. }

Дописываем тест и получаем
app/tests/cases/models/word.test.php

PHP:
  1. <?php
  2.  
  3. loadModel('Word');
  4.  
  5. class WordTest extends Word {
  6.     var $name = 'WordTest';
  7.     var $useTable = 'words';
  8.     var $useDbConfig = 'test_suite';
  9. }
  10.  
  11.  
  12. class WordTestCase extends CakeTestCase {
  13.     var $fixtures = array('word_test');
  14.     var $model = null;
  15.  
  16.     function setUp() {
  17.     }
  18.  
  19.     function testCreate() {
  20.         $this->model =& new WordTest();
  21.     }
  22.  
  23.     function testListFlipLow() {
  24.         $result = $this->model->generateListFlipLow();
  25.         $expected = array(
  26.             'table' => 1,
  27.             'boy'   => 2,
  28.             'girl'  => 3,
  29.         );
  30.         $this->assertEqual($result, $expected);
  31.     }
  32.  
  33.     function testAdd() {
  34.         $result = $this->model->add('Test');
  35.         $this->assertEqual($result, 4);
  36.  
  37.         $result = $this->model->generateListFlipLow();
  38.         $expected = array(
  39.             'table' => 1,
  40.             'boy'   => 2,
  41.             'girl'  => 3,
  42.             'test'  => 4,
  43.         );
  44.         $this->assertEqual($result, $expected);
  45.     }
  46.  
  47.     function tearDown() {
  48.     }
  49. }
  50. ?>

Обратите внимание, что setUp и tearDown запускаются до и после каждого теста.

А теперь сама модель из
app/models/word.php

PHP:
  1. <?php
  2. class Word extends AppModel {
  3.  
  4.     var $name = 'Word';
  5.     var $validate = array(
  6.         'id' => VALID_NUMBER,
  7.         'name' => VALID_NOT_EMPTY,
  8.     );
  9.  
  10.     /**
  11.      * Adds a word and returns id
  12.      *
  13.      * @param string $name
  14.      * @return integer If failed to save returns false
  15.      */
  16.     function add($name) {
  17.         $item = array(
  18.             'name'  => $name,
  19.         );
  20.         $this->create();
  21.         if ($this->save($item)) {
  22.             return $this->getInsertId();
  23.         } else {
  24.             return false;
  25.         }
  26.     }
  27.  
  28.     /**
  29.      * Generates list of lowercase names and its ids
  30.      *
  31.      * @return array (a(name=>id), ...)
  32.      */
  33.     function generateListFlipLow(&$model) {
  34.         $items = $this->generateList();
  35.         foreach ($items as $id=>$name) {
  36.             $items[$id] = low($name);
  37.         }
  38.  
  39.         return array_flip($items);
  40.     }
  41. }
  42. ?>

Запускаем server.com/test.php и радуемся тому, что тесты работают :)


Понравилось?

  1. Подпишись через RSS
  2. Расскажи о http://php.southpark.com.ua друзьям.
    Все способы хороши: ICQ, E-mail, свой блог, комментарий в чужом блоге или сообщение на форуме
  3. Добавь статью на news2.ru, Хабрахабр или в закладки

Огромное спасибо!

И не стесняйтесь комментировать - у меня стоит плагин, который убирает rel="nofollow" у людей, которые написали больше 5 комментариев.

RSS feed | Trackback URI

5 комментариев »

2007-10-20 14:51:04

[...] , . [...]

 
Comment by
2007-10-22 11:08:25

, )

 
Comment by
2007-10-22 15:07:42

. - .

 
2007-12-06 17:52:17

[...] я уже писал, я люблю выносить обработку данных в модели. При этом у [...]

 
Comment by Антон Subscribed to comments via email
2008-01-07 15:15:54

Привет, а есть что нибудь по тестированию контроллеров? У них (разработчиков) там что-то непонятно, сделали они что нибудь для этого в 1.2 или нет. Нашел решение от 2006 года, но оно для 1.1, да и хочется что нибудь совсем родного, а не костыли прикручивать..
http://www.thinkingphp.org/2006/08/24/controller-testing-in-cakephp/ - это решение.

 
Имя (required)
E-mail (required - never shown publicly)
URL
Текст комментария
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.