Piloter le chargement d’une application avec Zend_Test

Contexte

On m’a demandé de convertir une application existante écrite en C avec une BDD Access en une application PHP+Oracle. La structure de la base de données a été entièrement modifié pour accepter de nouvelles évolutions. Il y avait donc une possibilité toute simple d’injecter les anciennes données issues d’Access dans la nouvelle base Oracle par l’intermédiaire d’un script SQL. J’ai cependant opté pour un pilotage de la nouvelle application avec Zend_Test chaque test correspondant à une donnée issue d’Access à entrer dans Oracle.

Note

Ce qui suit ne correspond pas aux tests de mon application (qui sont d’ailleurs eux aussi réalisés avec Zend_Test).

Initialisation

L’application est une supervision de production d’éléments. Chaque élément suit une gamme de fabrication. Les étapes de fabrication sont sauvegardées (qui à fait quoi et quand). Cette application est de type CRUD. Elle est composée de formulaires construits avec Zend_Form. Le script va donc consisté en l’appel des pages d’ajout (ou plus rarement de modification) avec les bons paramètres. Chaque ajout ou modification est assigné à un utilisateur. L’authentification est donc nécessaire pour chaque action puisque l’utilisateur peut changer à chaque étape.

La structure est similaire à la structure préconisée sur le site du Zend Framework.

Le bootstrap de l’application

Il doit permettre l’initialisation de l’application indépendamment de la distribution de la requête. 2 fichiers sont dans mon cas utilisés :

// index.php dans /appli/html (seul dossier accessible par Apache)
require_once ('../library/Mp/Site.php');
echo Mp_Site::run('MONAPPLI', 'PROD');
// Site.php dans /appli/library/Mp
class Mp_Site
{
    ... // mes variables (toutes statiques)

    public static function run ($app_name, $mode = 'PROD')
    {
        self::prepare($app_name, $mode);
        self::execute();
    }

    public static function prepare ($app_name, $mode = 'TEST')
    {
        self::setupEnvironment($mode);
        self::setupRegistry();
        self::setupLogger();
        self::setupConfiguration();
        self::setupDatabase();
        self::setupSession();
        self::setupFrontController();
        self::setupLayout();
        self::setupView();
        self::setupCache();
        self::setupApplication();
        self::$_already_loaded = true;
    }

    public static function execute ()
    {
        $request = new Zend_Controller_Request_Http();
        $response = new Zend_Controller_Response_Http();
        $response->append('body', '');
        try {
            self::$front->dispatch($request, $response);
        } catch (Exception $e) {
            exit($e->getMessage());
        }
    }

    ... // autres méthodes (elles aussi statiques)
}

Dans le cas de l’appel de l’application en mode Web, on passe par index.php qui prépare l’appli et l’exécute. Dans notre cas, nous allons seulement faire appel à prepare().

Script global (à l’image d’une suite PHPUnit)

Chaque table à remplir est dans un fichier séparé.

if (! defined('PHPUnit_MAIN_METHOD')) {
    define('PHPUnit_MAIN_METHOD', 'AllChargementMonappli::main');
}
class AllChargementMonappli
{

    public static function main ()
    {
        PHPUnit_TextUI_TestRunner::run(self::suite());
    }

    public static function suite ()
    {
        $suite = new PHPUnit_Framework_TestSuite('ChargementMonappli');
        $suite->addTestSuite('ChargementMonappli_TypeFournisMatprem');
        $suite->addTestSuite('ChargementMonappli_Table2');
        $suite->addTestSuite('ChargementMonappli_Table3');
        // ...
        $suite->addTestSuite('ChargementMonappli_TableN');
        return $suite;
    }
}
if (PHPUnit_MAIN_METHOD == 'AllChargementMonappli::main') {
    AllChargementMonappli::main();
}

Classe commune pour toutes les tables

  • La fonction setUp() est appelée à chaque démarrage de test. Elle permet de réinitialiser l’environnement ZF (requête, réponse, dispatcheur…). Il faut lui fournir les infos permettant l’amorçage de votre application (dans mon cas il faut définir $this->bootstrap comme un callback valide).
  • La fonction appBootstrap() est nécessaire car mon bootstrap est une fonction statique.
  • La fonction connect() permet de connecter un utilisateur en mode ‘TEST’ le password n’est pas vérifié.
  • La fonction _loadPage() charge une page suivant les paramètres fournis, comme dans notre cas il s’agit de pilotages de formulaires (ajout ou modif) : la méthode est toujours de type POST.
  • La fonction _verifError() vérifie les erreurs retournées par Zend_Form (normalement aucune !)
  • Les fonctions _searchAjax() et _readAjax() permettent d’aller lire une information au format json (je n’utilise pas dans ce cas le ContextSwitch, mon application ne fonctionne pas sans Javascript car il s’agit d’un Intranet donc je connais l’ensemble du parc). Toutes les requêtes pures Ajax sont donc tout simplement stockées dans un contrôleur AjaxController.
class ChargementMonappli_Commun
      extends Zend_Test_PHPUnit_ControllerTestCase
{

    public function setUp ()
    {
        $this->bootstrap = array($this , 'appBootstrap');
        parent::setUp();
    }

    public function appBootstrap ()
    {
        Mp_Site::prepare('MONAPPLI', 'TEST');
    }

    public function connect ($user, $password = null)
    {
        if ($password == null) {
            $password = $user;
        }
        $request = $this->getRequest();
        $request->setMethod('POST')->setPost(array('user' => $user,
                                                   'password' => $password,
                                                   'page_demandee' => '/toto'));
        $this->dispatch('/default/login/authentifie');
        $this->assertRedirectTo('/toto');
        $this->resetResponse();
        $request->setMethod('GET')->clearPost();
    }

    protected function _loadPage ($page, $param, $ajax = false)
    {
        if ($ajax) {
            $param = $this->_searchAjax($param);
        }
        $this->getRequest()->setMethod('POST');
        $this->getRequest()->setPost($param);
        $this->dispatch($page);
        $this->_verifError();
        $this->resetResponse();
        $this->getRequest()->clearPost();
    }

    protected function _verifError ()
    {
        $retour = null;
        $text = $this->getResponse()->getBody();
        if (preg_match("`<ul class=\"errors\">(.*)</ul>`", $text, $retour)) {
            $this->assertTrue(0, $retour[1]);
        }
        $retour = null;
        if (preg_match("`<div id=\"texte\">L'élément n'a pas été ajouté(.*)<br/>`", $text, $retour)) {
            $this->assertTrue(0, $retour[1]);
        }
    }
    protected function _searchAjax ($table)
    {
        foreach ($table as &$v) {
            if (is_string($v) && substr($v, 0, 26) == '/paimbinfo/outillage/ajax/') {
                $v = $this->_readAjax($v);
            }
        }
        return $table;
    }
    protected function _readAjax ($address)
    {
        $this->dispatch($address);
        $response = Zend_Json::decode($this->getResponse()->getBody());
        $this->resetResponse();
        $this->getRequest()->setMethod('GET')->clearPost();
        return $response[1];
    }
}

 Chargement de la table 1

Il s’agit de la table des fournisseurs de matière première. Chaque testTypeFournisMatpremX() correspond à une entrée dans la base et renverra un ‘.’, un ‘F’ ou un ‘E’ comme les tests classiques de PHUnit m’avertissant ainsi des problèmes rencontrés à l’injection des données. Le fichier ci-dessous est entièrement créé via un script VBA.

class ChargementMonappli_TypeFournisMatprem extends ChargementMonappli_Commun
{
    public function testTypeFournisMatprem1 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 1', 'n_type_outillage' => 0, 's_adresse_fm' => 'USA'));
    }
    public function testTypeFournisMatprem2 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 2', 'n_type_outillage' => 0, 'n_telephone_fm' => 33240000000, 'n_fax_fm' => 33240000001, 's_adresse_fm' => '44300 NANTES'));
    }
    public function testTypeFournisMatprem3 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 3', 'n_type_outillage' => 0, 'n_telephone_fm' => 33240000002, 'n_fax_fm' => 33240000003, 's_adresse_fm' => '44600 SAINT-NAZAIRE'));
    }
    public function testTypeFournisMatprem4 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 4', 'n_type_outillage' => 0));
    }
    public function testTypeFournisMatprem5 ()
    {
        $this->connect('toto', 'password');
        $this->_loadPage('/monappli/base/ajout/table/type_fournis_matprem',
                         array('s_type_fournis_matprem' => 'FOURNISSEUR 5', 'n_type_outillage' => 0));
    }
}

Pour terminer

Il suffit de lancer en ligne de commande : phpunit --verbose AllChargementMonappli > export.txt pour récupérer l’ensemble du résultat dans un fichier ‘export.txt’. Zend_Test permet donc bien plus que le simple test unitaire.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>