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

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

писал, теперь пришло время моделей.
В поисках подходящего the fucking manual я набрёл на 

Testing Models with CakePHP 1.2 test suite на bakery.cakephp.org.
И всё очень хорошо расписано, но, блин, не работает и всё. Поэтому не читайте то, что там написано. Я 
тщательно изучив исходники методом научного тыка сделал так, чтобы всё работало.
Идеология тестирования моделей в Cake PHP
Модели работают с базой данных и соответственно надо проверить насколько хорошо это получается. Я сторонник fat Models, thin Controllers и стараюсь переносить всю логику работы с данными в модели. Поэтому проверять надо много.
Можно указать отдельное подключение для тестовой базы данных в 
app/config/database.php в переменной 
$test. Обычно подходят те же значения, что и в 
$default. Если вы уберёте 
$test, то 
Cake PHP сам установит её такой же как и 
$default. В любом случае, не волнуйтесь, потому что для тестовых таблиц будет добавлен префикс 
test_suite.
Для создания тестовых таблиц используются 
fixtures. Они создают тестовые таблицы до проведения теста, перед каждым тестом загружают туда тестовые данные, после каждого теста очищают тестовые таблицы и после последнего теста удаляют их.
Создание Fixtures
Как говорится «сначала было слово», вот мы и создадим fixture для модели Word.
app/test/fixtures/word_test_fixture.php
<?
    class WordTestFixture extends CakeTestFixture {
        var $name = 'WordTest';
        var $useTable = 'words';
        var $import = 'Word';
        var $records = array(
            array('id'=>1, 'name'=>'Table'),
            array('id'=>2, 'name'=>'boy'),
            array('id'=>3, 'name'=>'girl'),
        );
    }
?>
Эта 
fixture создаст таблицу 
test_suite_words (
$useTable), и возьмёт поля с таблицы, которую использует модель 
Word (
$import). После этого она заполнит их записями из 
$records.
Обычно лучше вносить свои тестовые данные, но если надо брать данные из существующей таблицы то надо 
$import определить так
var $import = array('model' => 'Word', 'records' => true);
Тогда 
$records указвать не нужно. Вместо 
model можно указать 
table. Также можно указать 
connection.
При желании можно было бы не брать поля из существующей таблицы, а вручную указать их. Это может быть удобно, если таблица ещё не создана. Вот этот же пример с указаниеми полей.
<?
    class WordTestFixture extends CakeTestFixture {
        var $name = 'WordTest';
        var $useTable = 'words';
        var $fields = array(
            'id' => array('type'=>'integer', 'key'=>'primary'),
            'name' => array('type'=>'string', 'length'=>255, 'null'=>false),
            'created' => 'datetime',
        );
        var $records = array(
            array('id'=>1, 'name'=>'Table'),
            array('id'=>2, 'name'=>'boy'),
            array('id'=>3, 'name'=>'girl'),
        );
    }
?>
Опции для 
$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.
Созание теста
Загружаем тестируемую модель
loadModel('Word');
Для того, чтобы использовать 
fixtures, надо создать потомка тестируемой модели и уже его тестировать.
class WordTest extends Word {
    var $name = 'WordTest';
    var $useTable = 'words';
    var $useDbConfig = 'test_suite';
}
Дописываем тест и получаем
app/tests/cases/models/word.test.php
<?php
    loadModel('Word');
    class WordTest extends Word {
        var $name = 'WordTest';
        var $useTable = 'words';
        var $useDbConfig = 'test_suite';
    }
    class WordTestCase extends CakeTestCase {
        var $fixtures = array('word_test');
        var $model = null;
        function setUp() { }
        function testCreate() {
            $this->model =& new WordTest();
        }
        function testListFlipLow() {
            $result = $this->model->generateListFlipLow();
            $expected = array(
                'table' => 1,
                'boy'   => 2,
                'girl'  => 3,
            );
            $this->assertEqual($result, $expected);
        }
        function testAdd() {
            $result = $this->model->add('Test');
            $this->assertEqual($result, 4);
            $result = $this->model->generateListFlipLow();
            $expected = array(
                'table' => 1,
                'boy'   => 2,
                'girl'  => 3,
                'test'  => 4,
            );
            $this->assertEqual($result, $expected);
        }
        function tearDown() { }
    }
?>
Обратите внимание, что 
setUp и 
tearDown запускаются до и после каждого теста.
А теперь сама модель из
app/models/word.php
<?php
    class Word extends AppModel {
        var $name = 'Word';
        var $validate = array(
            'id' => VALID_NUMBER,
            'name' => VALID_NOT_EMPTY,
        );
        /**
         * Adds a word and returns id
         *
         * @param string $name
         * @return integer If failed to save returns false
         */
        function add($name) {
            $item = array(
                'name'  => $name,
            );
            $this->create();
            if ($this->save($item)) {
                return $this->getInsertId();
            } else {
                return false;
            }
        }
        /**
         * Generates list of lowercase names and its ids
         *
         * @return array (a(name=>id), ...)
         */
        function generateListFlipLow(&$model) {
            $items = $this->generateList();
            foreach ($items as $id=>$name) {
                $items[$id] = low($name);
            }
            return array_flip($items);
        }
    }
?>
Запускаем 

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