Zaczynamy Test Driven Development

Nie mam zamiaru opisywać tutaj zasad TDD. Po prostu zapragnąłem się tego uczyć i tutaj będę umieszczał poszczególne “lekcje”.

Pomysł jest taki, żeby zbudować przy pomocy TDD i CakePHP prosty engine forum. Założyłem nowy projekt w eclipse, wgrałem cake’a i bibliotekę simpletest do katalogu vendors. Założyłem dwie bazy private_bulletin_board i private_bulletin_board_test (ten przedrostek private służy tylko do uporządkowania moich baz w phpMyAdminie).

Zdefiniowałem configuracje baz:

//app/config/database.php
class DATABASE_CONFIG {

var $default = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'netarch',
'password' => 'noth1nG',
'database' => 'private_bulletin_board',
'prefix' => '',
);

var $test = array(
'driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'netarch',
'password' => 'noth1nG',
'database' => 'private_bulletin_board_test',
'prefix' => '',
);
}

Pierwszy krok, to stworzenie testu. Pomyślmy co mógłby testować? Powiedzmy, że każde forum składa się z tematów. Zaczynamy test:

//tests/cases/models/topic.test.php
App::import('model', 'Topic');

class TopicTestCase extends CakeTestCase {
var $fixtures = array();

function start() {
parent::start();
$this->Topic = ClassRegistry::init('Topic');
}

}

Teoretycznie po uruchomieniu testu powinniśmy dostać informację o nieistniejącej klasie Topic. Jednak niespodzianka, brak klasy nie jest problemem, za to dostajemy info o braku tabeli topics dla klasy Topic:
missing-db

Widocznie ClassRegistry nie potrzebuje klasy, aby zainicjować jej obiekt ;). Poprawmy metodę start:

function start() {
parent::start();
$this->Topic = new Topic();
$this->Topic = ClassRegistry::init('Topic');
}

Jest nieco lepiej:
class-not-found

Mamy test, który nie przechodzi. Co paradoksalnie oznacza, że wszystko jest w porządku. Krok drugi to stworzenie testowanej klasy:

/models/topic.php
class Topic extends AppModel {
var $name = "Topic";
}

Testujemy:
missing-db
Znów missing table, ale przynajmniej się tego spodziewaliśmy. Potrzebujemy testowych danych, stworzymy zaten fixture:

//test/fixtures/topc_fixture.php
class TopicFixture extends CakeTestFixture {
var $name = "Topic";
var $fields = array(
'id' => array('type'=>'integer', 'key'=>'primary'),
'title' => array('type'=>'string', 'length' => 150, 'null'=> false)
);

}

Niestety w międzyczasie należało wywalić z testu zapis $this->Topic = new Topic(). Nie pytajcie dlaczego ;)
Należy też w tym pliku dodać

var $fixtures = array('app.topic');

Testujemy:
success1

Pierwszy sukces. Jeden cykl zakończony. Żeby jednak nie było tak jałowo – będziemy kontynuować, aż model zacznie robić coś pożytecznego. Poprawmy test. Żeby to zrobić trzeba się zastanowić co od takiej klasy możemy oczekiwać? Wydaje się, że na pewno będziemy potrzebowali listę ostatnich aktualnych postów. Zatem zacznijmy znów od testu. Dodajemy metodę:

//topic.test.php
function testGetLastTopics() {
$topics = $this->Topic->getLastTopic();
}

i wykonujemy test:
error1
Test oblany. Zauważ, że nie dostaliśmy błędu o nieistniejącej metodzie. To przez “magiczne” funkcje modeli, które dla nieistniejących metod generują zapytania sql (dlatego możesz zrobić getByJakiesPoleWBazie()). Teraz trzymając się zasad należy dodać minimalną ilość zmian w kodzie, aby test zaczął przechodzić. W tym wypadku wystarczy zdefiniować metodę w modelu:

function getLastTopics() {}

Test:
pass1
Drugi cykl za nami. Teraz chciałbym, żeby metoda zwracała mi tablicę z tematami z forum. Modyfikuję test:

function testGetLastTopics() {
$topics = $this->Topic->getLastTopics();
$this->assertTrue(is_array($topics));
}

Test:
fail11
Oczywiście wszystko się zgadza. Poprawiamy metodę pamiętając o tym, że wykonujemy minimalną ilość poprawek, aby kod zacząć przechodzić testy.

class Topic extends AppModel {
var $name = "Topic";
function getLastTopics() {
return array();
}
}

test:
pass2
Jeśli zaczyna się robić nieco nudnawo, to podnieśmy poprzeczkę. Sprawdzimy, czy model faktycznie zwróci nam dane. Założymy, że topic to nazwa i id:

function testGetLastTopics() {
$topics = $this->Topic->getLastTopics();
$this->assertTrue(is_array($topics));

$expected = array( array( 'Topic' => array('id'=>1, 'title'=>'Pierwszy post') ) );
$this->assertEqual($topics, $expected);
}

I testujemy (oczywiście spodziewając się błędu):
fail2
Poprawiamy metodę w modelu:

function getLastTopics() {
return $this->find('all');
}

Nadal mamy błąd dlatego, że brakuje nam danych testowych. Musimy wrócić do fixtures na moment i dodać tam następujący kod:

var $records = array(
array('id'=> 1, 'title' => 'Pierwszy post'),
);

pass3

To na razie tyle. Niedługo może bardziej skomplikowane rzeczy ;)
część druga…

Share Button