Comment écrire des tests unitaires en PHP? [fermé]


98

J'ai lu partout à quel point ils sont géniaux, mais pour une raison quelconque, je n'arrive pas à comprendre comment exactement je suis censé tester quelque chose. Quelqu'un pourrait-il publier un exemple de code et comment le testerait-il? Si ce n'est pas trop compliqué :)


5
Pour l'équilibre, il n'y a pas 2 ou 3 frameworks de tests unitaires pour PHP - il y a une liste ici: en.wikipedia.org/wiki/List_of_unit_testing_frameworks#PHP
Fenton

Réponses:


36

Il existe un troisième "framework", qui est de loin plus facile à apprendre - encore plus facile que Simple Test, il s'appelle phpt.

Une introduction peut être trouvée ici: http://qa.php.net/write-test.php

Edit: Je viens de voir votre demande d'exemple de code.

Supposons que vous ayez la fonction suivante dans un fichier appelé lib.php :

<?php
function foo($bar)
{
  return $bar;
}
?>

Vraiment simple et direct, le paramètre que vous transmettez est renvoyé. Regardons donc un test pour cette fonction, nous appellerons le fichier de test foo.phpt :

--TEST--
foo() function - A basic test to see if it works. :)
--FILE--
<?php
include 'lib.php'; // might need to adjust path if not in the same dir
$bar = 'Hello World';
var_dump(foo($bar));
?>
--EXPECT--
string(11) "Hello World"

En un mot, nous fournissons le paramètre $baravec une valeur "Hello World"et nous var_dump()la réponse de l'appel à la fonction foo().

Pour exécuter ce test, utilisez: pear run-test path/to/foo.phpt

Cela nécessite une installation fonctionnelle de PEAR sur votre système, ce qui est assez courant dans la plupart des cas. Si vous avez besoin de l'installer, je vous recommande d'installer la dernière version disponible. Au cas où vous auriez besoin d'aide pour le configurer, n'hésitez pas à demander (mais fournissez le système d'exploitation, etc.).


Ne devrait-il pas l'être run-tests?
Dharman

31

Il existe deux frameworks que vous pouvez utiliser pour les tests unitaires. Simpletest et PHPUnit , que je préfère. Lisez les tutoriels sur la façon d'écrire et d'exécuter des tests sur la page d'accueil de PHPUnit. C'est assez simple et bien décrit.


22

Vous pouvez rendre les tests unitaires plus efficaces en modifiant votre style de codage pour l'adapter.

Je recommande de parcourir le blog de test de Google , en particulier l'article sur l' écriture de code testable .


7
Je pense que vous avez mentionné un excellent article. Commencer votre réponse par «Les tests unitaires ne sont pas très efficaces» m'a presque incité à voter contre, étant un adepte des tests ... Peut-être, reformuler de manière positive encouragerait les gens à lire l'article.
xtofl

2
@xtofl l'a modifié pour augmenter légèrement la 'positivité' :)
icc97

13

J'ai roulé le mien parce que je n'avais pas le temps d'apprendre à quelqu'un d'autre façon de faire les choses, cela a pris environ 20 minutes à rédiger, 10 à l'adapter pour le poster ici.

Unittesting m'est très utile.

c'est un peu long mais ça s'explique et il y a un exemple en bas.

/**
 * Provides Assertions
 **/
class Assert
{
    public static function AreEqual( $a, $b )
    {
        if ( $a != $b )
        {
            throw new Exception( 'Subjects are not equal.' );
        }
    }
}

/**
 * Provides a loggable entity with information on a test and how it executed
 **/
class TestResult
{
    protected $_testableInstance = null;

    protected $_isSuccess = false;
    public function getSuccess()
    {
        return $this->_isSuccess;
    }

    protected $_output = '';
    public function getOutput()
    {
        return $_output;
    }
    public function setOutput( $value )
    {
        $_output = $value;
    }

    protected $_test = null;
    public function getTest()
    {
        return $this->_test;
    }

    public function getName()
    {
        return $this->_test->getName();
    }
    public function getComment()
    {
        return $this->ParseComment( $this->_test->getDocComment() );
    }

    private function ParseComment( $comment )
    {
        $lines = explode( "\n", $comment );
        for( $i = 0; $i < count( $lines ); $i ++ )
        {
            $lines[$i] = trim( $lines[ $i ] );
        }
        return implode( "\n", $lines );
    }

    protected $_exception = null;
    public function getException()
    {
        return $this->_exception;
    }

    static public function CreateFailure( Testable $object, ReflectionMethod $test, Exception $exception )
    {
        $result = new self();
        $result->_isSuccess = false;
        $result->testableInstance = $object;
        $result->_test = $test;
        $result->_exception = $exception;

        return $result;
    }
    static public function CreateSuccess( Testable $object, ReflectionMethod $test )
    {
        $result = new self();
        $result->_isSuccess = true;
        $result->testableInstance = $object;
        $result->_test = $test;

        return $result;
    }
}

/**
 * Provides a base class to derive tests from
 **/
abstract class Testable
{
    protected $test_log = array();

    /**
     * Logs the result of a test. keeps track of results for later inspection, Overridable to log elsewhere.
     **/
    protected function Log( TestResult $result )
    {
        $this->test_log[] = $result;

        printf( "Test: %s was a %s %s\n"
            ,$result->getName()
            ,$result->getSuccess() ? 'success' : 'failure'
            ,$result->getSuccess() ? '' : sprintf( "\n%s (lines:%d-%d; file:%s)"
                ,$result->getComment()
                ,$result->getTest()->getStartLine()
                ,$result->getTest()->getEndLine()
                ,$result->getTest()->getFileName()
                )
            );

    }
    final public function RunTests()
    {
        $class = new ReflectionClass( $this );
        foreach( $class->GetMethods() as $method )
        {
            $methodname = $method->getName();
            if ( strlen( $methodname ) > 4 && substr( $methodname, 0, 4 ) == 'Test' )
            {
                ob_start();
                try
                {
                    $this->$methodname();
                    $result = TestResult::CreateSuccess( $this, $method );
                }
                catch( Exception $ex )
                {
                    $result = TestResult::CreateFailure( $this, $method, $ex );
                }
                $output = ob_get_clean();
                $result->setOutput( $output );
                $this->Log( $result );
            }
        }
    }
}

/**
 * a simple Test suite with two tests
 **/
class MyTest extends Testable
{
    /**
     * This test is designed to fail
     **/
    public function TestOne()
    {
        Assert::AreEqual( 1, 2 );
    }

    /**
     * This test is designed to succeed
     **/
    public function TestTwo()
    {
        Assert::AreEqual( 1, 1 );
    }
}

// this is how to use it.
$test = new MyTest();
$test->RunTests();

Cela produit:

Test: TestOne était un échec 
/ **
* Ce test est conçu pour échouer
** / (lignes: 149-152; fichier: /Users/kris/Desktop/Testable.php)
Test: TestTwo a été un succès 

7

Obtenez PHPUnit. Il est très simple à utiliser.

Commencez ensuite par des affirmations très simples. Vous pouvez faire beaucoup avec AssertEquals avant de vous lancer dans autre chose. C'est un bon moyen de se mouiller les pieds.

Vous pouvez également essayer d'écrire votre test en premier (puisque vous avez donné à votre question la balise TDD), puis d'écrire votre code. Si vous ne l'avez pas fait auparavant, c'est une révélation.

require_once 'ClassYouWantToTest';
require_once 'PHPUnit...blah,blah,whatever';

class ClassYouWantToTest extends PHPUnit...blah,blah,whatever
{
    private $ClassYouWantToTest;

   protected function setUp ()
    {
        parent::setUp();
        $this->ClassYouWantToTest = new ClassYouWantToTest(/* parameters */);
    }

    protected function tearDown ()
    {
        $this->ClassYouWantToTest = null;
        parent::tearDown();
    }

    public function __construct ()
    {   
        // not really needed
    }

    /**
     * Tests ClassYouWantToTest->methodFoo()
     */
    public function testMethodFoo ()
    {
        $this->assertEquals(
            $this->ClassYouWantToTest->methodFoo('putValueOfParamHere), 'expectedOutputHere);

    /**
     * Tests ClassYouWantToTest->methodBar()
     */
    public function testMethodFoo ()
    {
        $this->assertEquals(
            $this->ClassYouWantToTest->methodBar('putValueOfParamHere), 'expectedOutputHere);
}

5

Pour les tests simples ET la documentation, php-doctest est assez sympa et c'est un moyen très simple de commencer car vous n'avez pas à ouvrir un fichier séparé. Imaginez la fonction ci-dessous:

/**
* Sums 2 numbers
* <code>
* //doctest: add
* echo add(5,2);
* //expects:
* 7
* </code>
*/
function add($a,$b){
    return $a + $b;   
}

Si vous exécutez maintenant ce fichier via phpdt (exécuteur de ligne de commande de php-doctest), un test sera exécuté. Le doctest est contenu dans le bloc <code>. Doctest est né en python et convient parfaitement pour donner des exemples utiles et exécutables sur la façon dont le code est censé fonctionner. Vous ne pouvez pas l'utiliser exclusivement car le code lui-même serait jonché de cas de test, mais j'ai trouvé qu'il était utile avec une bibliothèque tdd plus formelle - j'utilise phpunit.

Cette 1ère réponse ici résume bien la situation (ce n'est pas unité vs doctest).


1
cela ne fait-il pas un peu de bruit dans la source?
Ali Ghanavatian

ça peut. il ne doit être utilisé que pour des tests simples et uniques. sert également de documentation. si vous avez besoin de plus de test unitaire.
Sofia

2

phpunit est à peu près le cadre de test unitaire de facto pour php. il existe également DocTest (disponible sous forme de package PEAR) et quelques autres. php lui-même est testé pour les régressions et autres via des tests phpt qui peuvent également être exécutés via pear.


2

Les tests de réception codée ressemblent beaucoup aux tests unitaires courants, mais ils sont très puissants dans les cas où vous avez besoin de moqueries et de stubbing.

Voici l'exemple de test du contrôleur. Notez la facilité avec laquelle les stubs sont créés. La facilité avec laquelle vous vérifiez que la méthode a été invoquée.

<?php
use Codeception\Util\Stub as Stub;

const VALID_USER_ID = 1;
const INVALID_USER_ID = 0;

class UserControllerCest {
public $class = 'UserController';


public function show(CodeGuy $I) {
    // prepare environment
    $I->haveFakeClass($controller = Stub::makeEmptyExcept($this->class, 'show'));
    $I->haveFakeClass($db = Stub::make('DbConnector', array('find' => function($id) { return $id == VALID_USER_ID ? new User() : null ))); };
    $I->setProperty($controller, 'db', $db);

    $I->executeTestedMethodOn($controller, VALID_USER_ID)
        ->seeResultEquals(true)
        ->seeMethodInvoked($controller, 'render');

    $I->expect('it will render 404 page for non existent user')
        ->executeTestedMethodOn($controller, INVALID_USER_ID)
        ->seeResultNotEquals(true)
        ->seeMethodInvoked($controller, 'render404','User not found')
        ->seeMethodNotInvoked($controller, 'render');
}
}

Il y a aussi d'autres choses sympas. Vous pouvez tester l'état de la base de données, le système de fichiers, etc.


1

Outre les excellentes suggestions sur les frameworks de test déjà données, construisez-vous votre application avec l'un des frameworks Web PHP intégrant des tests automatisés, tels que Symfony ou CakePHP ? Parfois, avoir un endroit pour laisser tomber vos méthodes de test réduit les frictions de démarrage que certaines personnes associent aux tests automatisés et au TDD.


1

Beaucoup trop pour re-publier ici, mais voici un excellent article sur l'utilisation de phpt . Il couvre un certain nombre d'aspects autour de phpt qui sont souvent négligés, donc cela pourrait valoir la peine d'être lu pour élargir vos connaissances en php au-delà de la simple rédaction d'un test. Heureusement, l'article traite également des tests d'écriture!

Les principaux points de discussion

  1. Découvrez comment fonctionnent les aspects marginalement documentés de PHP (ou à peu près n'importe quelle partie d'ailleurs)
  2. Écrivez des tests unitaires simples pour votre propre code PHP
  3. Écrire des tests dans le cadre d'une extension ou pour transmettre un bug potentiel aux internes ou aux groupes d'assurance qualité

1

Je sais qu'il y a déjà beaucoup d'informations ici, mais comme cela apparaît toujours dans les recherches Google, je pourrais aussi bien ajouter Chinook Test Suite à la liste. C'est un cadre de test simple et petit.

Vous pouvez facilement tester vos classes avec lui et également créer des objets simulés. Vous exécutez les tests via un navigateur Web et (pas encore) via une console. Dans le navigateur, vous pouvez spécifier quelle classe de test ou même quelle méthode de test exécuter. Ou vous pouvez simplement exécuter tous les tests.

Une capture d'écran de la page github:

Cadre de test unitaire Chinook

Ce que j'aime, c'est la façon dont vous affirmez les tests. Ceci est fait avec ce que l'on appelle des «assertions fluides». Exemple:

$this->Assert($datetime)->Should()->BeAfter($someDatetime);

Et créer des objets simulés est également un jeu d'enfant (avec une syntaxe fluide):

$mock = new CFMock::Create(new DummyClass());
$mock->ACallTo('SomeMethod')->Returns('some value');

Quoi qu'il en soit, plus d'informations peuvent être trouvées sur la page github avec un exemple de code:

https://github.com/w00/Chinook-TestSuite

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.