21 octobre 2021 170.247K

Formation Laravel : Tests

Lors de la création d’une application web vous allez passer énormément de temps à faire des tests. La plupart du temps ces tests sont réalisés manuellement et prennent du temps (comme par exemple remplir un formulaire pour envoyer les données sur le serveur et les tester). Mais le langage PHP permet d’effectuer ces tests de manière automatique grâce notamment à PHPUnit. PHPUnit est le package php de référence en terme de testing. Il a été créé par Sebastian Bergmann en 2004 et est utilisé par la plupart des framework php actuel (Zend Framework, Symfony, Cake PHP etc…) et Laravel en fait bien entendu partie.

Vous pouvez retrouver le code source de PHPUnit sur Github.

1. Paramétrer l’environnement de testing

Les fichiers de tests se trouvent dans le dossier tests se trouvant à la racine du projet Laravel. Mais avant de nous y rendre nous allons devoir paramétrer notre environnement pour le testing. Ce paramétrage se réalise dans le fichier phpunit.xml qui se trouve dans le dossier public.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
            backupStaticAttributes="false"
            bootstrap="vendor/autoload.php"
            colors="true"
            convertErrorsToExceptions="true"
            convertNoticesToExceptions="true"
            convertWarningsToExceptions="true"
            processIsolation="false"
            stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_URL" value="http://127.0.0.1:8000"/>
        <env name="APP_ENV" value="testing"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="MAIL_DRIVER" value="array"/>
    </php>
</phpunit>

Vous pouvez également créer un fichier d’environnement pour le testing directement à la racine du projet en nommant le fichier .env.testing qui override le fichier .env existant :

APP_ENV=testing
APP_DEBUG=true
APP_URL=http://127.0.0.1:8000/

BCRYPT_ROUNDS=4
CACHE_DRIVER=array
SESSION_DRIVER=array
QUEUE_DRIVER=sync
MAIL_DRIVER=array

Une fois vos paramétrages changés pensez bien à clear le cache de votre configuration :

php artisan config:clear

2. L’utilisation de PHPUnit par Laravel

Maintenant retournons vers notre dossier tests. Vous pouvez voir qu’il y a 2 dossiers à l’intérieur :
• Feature : qui regroupe les tests dit fonctionnels permettant de tester votre application.
• Unit : qui regroupe les tests dit unitaires permettant de tester une petite partie (une unité) d’un code. Elles sont utiles pour tester du code présent dans vos helpers, models, controllers etc…

Vous pouvez remarquer qu’il existe déjà un fichier d’exemple dans chacun de ces dossiers. Regardons chacun de ces 2 fichiers ExampleTest.php dans le dossier feature et dans le dossier unit :

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    /**
        * A basic test example.
        *
        * @return void
        */
    public function testBasicTest()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }
}
<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    /**
        * A basic test example.
        *
        * @return void
        */
    public function testBasicTest()
    {
        $this->assertTrue(true);
    }
}

Nous remarquons que chacune de ces class ExampleTest extends la class TestCase :

<?php

namespace Tests;

use Illuminate\Foundation\Testing\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    use CreatesApplication;
}

La class CreatesApplication est une class qui nous retourne simplement notre application :

<?php

namespace Tests;

use Illuminate\Contracts\Console\Kernel;

trait CreatesApplication
{
    /**
        * Creates the application.
        *
        * @return \Illuminate\Foundation\Application
        */
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->make(Kernel::class)->bootstrap();

        return $app;
    }
}

Ce qui nous intéresse vraiment dans cette class TestCase c’est qu’elle extends à nouveau un autre TestCase, nous allons continuer à remonter les divers extends jusqu’à arriver à la class TestCase de PHPUnit qui se trouve ici : PHPUnit\Framework\TestCase. Voici un aperçu de cette class :

<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace PHPUnit\Framework;

use DeepCopy\DeepCopy;
use PHPUnit\Framework\Constraint\Exception as ExceptionConstraint;
use PHPUnit\Framework\Constraint\ExceptionCode;
use PHPUnit\Framework\Constraint\ExceptionMessage;
use PHPUnit\Framework\Constraint\ExceptionMessageRegularExpression;
use PHPUnit\Framework\MockObject\Generator as MockGenerator;
use PHPUnit\Framework\MockObject\Matcher\AnyInvokedCount as AnyInvokedCountMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtIndex as InvokedAtIndexMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtLeastCount as InvokedAtLeastCountMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedAtMostCount as InvokedAtMostCountMatcher;
use PHPUnit\Framework\MockObject\Matcher\InvokedCount as InvokedCountMatcher;
use PHPUnit\Framework\MockObject\MockBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub;
use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub;
use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub;
use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub;
use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub;
use PHPUnit\Framework\MockObject\Stub\ReturnStub;
use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub;
use PHPUnit\Runner\BaseTestRunner;
use PHPUnit\Runner\PhptTestCase;
use PHPUnit\Util\GlobalState;
use PHPUnit\Util\PHP\AbstractPhpProcess;
use Prophecy;
use Prophecy\Exception\Prediction\PredictionException;
use Prophecy\Prophecy\MethodProphecy;
use Prophecy\Prophecy\ObjectProphecy;
use Prophecy\Prophet;
use ReflectionClass;
use ReflectionException;
use ReflectionObject;
use SebastianBergmann\Comparator\Comparator;
use SebastianBergmann\Comparator\Factory as ComparatorFactory;
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Exporter\Exporter;
use SebastianBergmann\GlobalState\Blacklist;
use SebastianBergmann\GlobalState\Restorer;
use SebastianBergmann\GlobalState\Snapshot;
use SebastianBergmann\ObjectEnumerator\Enumerator;
use Text_Template;
use Throwable;

abstract class TestCase extends Assert implements SelfDescribing, Test
{

Nous avons a nouveau une class TestCase qui cette fois extends la class Assert, en nous y rendant nous trouvons des tas de méthodes d’assertions qui nous serviront lors de nos tests. Vous pouvez y retrouver l’assertion assertTrue() dans le fichier de test de notre dossier Unit :

<?php
/*
 * This file is part of PHPUnit.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace PHPUnit\Framework;

use ArrayAccess;
use Countable;
use DOMDocument;
use DOMElement;
use PHPUnit\Framework\Constraint\ArrayHasKey;
use PHPUnit\Framework\Constraint\ArraySubset;
use PHPUnit\Framework\Constraint\Attribute;
use PHPUnit\Framework\Constraint\Callback;
use PHPUnit\Framework\Constraint\ClassHasAttribute;
use PHPUnit\Framework\Constraint\ClassHasStaticAttribute;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Constraint\Count;
use PHPUnit\Framework\Constraint\DirectoryExists;
use PHPUnit\Framework\Constraint\FileExists;
use PHPUnit\Framework\Constraint\GreaterThan;
use PHPUnit\Framework\Constraint\IsAnything;
use PHPUnit\Framework\Constraint\IsEmpty;
use PHPUnit\Framework\Constraint\IsEqual;
use PHPUnit\Framework\Constraint\IsFalse;
use PHPUnit\Framework\Constraint\IsFinite;
use PHPUnit\Framework\Constraint\IsIdentical;
use PHPUnit\Framework\Constraint\IsInfinite;
use PHPUnit\Framework\Constraint\IsInstanceOf;
use PHPUnit\Framework\Constraint\IsJson;
use PHPUnit\Framework\Constraint\IsNan;
use PHPUnit\Framework\Constraint\IsNull;
use PHPUnit\Framework\Constraint\IsReadable;
use PHPUnit\Framework\Constraint\IsTrue;
use PHPUnit\Framework\Constraint\IsType;
use PHPUnit\Framework\Constraint\IsWritable;
use PHPUnit\Framework\Constraint\JsonMatches;
use PHPUnit\Framework\Constraint\LessThan;
use PHPUnit\Framework\Constraint\LogicalAnd;
use PHPUnit\Framework\Constraint\LogicalNot;
use PHPUnit\Framework\Constraint\LogicalOr;
use PHPUnit\Framework\Constraint\LogicalXor;
use PHPUnit\Framework\Constraint\ObjectHasAttribute;
use PHPUnit\Framework\Constraint\RegularExpression;
use PHPUnit\Framework\Constraint\SameSize;
use PHPUnit\Framework\Constraint\StringContains;
use PHPUnit\Framework\Constraint\StringEndsWith;
use PHPUnit\Framework\Constraint\StringMatchesFormatDescription;
use PHPUnit\Framework\Constraint\StringStartsWith;
use PHPUnit\Framework\Constraint\TraversableContains;
use PHPUnit\Framework\Constraint\TraversableContainsOnly;
use PHPUnit\Util\InvalidArgumentHelper;
use PHPUnit\Util\Type;
use PHPUnit\Util\Xml;
use ReflectionClass;
use ReflectionException;
use ReflectionObject;
use Traversable;

/**
 * A set of assertion methods.
 */
abstract class Assert
{
    // .... //
    
    /**
        * Asserts that a condition is true.
        *
        * @throws ExpectationFailedException
        * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
        */
    public static function assertTrue($condition, string $message = ''): void
    {
        static::assertThat($condition, static::isTrue(), $message);
    }

    // .... //
}

Heu… Je ne trouve pas assertStatus() !

Et non ! Cette assertion n’existe pas dans PHPUnit, vous retrouverez les assertions de response dans la class TestResponse.php qui ce trouve dans le namespace Illuminate\Foundation\Testing et qui est une macro. Vous pouvez voir que Laravel utilise tout de même PHPUnit pour créer ses propres assertions :

<?php

namespace Illuminate\Foundation\Testing;

use Closure;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Carbon;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Traits\Macroable;
use PHPUnit\Framework\Assert as PHPUnit;
use Illuminate\Foundation\Testing\Constraints\SeeInOrder;

/**
    * @mixin \Illuminate\Http\Response
    */
class TestResponse
{
    use Macroable {
        __call as macroCall;
    }

    /**
        * The response to delegate to.
        *
        * @var \Illuminate\Http\Response
        */
    public $baseResponse;

    /**
        * Create a new test response instance.
        *
        * @param  \Illuminate\Http\Response  $response
        * @return void
        */
    public function __construct($response)
    {
        $this->baseResponse = $response;
    }
    
    
    // .... //
    
    
    /**
        * Assert that the response has the given status code.
        *
        * @param  int  $status
        * @return $this
        */
    public function assertStatus($status)
    {
        $actual = $this->getStatusCode();

        PHPUnit::assertTrue(
            $actual === $status,
            "Expected status code {$status} but received {$actual}."
        );

        return $this;
    }
    
    
    // .... //
    
    
}

3. Créer des tests avec Laravel

Bien maintenant que nous en savons un peu plus sur l’origine de ces assertions commençons par créer un nouveau fichier de tests en lançant la commande suivante :

php artisan make:test CustomTest

Cela créera par défaut un fichier de tests de type « Feature », si vous souhaitez créer un fichier de tests de type unitaire vous devez ajouter l’option --unit :

php artisan make:test CustomTest --unit

Pour l’exemple nous allons réaliser des tests de notre model User, cette table aura une colonne ‘firstname’ et ‘lastname’.

Ensuite faites une migration de votre table users et créez un utilisateur. Nous imaginons maintenant que nous avons une méthode getFullName dans notre model qui retourne le nom complet de notre utilisateur :

<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;

use App\Models\User;

class UserTest extends TestCase
{
    /**
        * @return void
        */
    public function test_return_user_full_name()
    {
        $user = User::first();
        $this->assertEquals( $user->firstname.' '.$user->lastname, $user->returnFullName() );
    }
}

Attention ici au nom de votre méthode, celui-ci doit impérativement commencer par “test” pour que PHPUnit comprenne cette méthode comme un test à lancer. Vous pouvez également ajouter @test dans la documentation de votre méthode pour ne pas avoir à l’écrire au début du nom de la méthode :

/**
 * @test
 * @return void
 */
public function return_user_full_name()
{
    $user = User::first();
    $this->assertEquals( $user->firstname.' '.$user->lastname, $user->returnFullName() );
}

Concernant l’assertion que nous avons écrite si vous vous rendez de nouveau dans la class Assert vous aurez plus d’infos concernant assertEquals() :

/**
 * Asserts that two variables are equal.
 *
 * @throws ExpectationFailedException
 * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
 */
public static function assertEquals($expected, $actual, string $message = '', float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void
{
    $constraint = new IsEqual(
        $expected,
        $delta,
        $maxDepth,
        $canonicalize,
        $ignoreCase
    );

    static::assertThat($actual, $constraint, $message);
}

Cette assertion nous permet de vérifier si le résultat attendu $expected est égal au résultat obtenu $actual.

Maintenant vous pouvez lancer phpunit en lançant la commande :

./vendor/bin/phpunit

Si vos tests sont correctes cela vous retournera “OK”, si certains de vos tests échouent cela vous retournera FAILURES. Cela vous retourne également le nombre de tests (qui sont le nombre des diverses méthodes créées dans vos class de Tests, puis le nombre d’assertion et enfin le nombre d’erreurs si vous en avez. Il peut y avoir plusieurs assertions dans un seul test.

Voilà vous connaissez l’essentiel à savoir sur les testing dans Laravel. Je laisse maintenant libre cours à votre curiosité pour parcourir les diverses assertions qui vous pourriez utiliser lors de vos tests. 🙂

Besoin d'une formation personalisée ?

Avis

4,9
Rated 4.9 out of 5
Excellent93%
Splendide !7%
Sympa0%
Sans plus0%
Pas terrible0%

Pas de titre

Rated 5 out of 5
7 mars 2024

super

hajar

Pas de titre

Rated 5 out of 5
21 juin 2023

Merci énormément je suis sur exciter à cause de ce cours

Kassi Patrick

Pas de titre

Rated 5 out of 5
10 mai 2023

Excellente formation

abdo maroc

Pas de titre

Rated 5 out of 5
7 mai 2023

Merci

Ranaivoson (c'est malgache)

Pas de titre

Rated 5 out of 5
18 avril 2023

Merci

Anthea