Tester comme un patron à Laravel les modèles

Si vous souhaitez savoir pourquoi les tests sont bénéfiques, cet article ne vous convient pas. Au cours de ce didacticiel, je suppose que vous en connaissez déjà les avantages et que vous souhaitez apprendre à écrire et à organiser au mieux vos tests dans Laravel 4.

La version 4 de Laravel offre de sérieuses améliorations en matière de test par rapport à sa version précédente. Cet article est le premier d'une série qui explique comment écrire des tests pour les applications Laravel 4. Nous allons commencer la série en discutant des tests de modèles.


Installer

Base de données en mémoire

Sauf si vous exécutez des requêtes brutes sur votre base de données, Laravel permet à votre application de rester agnostique à la base de données. Avec un simple changement de pilote, votre application peut désormais fonctionner avec d'autres SGBD (MySQL, PostgreSQL, SQLite, etc.). Parmi les options par défaut, SQLite offre une fonctionnalité particulière, mais très utile: les bases de données en mémoire..

Avec Sqlite, nous pouvons définir la connexion à la base de données sur :Mémoire:, ce qui accélérera considérablement nos tests, car la base de données n’existe pas sur le disque dur. De plus, la base de données de production / développement ne sera jamais renseignée avec les données de test restantes, car la connexion, :Mémoire:, commence toujours par une base de données vide.

En bref: une base de données en mémoire permet des tests rapides et propres.

Dans le app / config / testing répertoire, créez un nouveau fichier, nommé database.php, et remplissez-le avec le contenu suivant:

// app / config / testing / database.php  'sqlite', 'connections' => array ('sqlite' => array ('driver' => 'sqlite', 'database' => ': memory:', 'prefix' => "),));

Le fait que database.php est placé dans la configuration essai répertoire signifie que ces paramètres ne seront utilisés que dans un environnement de test (que Laravel définit automatiquement). En tant que tel, lorsque votre application est accédée normalement, la base de données en mémoire ne sera pas utilisée..

Avant de lancer des tests

Comme la base de données en mémoire est toujours vide lorsqu’une connexion est établie, il est important de: émigrer la base de données avant chaque test. Pour ce faire, ouvrez app / tests / TestCase.php et ajoutez la méthode suivante à la fin de la classe:

/ ** * Migre la base de données et configure le mailer sur 'feindre'. * Les tests seront exécutés rapidement. * * / fonction privée prepareForTests () Artisan :: call ('migrate'); Mail :: pretend (true); 

Noter la installer() Cette méthode est exécutée par PHPUnit avant chaque test..

Cette méthode préparera la base de données et modifiera le statut de Laravel. Mailer classe à faire semblant. De cette façon, le Mailer n'enverra aucun email lors de l'exécution des tests. Au lieu de cela, il enregistrera les messages "envoyés".

Finaliser app / tests / TestCase.php, appel prepareForTests () au sein de PHPUnit installer() méthode, qui sera exécutée avant chaque test.

N'oublie pas le parent :: setUp (), comme nous remplaçons la méthode de la classe parente.

/ ** * Préparation par défaut pour chaque test * * / fonction publique setUp () parent :: setUp (); // N'oublie pas ça! $ this-> prepareForTests (); 

À ce point, app / tests / TestCase.php devrait ressembler au code suivant. Rappelez-vous que créer une application est créé automatiquement par Laravel. Tu n'as pas besoin de t'inquiéter.

// app / tests / TestCase.php prepareForTests ();  / ** * Crée l'application. * * @return Symfony \ Component \ HttpKernel \ HttpKernelInterface * / fonction publique createApplication () $ unitTesting = true; $ testEnvironment = 'testing'; return require __DIR __. '/… /… /start.php';  / ** * Migre la base de données et configure le mailer sur 'feindre'. * Les tests seront exécutés rapidement. * / fonction privée prepareForTests () Artisan :: call ('migrate'); Mail :: pretend (true); 

Maintenant, pour écrire nos tests, il suffit d’étendre Cas de test, et la base de données sera initialisée et migrée avant chaque test.


Les tests

Il est correct de dire que, dans cet article, nous ne suivrons pas les TDD processus. Le problème est ici didactique, dans le but de montrer comment les tests peuvent être écrits. Pour cette raison, j'ai choisi de révéler d'abord les modèles en question, puis leurs tests associés. Je pense que c'est une meilleure façon d'illustrer ce tutoriel..

Le contexte de cette application de démonstration est un simple blog / CMS, contenant des utilisateurs (authentification), des publications et des pages statiques (affichées dans le menu)..

Modèle de poste

Veuillez noter que le modèle étend la classe, Ardent, plutôt que Eloquent. Ardent est un package qui facilite la validation lors de l’enregistrement du modèle (voir le $ règles propriété).

Ensuite, nous avons le public statique $ factory tableau, qui exploite le package FactoryMuff, pour aider à la création d’objets lors des tests.

Tous les deux Ardentx et FactoryMuff sont disponibles via Packagist et Composer.

Dans notre Poster modèle, nous avons une relation avec le Utilisateur modèle, à travers la magie auteur méthode.

Enfin, nous avons une méthode simple qui renvoie la date, au format "jour mois année".

// app / models / Post.php  'required', // Post Tittle 'slug' => 'required | alpha_dash', // Post Url 'content' => 'required', // Post content (Markdown) 'author_id' => 'required | numeric', // Identifiant de l'auteur); / ** * Tableau utilisé par FactoryMuff pour créer des objets test * / public static $ factory = array ('title' => 'string', 'slug' => 'string', 'content' => 'text', 'author_id '=>' factory | User ', // sera l'identifiant d'un utilisateur existant.); / ** * appartient à l'utilisateur * / public function author () return $ this-> appartientTo ('User', 'author_id');  / ** * Obtenir la date de publication formatée * * @ chaîne de retour * / fonction publique postedAt () $ date_obj = $ this-> created_at; if (is_string ($ this-> created_at)) $ date_obj = DateTime :: createFromFormat ('Y-m-d H: i: s', $ date_obj); return $ date_obj-> format ('d / m / Y'); 

Post-tests

Pour garder les choses organisées, j'ai placé la classe avec le Poster tests de modèle dans app / tests / models / PostTest.php. Nous allons passer en revue tous les tests, une section à la fois.

// app / tests / models / PostTest.php  

Nous étendons la Cas de test class, qui est une exigence pour les tests PHPUnit à Laravel. De plus, n'oubliez pas notre préparer des tests méthode qui sera exécutée avant chaque test.

 fonction publique test_relation_with_author () // Instancie, remplit de valeurs, enregistre et renvoie $ post = FactoryMuff :: create ('Post'); // Grâce à FactoryMuff, ce $ post a un auteur $ this-> assertEquals ($ post-> author_id, $ post-> author-> id); 

Ce test est "optionnel". Nous testons que la relation "Poster appartient à Utilisateur". Le but ici est principalement de démontrer les fonctionnalités de FactoryMuff.

Une fois la Poster la classe a la $ usine tableau statique contenant 'author_id' => 'factory | User' (notez le code source du modèle, illustré ci-dessus), le FactoryMuff instancie un nouveau Utilisateur remplit ses attributs, enregistre dans la base de données et renvoie finalement son identifiant au author_id attribuer dans le Poster.

Pour que cela soit possible, le Utilisateur le modèle doit avoir un $ usine tableau décrivant aussi ses champs.

Notez comment vous pouvez accéder à la Utilisateur relation à travers $ post-> auteur. Par exemple, nous pouvons accéder à la $ post-> auteur-> nom d'utilisateur, ou tout autre attribut utilisateur existant.

Le package FactoryMuff permet l'instanciation rapide d'objets cohérents à des fins de test, tout en respectant et en instanciant les relations nécessaires. Dans ce cas, lorsque nous créons un Poster avec FactoryMuff :: create ('Post') la Utilisateur sera également préparé et mis à disposition.

 fonction publique test_posted_at () // Instancie, remplit de valeurs, enregistre et renvoie $ post = FactoryMuff :: create ('Post'); // Expression régulière représentant le motif d / m / Y $ attendu = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Vrai si preg_match trouve le motif $ correspond = (preg_match ($ attendu, $ post-> postedAt ()))? vrai faux; $ this-> assertTrue ($ correspondances); 

Pour finir, nous déterminons si la chaîne retournée par le postedAt () La méthode suit le format "jour / mois / année". Pour cette vérification, une expression régulière est utilisée pour tester si le motif \ d 2 \ / \ d 2 \ / \ d 4 ("2 chiffres" + "barre" + "2 chiffres" + "barre" + "4 chiffres") est trouvé.

Alternativement, nous pourrions utiliser l'assertRegExp de PHPUnit.

À ce stade, le app / tests / models / PostTest.php Le fichier est comme suit:

// app / tests / models / PostTest.php assertEquals ($ post-> author_id, $ post-> author-> id);  public function test_posted_at () // Instanciez, remplissez de valeurs, enregistrez et renvoyez $ post = FactoryMuff :: create ('Post'); // Expression régulière représentant le motif d / m / Y $ attendu = '/ \ d 2 \ / \ d 2 \ / \ d 4 /'; // Vrai si preg_match trouve le motif $ correspond = (preg_match ($ attendu, $ post-> postedAt ()))? vrai faux; $ this-> assertTrue ($ correspondances); 

PS: J'ai choisi de ne pas écrire le nom des tests dans CamelCase à des fins de lisibilité. PSR-1 me pardonne, mais testRelationWithAuthor n'est pas aussi lisible que je préférerais personnellement. Vous êtes libre d'utiliser le style que vous préférez, bien sûr.

Modèle de page

Notre CMS a besoin d'un modèle pour représenter les pages statiques. Ce modèle est implémenté comme suit:

 'required', // Titre de la page 'slug' => 'required | alpha_dash', // Slug (url) 'content' => 'required', // Content (markdown) 'author_id' => 'required | numeric' // Identifiant de l'auteur); / ** * Tableau utilisé par FactoryMuff * / public static $ factory = array ('title' => 'chaîne', 'slug' => 'chaîne', 'content' => 'text', 'author_id' => ' factory | User ', // Sera l'id d'un utilisateur existant.); / ** * appartient à l'utilisateur * / public function author () return $ this-> appartientTo ('User', 'author_id');  / ** * Rend le menu en utilisant cache * * @return string Html pour les liens de page. * / fonction statique publique renderMenu () $ pages = Cache :: RememberForever ('pages_for_menu', function () return Page :: select (array ('title', 'slug')) - - get () -> toArray ();); $ result = "; foreach ($ pages en tant que $ page) $ result. = HTML :: action ('PagesController @ show', $ page ['titre'], ['slug' => $ page ['slug'] ]). ' | '; return $ result; / ** * Oublie le cache lors de l'enregistrement * / fonction publique afterSave ($ success) if ($ succès) Cache :: forget (' pages_for_menu '); / ** * Oublie le cache quand supprimé * / fonction publique delete () parent :: delete (); Cache :: oublier ('pages_for_menu');

Nous pouvons observer que la méthode statique, renderMenu (), affiche un certain nombre de liens pour toutes les pages existantes. Cette valeur est enregistrée dans la clé de cache, 'pages_for_menu'. Ainsi, à l’avenir, les appels à renderMenu (), il n'y aura pas besoin de frapper la vraie base de données. Cela peut apporter des améliorations significatives aux performances de notre application..

Cependant, si un Page est enregistré ou supprimé (afterSave () et effacer() méthodes), la valeur du cache sera effacée, entraînant la renderMenu () pour refléter le nouvel état de la base de données. Donc, si le nom d’une page est modifié, ou si elle est supprimée, le clé 'pages_for_menu' est effacé de la mémoire cache. (Cache :: forget ('pages_for_menu');)

NOTE: La méthode, afterSave (), est disponible via le package Ardent. Sinon, il serait nécessaire de mettre en œuvre le enregistrer() méthode pour nettoyer le cache et appeler parent :: save ();

Tests de page

Dans: app / tests / models / PageTest.php, nous écrirons les tests suivants:

assertEquals ($ page-> author_id, $ page-> author-> id); 

Encore une fois, nous avons un test "optionnel" pour confirmer la relation. Comme les relations sont la responsabilité de Illuminate \ Database \ Eloquent, qui est déjà couvert par les propres tests de Laravel, nous n'avons pas besoin d'écrire un autre test pour confirmer que ce code fonctionne comme prévu.

 fonction publique test_render_menu () $ pages = array (); pour ($ i = 0; $ i < 4; $i++)  $pages[] = FactoryMuff::create('Page');  $result = Page::renderMenu(); foreach ($pages as $page)  // Check if each page slug(url) is present in the menu rendered. $this->assertGreaterThan (0, strpos ($ result, $ page-> slug));  // Vérifie si le cache a été écrit $ this-> assertNotNull (Cache :: get ('pages_for_menu')); 

C’est l’un des tests les plus importants pour le Page modèle. Tout d'abord, quatre pages sont créées dans le pour boucle. Suite à cela, le résultat de la renderMenu () L'appel est stocké dans le $ résultat variable. Cette variable doit contenir une chaîne HTML contenant des liens vers les pages existantes..

le pour chaque boucle vérifie si le slug (url) de chaque page est présent dans $ résultat. Cela suffit, car le format exact du code HTML ne correspond pas à nos besoins..

Enfin, nous déterminons si la clé de cache, pages_for_menu, a quelque chose stocké. En d'autres termes, le renderMenu () appel effectivement sauvé une certaine valeur dans le cache?

 fonction publique test_clear_cache_after_save () // Une valeur de test est enregistrée dans le cache Cache :: put ('pages_for_menu', 'avalue', 5); // Ceci devrait nettoyer la valeur dans le cache $ page = FactoryMuff :: create ('Page'); $ this-> assertNull (Cache :: get ('pages_for_menu')); 

Ce test vise à vérifier si, lors de la sauvegarde d’une nouvelle Page, la clé de cache 'pages_for_menu' est vidé. le FactoryMuff :: create ('Page'); déclenche finalement la enregistrer() méthode, de sorte que cela devrait suffire pour la clé, 'pages_for_menu', être effacé.

 fonction publique test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Une valeur de test est enregistrée dans le cache Cache :: put ('pages_for_menu', 'valeur', 5); // Ceci devrait nettoyer la valeur dans le cache $ page-> delete (); $ this-> assertNull (Cache :: get ('pages_for_menu')); 

Semblable au test précédent, celui-ci détermine si la clé 'pages_for_menu' est vidé correctement après la suppression d'un Page.

Votre PageTest.php devrait ressembler à ceci:

assertEquals ($ page-> author_id, $ page-> author-> id);  fonction publique test_render_menu () $ pages = array (); pour ($ i = 0; $ i < 4; $i++)  $pages[] = FactoryMuff::create('Page');  $result = Page::renderMenu(); foreach ($pages as $page)  // Check if each page slug(url) is present in the menu rendered. $this->assertGreaterThan (0, strpos ($ result, $ page-> slug));  // Vérifie si le cache a été écrit $ this-> assertNotNull (Cache :: get ('pages_for_menu'));  fonction publique test_clear_cache_after_save () // Une valeur de test est enregistrée dans le cache Cache :: put ('pages_for_menu', 'avalue', 5); // Ceci devrait nettoyer la valeur dans le cache $ page = FactoryMuff :: create ('Page'); $ this-> assertNull (Cache :: get ('pages_for_menu'));  fonction publique test_clear_cache_after_delete () $ page = FactoryMuff :: create ('Page'); // Une valeur de test est enregistrée dans le cache Cache :: put ('pages_for_menu', 'valeur', 5); // Ceci devrait nettoyer la valeur dans le cache $ page-> delete (); $ this-> assertNull (Cache :: get ('pages_for_menu')); 

Modèle d'utilisateur

En relation avec les modèles présentés précédemment, nous avons maintenant le Utilisateur. Voici le code pour ce modèle:

 'string', 'email' => 'email', 'password' => '123123', 'password_confirmation' => '123123',); / ** * comporte de nombreuses pages * / pages de fonction publique () return $ this-> hasMany ('Page', 'author_id');  / ** * a beaucoup de posts * / posts publics () return $ this-> hasMany ('Post', 'author_id'); 

Ce modèle est absent des tests.

Nous pouvons observer que, à l'exception des relations (qu'il peut être utile de tester), il n'y a pas d'implémentation de méthode ici. Qu'en est-il de l'authentification? Eh bien, l’utilisation du paquet Confide fournit déjà l’implémentation et les tests pour cette.

Les tests pour Zizaco \ Confide \ ConfideUser sont situés dans ConfideUserTest.php.

Il est important de déterminer les responsabilités de la classe avant de passer vos tests. Tester l'option de "réinitialiser le mot de passe" d'un Utilisateur serait redondant. C’est parce que la responsabilité de ce test incombe à Zizaco \ Confide \ ConfideUser; pas dedans Utilisateur.

Il en va de même pour les tests de validation des données. Comme le paquet, Ardent, assume cette responsabilité, cela n’a aucun sens de tester à nouveau les fonctionnalités..

En bref: gardez vos tests propres et organisés. Déterminer la responsabilité propre de chaque classe et tester uniquement ce qui est strictement sa responsabilité.


Conclusion

L'utilisation d'une base de données en mémoire est une bonne pratique pour exécuter rapidement des tests sur une base de données. Grâce à l'aide de certains packages, tels que Ardent, FactoryMuff et Confide, vous pouvez réduire la quantité de code dans vos modèles, tout en maintenant des tests clairs et objectifs..

Dans la suite de cet article, nous passerons en revue Manette essai. Restez à l'écoute!

Toujours en train de démarrer avec Laravel 4, laissez-nous vous enseigner les bases!