2009-05-22 8 views
28

Je suis surtout convaincu des avantages des tests unitaires, et je voudrais commencer à appliquer le concept à une grande base de code existante écrite en PHP. Moins de 10% de ce code est orienté objet.Comment écrire des tests unitaires en PHP avec une base de code procédurale?

J'ai examiné plusieurs frameworks de tests unitaires (PHPUnit, SimpleTest et phpt). Cependant, je n'ai trouvé aucun exemple pour tester le code de procédure. Quel est le meilleur cadre pour ma situation et existe-t-il des exemples de tests unitaires PHP utilisant du code non-OOP?

Répondre

34

Vous pouvez tester unitairement la procédure PHP, pas de problème. Et vous n'avez certainement pas de chance si votre code est mélangé avec du HTML. Au niveau de l'application ou du test d'acceptation, votre PHP procédural dépend probablement de la valeur des superglobaux ($_POST, $_GET, $_COOKIE, etc.) pour déterminer le comportement, et finit par inclure un fichier modèle et cracher la sortie.

Pour effectuer des tests au niveau de l'application, vous pouvez simplement définir les valeurs superglobales; démarrer un tampon de sortie (pour empêcher le html d'envahir votre écran); appelle la page; affirmer contre des choses à l'intérieur du tampon; et trash le tampon à la fin. Donc, vous pourriez faire quelque chose comme ceci:

public function setUp() 
{ 
    if (isset($_POST['foo'])) { 
     unset($_POST['foo']); 
    } 
} 

public function testSomeKindOfAcceptanceTest() 
{ 
    $_POST['foo'] = 'bar'; 
    ob_start(); 
    include('fileToTest.php'); 
    $output = ob_get_flush(); 
    $this->assertContains($someExpectedString, $output); 
} 

Même pour d'énormes « cadres » avec beaucoup de comprend, ce genre de test vous dira si vous avez des caractéristiques au niveau des applications de travail ou non. Cela va être très important lorsque vous commencerez à améliorer votre code, car même si vous êtes convaincu que le connecteur de la base de données fonctionne encore et qu'il a l'air mieux qu'avant, vous devrez cliquer sur un bouton pour voir que oui, vous pouvez toujours connectez-vous et déconnectez-vous via la base de données.

Aux niveaux inférieurs, il existe des variations mineures en fonction de la portée variable et si les fonctions fonctionnent par effets secondaires (en retournant true ou false) ou en renvoyant le résultat directement.

Les variables sont-elles transmises explicitement, en tant que paramètres ou tableaux de paramètres entre les fonctions? Ou les variables sont-elles définies dans de nombreux endroits différents, et sont-elles transmises implicitement en tant que variables globales? Si c'est le cas (bien) explicite, vous pouvez tester une fonction en (1) incluant le fichier contenant la fonction, puis (2) en alimentant directement les valeurs de test de fonction, et (3) en capturant la sortie et en affirmant. Si vous utilisez des globals, vous devez juste faire très attention (comme ci-dessus, dans l'exemple $ _POST) pour annuler soigneusement tous les globals entre les tests. Il est également particulièrement utile de garder les tests très petits (5-10 lignes, 1-2 affirmations) lorsque vous travaillez avec une fonction qui pousse et tire beaucoup de globals.

Un autre problème fondamental est de savoir si les fonctions fonctionnent en retournant la sortie, ou en modifiant les paramètres passés, en renvoyant true/false à la place. Dans le premier cas, le test est plus facile, mais encore une fois, il est possible dans les deux cas:

// assuming you required the file of interest at the top of the test file 
public function testShouldConcatenateTwoStringsAndReturnResult() 
{ 
    $stringOne = 'foo'; 
    $stringTwo = 'bar'; 
    $expectedOutput = 'foobar'; 
    $output = myCustomCatFunction($stringOne, $stringTwo); 
    $this->assertEquals($expectedOutput, $output); 
} 

Dans le mauvais cas où votre code fonctionne par des effets secondaires et retourne vrai ou faux, vous pouvez toujours tester assez facilement :

/* suppose your cat function stupidly 
* overwrites the first parameter 
* with the result of concatenation, 
* as an admittedly contrived example 
*/ 
public function testShouldConcatenateTwoStringsAndReturnTrue() 
    { 
     $stringOne = 'foo'; 
     $stringTwo = 'bar'; 
     $expectedOutput = 'foobar'; 
     $output = myCustomCatFunction($stringOne, $stringTwo); 
     $this->assertTrue($output); 
     $this->Equals($expectedOutput, $stringOne); 
    } 

Espérons que cela aide.

+0

Cela ne va pas bien si le code que vous testez est jonché de 'exit;' déclarations :( –

+3

@YarekT Aucun test ne peut bien se passer si le code est jonché d'instructions 'exit' ou' die'. gérer la terminaison de script attendue en lançant une exception et en enregistrant un gestionnaire d'exceptions personnalisé assez intelligent pour ignorer le type d'exception personnalisé lorsqu'il est rencontré.Vous pouvez ensuite tester que l'exception attendue est levée.Bien sûr, une application bien conçue ne devrait vraiment pas Je n'ai jamais besoin de 'quitter 'ou' mourir' après la phase bootstrap. – rdlowrey

1

Vous pouvez essayer d'inclure votre code non-oop dans une classe de test en utilisant

require_once 'your_non_oop_file.php' # Contains fct_to_test() 

Et avec PHPUnit vous définissez votre fonction de test:

testfct_to_test() { 
    assertEquals(result_expected, fct_to_test(), 'Fail with fct_to_test'); 
} 
+0

Cela ressemble à une solution intéressante, mais que se passe-t-il si la plupart de votre logique n'implique pas de fonctions mais plutôt de simples boucles et conditions? –

+5

Ensuite, vous avez vous-même une grande ol 'aide de code spaghetti. –

+0

J'ai supposé que Travis n'a pas de code php avec html. –

6

Quels sont les tests unitaires bien faire, et ce que vous devriez les utiliser pour, c'est quand vous avez un morceau de code que vous donnez un certain nombre d'entrées, et vous vous attendez à récupérer un certain nombre de sorties. L'idée étant que, lorsque vous ajouterez des fonctionnalités plus tard, vous pourrez exécuter vos tests et vous assurer qu'il fonctionne toujours de la même manière.

Donc, si vous avez un code de base de la procédure, vous pouvez accomplir cet appel à vos fonctions dans les méthodes d'essai

require 'my-libraries.php'; 
class SomeTest extends SomeBaseTestFromSomeFramework { 
    public function testSetup() { 
     $this->assertTrue(true); 
    } 

    public function testMyFunction() { 
     $output = my_function('foo',3); 

     $this->assertEquals('expected output',$output); 
    } 
} 

Cette astuce avec le code PHP bases est souvent votre code de bibliothèque interfèrent avec le de votre framework de test, car votre base de code et les frameworks de test auront beaucoup de code lié à la mise en place d'un environnement applicatif dans un navigateur web (session, variables globales partagées, etc.). Attendez-vous à passer à un moment où vous pouvez inclure votre code de bibliothèque et exécuter un test de saleté simple (la fonction testSetup ci-dessus).

Si votre code n'a pas de fonctions, et qu'il ne s'agit que d'une série de fichiers PHP qui sortent des pages HTML, vous n'avez pas de chance. Votre code ne peut pas être séparé en unités distinctes, ce qui signifie que les tests unitaires ne vous seront pas très utiles. Vous feriez mieux de passer votre temps au niveau des «tests d'acceptation» avec des produits comme Selenium et Watir. Ceux-ci vous permettront d'automatiser un navigateur, puis vérifier les pages pour le contenu que les emplacements spécifiques/dans les formulaires.

+0

Je peux définitivement séparer mon code en unités distinctes. Votre exemple semble prometteur, je suppose qu'il n'utilise pas de cadre particulier? –

+0

Ouais, c'est juste un pseudo-code-test, mais les tests SImpleTest et les tests PHPUnit réels sont remarquablement similaires. –

Questions connexes