CakePHP: Articles/Tutorials/TestingModels

Тестирование моделей в 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:

Если не надо задавать дополнительных опций, то можно писать одной строкой, не создавая массив. Так например сделано в предыдущем примере для 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($result4);

            $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 и радуемся тому, что тесты работают.