Test Driven Development de la classe Product

Test des accesseurs

On fait hériter ProductTest de UnitTestCase et on inclut le code commun à toutes les classes de test. On définit aussi les méthodes setUp() et tearDown() et les méthodes de test que l'on fait pour l'instant échouer en y insérant une seule instruction : $this->fail('TODO');

Sauvegarder le source minimal suivant:

<?php
if ( is_dir('../tests/simpletest') ) {
  require_once (dirname(__FILE__) . '/../tests/simpletest/unit_tester.php');
  require_once (dirname(__FILE__) . '/../tests/simpletest/reporter.php');
}
else {
  require_once ('simpletest/unit_tester.php');
  require_once ('simpletest/reporter.php');
}

require_once ('Product.class.php');
class ProductTest extends UnitTestCase {
    private $product; // fixture
    function setUp() {
        $this->product = new Product('Paye', 'paye@cnam.fr');
    }
    function tearDown() {
        $this->product = null;
    }
    function testGetMail() {
        $this->fail('TODO');
    }
    function testSetMail() {
        $this->fail('TODO');
    }   
    // ...   
}
if (!defined('RUNNER')) {
    define('RUNNER', true);
    $test =  new ProductTest();
    $test->run(new HtmlReporter());
}

?>

L'exécution du test échoue :

Warning: require_once(Product.class.php) [function.require-once]: failed to open stream: No such file or directory

Créons le fichier Product.class.php :

File->New->PHP File

Saisir Product.class.php pour nom de fichier. Cliquer sur le bouton "Finish".

L'exécution du test échoue encore :

Fatal error: Class 'Product' not found in d:\home\web\tpdsi\tp6\producttest.class.php on line 9

Définissons la classe Product minimale dans  Product.class.php.

Sauvegarder le source minimal suivant:

<?php
class Product {
    private $id;
    private $name;
    private $mail;

    function Product($name, $mail, $id = -1) {
        $this->id = $id;
        $this->name = $name;
        $this->mail = $mail;
    }
}
?>

Relancer le test. 2 échecs :

Fail: testGetMail -> TODO at [D:\home\web\tpdsi\tp6\ProductTest.class.php line 22]
Fail: testSetMail -> TODO at [D:\home\web\tpdsi\tp6\ProductTest.class.php line 25]

1/1 test cases complete: 0 passes, 2 fails and 0 exceptions.

Redéfinissons les méthodes testGetMail() et testSetMail() de la classe de test et relançons le test :

    function testGetMail() {
        $this->assertEqual('paye@cnam.fr', $this->product->getMail());
    }
    function testSetMail() {
        $newMail = 'paye2@cnam.fr';
        $this->product->setMail($newMail);
        $this->assertEqual($newMail, $this->product->getMail());
    }   

Relancer le test. échec :

Fatal error: Call to undefined function: getmail() in d:\home\web\tpdsi\tp6\producttest.class.php on line 25

Dans une approche TDD (développement orientée par les tests), on n'écrit du code que lorsqu'un test échoue!

Contentons-nous de simplement déclarer les 2 premières méthodes getMail() et setMail() de la classe Product.

    function getMail() {
    }
    function setMail($mail) {
    }

Relancer le test. 2 échecs :

Fail: testGetMail -> Equal expectation fails at character 0 with [paye@cnam.fr] and [] at ...
Fail: testSetMail -> Equal expectation fails at character 0 with [paye2@cnam.fr] and [] at ...

1/1 test cases complete: 0 passes, 2 fails and 0 exceptions.

Définissons donc les 2  méthodes getMail() et setMail() de la classe Product.

    function getMail() {
        return $this->mail;
    }
    function setMail($mail) {
        $this->mail = $mail;
    }

Relancer le test. Ca marche! 2 assertions (Passes: 2) vérifiées :

1/1 test cases complete: 2 passes, 0 fails and 0 exceptions.

(Voir les sources complets après cette étape : ProductTest.class.php et Product.class.php.)

Reste à définir les méthodes de test de la vérification des adresses email.!

Tests de la méthode checkMail

Définissons le code minimal pour tester les longueurs de l'adresse mail en utilisant des assertions pour tester un mail trop court ou trop long :

// ProductTest.class.php
    function testCheckMailWithValidLength() {
        $b = $this->product->checkMail();
        $this->assertEqual(true, $b, 'Ce mail est correct!');
    }
    function testCheckTooShortMail() {
        $b = $this->product->checkMail('x@x');
        $this->assertEqual(false, $b, 'Ce mail est trop court!');
    }
    function testCheckTooLongMail() {
        $mail = 'engagelejeuquejelegagne@duel-de-mots.fr';
        $b = $this->product->checkMail($mail);
        $this->assertFalse($b, "$mail est une adresse trop longue => échec");
    }
    //

// Product.class.php
    function checkMail($mail = '') {
        if ( $mail == '' )
            $mail = $this->mail;
        // 1. tests sur longueur
        // TODO
        // ...
        return true;
    }

Relancer le test. 3 échecs. 

Fail: testCheckTooShortMail -> Ce mail est trop court! at ...
Fail: testCheckTooLongMail -> engagelejeuquejelegagne@duel-de-mots.fr est une adresse trop longue => échec at ...
Fail: testCheckMailWithoutArrobas -> TODO at ...

1/1 test cases complete: 3 passes, 3 fails and 0 exceptions.

Implémentons une à une les nouvelles méthodes de test.

Contrôle de la longueur du mail:

// Product.class.php
    function checkMail($mail = '') {
        if ( $mail == '' )
            $mail = $this->mail;
        // 1. tests sur longueur
        $l = strlen($mail);
        if ( $l < 10 )
            return false;
        if ( $l > 35 )
            return false;
        // ... TODO
        return true;
    }

Relancer le test. 2 échecs. 

Fail: testCheckMailWithoutArrobas -> TODO at [D:\home\web\tpdsi\tp6\ProductTest.class.php line 45]

1/1 test cases complete: 5 passes, 1 fails and 0 exceptions.

Autres tests de la méthode checkMail

Poursuivre itérativement l'implémentation de la méthode checkMail de Product.class.php en faisant (échouer puis) passer successivement les tests partiellement définis dans ProductTest.class.php.

A vous de jouer!