Quelle est la bonne façon de tester le code PHP7 à l'unité avec PHPUnit 4.1 dans Magento 2?

23

Lorsque j'écris mes modules, j'essaie de leur fournir des tests unitaires pour les parties les plus critiques de l'application. Cependant, il existe actuellement (Magento 2.1.3) plusieurs façons d'écrire des tests unitaires:

Différentes façons de tester

  • Intégrez-le avec bin/magento dev:tests:run unitet exécutez-le au-dessus des paramètres phpunit par défaut fournis avec Magento.
  • Écrivez-les séparément, exécutez-les avec vendor/bin/phpunit app/code/Vendor/Module/Test/Unitet moquez tout ce qui est Magento.
  • Écrivez-les séparément, simulez tout et utilisez une version globale de PHPUnit.
  • Écrivez-les séparément, exécutez-les avec vendor/bin/phpunit, mais utilisez toujours \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.

Magento 2 et PHPUnit

En plus de cela, Magento 2 est livré avec PHPUnit 4.1.0, qui n'est pas compatible PHP7. Les natifs avec indication de type (comme stringet `int) et la déclaration des types de retour dans vos signatures génèreront des erreurs. Par exemple, une interface / classe avec une signature de méthode comme celle-ci:

public function foo(string $bar) : bool;

... ne pourra pas être raillé par PHPUnit 4.1.0. :-(

Ma situation actuelle

C'est à cause de cela que j'écris maintenant principalement mes tests unitaires de la troisième manière (en appelant une version PHPUnit globale du système).

Dans ma configuration, j'ai PHPUnit 5.6 installé à l'échelle mondiale, donc je peux résoudre l'écriture de code PHP7 approprié, mais je dois faire quelques ajustements. Par exemple:

phpunit.xml doit ressembler à ceci pour que je puisse utiliser l'autochargeur du compositeur:

<?xml version="1.0"?>
<phpunit bootstrap="../../../../../../vendor/autoload.php"
         colors="true">
    <testsuites>
        <testsuite name="Testsuite">
            <directory>.</directory>
        </testsuite>
    </testsuites>
</phpunit>

... et dans toutes mes setUp()méthodes, j'ai la vérification suivante pour pouvoir écrire mes tests avec la compatibilité ascendante:

// Only allow PHPUnit 5.x:
if (version_compare(\PHPUnit_Runner_Version::id(), '5', '<')) {
    $this->markTestSkipped();
}

De cette façon, lorsque mes tests sont exécutés par PHPUnit intégré à Magentos, cela ne génère pas d'erreur.

Ma question

Voici donc ma question: est-ce une façon «saine» d'écrire des tests unitaires? Parce qu'il ne me semble pas juste que Magento soit livré avec tout un tas d'outils pour aider aux tests et je ne peux pas les utiliser parce que j'utilise PHP7. Je sais qu'il existe des tickets sur GitHub qui résolvent ce problème, mais je me demande comment la communauté écrit actuellement ses tests.

Existe-t-il un moyen d'écrire des tests unitaires dans Magento 2 afin que je n'aie pas à «rétrograder» mon code et que je puisse toujours utiliser les assistants intégrés de Magentos pour se moquer de tout ce que le gestionnaire d'objets touche? Ou est-ce même une mauvaise pratique d'utiliser le gestionnaire d'objets même dans vos tests unitaires?

Il me manque beaucoup de conseils / d'exemples sur la manière appropriée de tester à l'unité vos propres modules personnalisés.

Giel Berkers
la source
1
Quelle grande question.
camdixon

Réponses:

17

L'utilisation de la version PHPUnit fournie, même si elle est ancienne, est probablement la meilleure solution, car cela permettra d'exécuter les tests de tous les modules ensemble pendant le CI.

Je pense que l'écriture de tests d'une manière incompatible avec le cadre de test fourni réduit considérablement la valeur des tests.
Bien sûr, vous pouvez configurer CI pour exécuter vos tests avec une version différente de PHPUnit, mais cela ajoute beaucoup de complexité au système de construction.

Cela dit, je suis d'accord avec vous qu'il ne vaut pas la peine de prendre en charge PHP 5.6. J'utilise les indications de type scalaire PHP7 et les indications de type retour autant que possible (en plus, je ne me soucie pas de la place de marché).

Afin de contourner les limitations de la bibliothèque de simulation PHPUnit 4.1, il existe au moins deux solutions de contournement assez simples que j'ai utilisées dans le passé:

  1. Utilisez des classes anonymes ou régulières pour créer vos doubles de test, par exemple

    $fooIsFalseStub = new class extends Foo implements BarInterface() {
        public function __construct(){};
        public function isSomethingTrue(string $something): bool
        {
            return false;
        }
    };
  2. Utilisez le PHPUnit fourni mais une bibliothèque de simulation tierce qui peut être incluse via composer avec require-dev, par exemple https://github.com/padraic/mockery . Toutes les bibliothèques de simulation que j'ai essayées peuvent très facilement être utilisées avec n'importe quel framework de test, même une très ancienne version de PHPUnit comme 4.1.

Aucun de ceux-ci n'a d'avantage technique sur l'autre. Vous pouvez implémenter n'importe quelle double logique de test requise avec l'une ou l'autre.

Personnellement, je préfère utiliser des classes anonymes car cela n'ajoute pas au nombre de dépendances externes, et c'est aussi plus amusant de les écrire de cette façon.

EDIT :
Pour répondre à vos questions:

Est-ce que Mockery «résout» le problème de PHPUnit 4.1.0 ne pouvant pas gérer correctement les indications de type PHP7?

Oui, voir l'exemple ci-dessous.

Et quels sont les avantages des classes anonymes par rapport à la moquerie?

L'utilisation de classes anonymes pour créer des doubles de test est également une "simulation", ce n'est pas vraiment différent de l'utilisation d'une bibliothèque de simulation telle que PHPUnits ou Mockery ou autre.
Une maquette est juste sur un type spécifique de test double , quelle que soit la façon dont elle est créée.
Une petite différence entre l'utilisation de classes anonymes ou d'une bibliothèque de simulation est que les classes anonymes n'ont pas de dépendance de bibliothèque externe, car il s'agit simplement de PHP. Sinon, il n'y a ni avantages ni inconvénients. C'est simplement une question de préférence. Je l'aime parce qu'il illustre que les tests ne concernent pas un framework de test ou une bibliothèque de simulation, les tests consistent simplement à écrire du code qui exécute le système testé et valide automatiquement son fonctionnement.

Et que diriez-vous de mettre à jour la version de PHPUnit dans le fichier principal composer.json vers 5.3.5 (la dernière version prenant en charge PHP7 et ayant des méthodes de simulation publiques (requises par les propres tests de Magento 2))?

Cela peut être problématique car les tests dans d'autres modules et le noyau ne sont testés qu'avec PHPUnit 4.1, et en tant que tel, vous pouvez rencontrer de faux échecs dans CI. Je pense qu'il est préférable de s'en tenir à la version intégrée de PHPUnit pour cette raison. @maksek a déclaré qu'ils mettront à jour PHPUnit, mais il n'y a pas d'ETA pour cela.


Exemple de test avec un double test d'une classe qui nécessite PHP7 s'exécutant avec PHPUnit 4.1, en utilisant la bibliothèque Mockery:

<?php

declare(strict_types = 1);

namespace Example\Php7\Test\Unit;

// Foo is a class that will not work with the mocking library bundled with PHPUnit 4.1 
// The test below creates a mock of this class using mockery and uses it in a test run by PHPUnit 4.1
class Foo
{
    public function isSomethingTrue(string $baz): bool
    {
        return 'something' === $baz; 
    }
}

// This is another class that uses PHP7 scalar argument types and a return type.
// It is the system under test in the example test below.
class Bar
{
    private $foo;

    public function __construct(Foo $foo)
    {
        $this->foo = $foo;
    }

    public function useFooWith(string $s): bool
    {
        return $this->foo->isSomethingTrue($s);
    }
}

// This is an example test that runs with PHPUnit 4.1 and uses mockery to create a test double
// of a class that is only compatible with PHP7 and younger.
class MockWithReturnTypeTest extends \PHPUnit_Framework_TestCase
{
    protected function tearDown()
    {
        \Mockery::close();
    }

    public function testPHPUnitVersion()
    {
        // FYI to show this test runs with PHPUnit 4.1
        $this->assertSame('4.1.0', \PHPUnit_Runner_Version::id());
    }

    public function testPhpVersion()
    {
        // FYI this test runs with PHP7
        $this->assertSame('7.0.15', \PHP_VERSION);
    }

    // Some nonsensical example test using a mock that has methods with
    // scalar argument types and PHP7 return types.
    public function testBarUsesFoo()
    {
        $stubFoo = \Mockery::mock(Foo::class);
        $stubFoo->shouldReceive('isSomethingTrue')->with('faz')->andReturn(false);
        $this->assertFalse((new Bar($stubFoo))->useFooWith('faz'));
    }
}
Vinai
la source
Est-ce que Mockery «résout» le problème de PHPUnit 4.1.0 ne pouvant pas gérer correctement les indications de type PHP7? Et quels sont les avantages des classes anonymes par rapport à la moquerie? Et que diriez-vous de mettre à jour la version de PHPUnit dans le composer.jsonfichier principal vers 5.3.5 (la dernière version prenant en charge PHP7 et ayant des méthodes de simulation publiques (requises par les propres tests de Magento 2))? Tant de questions maintenant ...
Giel Berkers
Mise à jour de ma réponse en réponse à votre question @GielBerkers
Vinai
Merci pour votre excellente réponse. C'est totalement clair maintenant! Je pense que je vais essayer Mockery. Les classes anonymes semblent que je dois réinventer beaucoup de choses que Mockery propose déjà. J'ai d'abord voulu apprendre les bases de PHPUnit et continuer à partir de là. Je pense que le moment est venu.
Giel Berkers
Génial! Profitez de l'exploration de Mockery, une excellente bibliothèque. Pendant que vous y êtes, consultez peut-être aussi hamcrest, une bibliothèque d'assertions - elle sera installée avec Mockery automatiquement.
Vinai
3

À l'heure actuelle, Magento 2 prend en charge les prochaines versions de PHP:

"php": "~5.6.5|7.0.2|7.0.4|~7.0.6"

Cela signifie que tout le code écrit par l'équipe Magento fonctionne sur toutes les versions prises en charge.

Par conséquent, Magento Team n'utilise pas uniquement les fonctionnalités de PHP 7. Les fonctionnalités de PHP 5.6 peuvent être couvertes par PHPUnit 4.1.0.

En écrivant votre propre code, vous pouvez faire tout ce que vous voulez et écrire des tests comme vous le souhaitez. Mais je pense que vous ne pourrez pas publier votre extension sur Magento Marketplace en raison d'une violation des exigences.

yaronish
la source
En fait, PHPUnit 5.7 est pris en charge sur PHP 5.6, PHP 7.0 et PHP 7.1. PHPUnit 4.8 a été pris en charge sur PHP 5.3 - 5.6. Ainsi, même si Magento 2 prend en charge PHP 5.6, il pourrait toujours être mis à niveau vers PHPUnit 5.7.
Vinai