¿Cuál es la forma correcta de probar el código PHP7 con PHPUnit 4.1 en Magento 2?


23

Cuando escribo mis módulos, intento proporcionarles pruebas unitarias para las partes más críticas de la aplicación. Sin embargo, en este momento (Magento 2.1.3) hay varias formas de escribir pruebas unitarias:

Diferentes formas de prueba

  • Integrarla con bin/magento dev:tests:run unity ejecutarlo en la parte superior de la configuración por defecto que vienen con PHPUnit Magento.
  • Escríbalos por separado, ejecútelos vendor/bin/phpunit app/code/Vendor/Module/Test/Unity búrlate de todo lo que es Magento.
  • Escríbalos por separado, simule todo y use una versión global del sistema de PHPUnit.
  • Escríbalos por separado, ejecútelos vendor/bin/phpunit, pero aún utilícelos \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.

Magento 2 y PHPUnit

Además de eso, Magento 2 viene incluido con PHPUnit 4.1.0, que no es compatible con PHP7. Los nativos que insinúan el tipo (como stringe `int) y declaran los tipos de retorno en sus firmas arrojarán errores. Por ejemplo, una interfaz / clase con una firma de método como esta:

public function foo(string $bar) : bool;

... PHPUnit 4.1.0 no puede burlarse de él. :-(

Mi situacion actual

Debido a esto, ahora estoy escribiendo principalmente mis pruebas unitarias de la tercera manera (llamando a una versión de PHPUnit global del sistema).

En mi configuración, tengo PHPUnit 5.6 instalado globalmente, por lo que puedo resolver escribir el código PHP7 adecuado, pero tengo que hacer algunos ajustes. Por ejemplo:

phpunit.xml tiene que verse así para poder usar el autocargador de compositores:

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

... y en todos mis setUp()métodos, tengo la siguiente verificación para poder escribir mis pruebas con compatibilidad hacia adelante:

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

De esta manera, cuando mis pruebas son ejecutadas por PHPUnit incorporado de Magentos, no arroja un error.

Mi pregunta

Así que aquí está mi pregunta: ¿es esta una forma 'saludable' de escribir pruebas unitarias? Porque no me parece correcto que Magento venga incluido con un montón de herramientas para ayudar con las pruebas y no puedo usarlas porque estoy usando PHP7. Sé que hay entradas en GitHub que abordan este problema, pero me pregunto cómo la comunidad está escribiendo actualmente sus pruebas.

¿Hay alguna manera de escribir pruebas unitarias en Magento 2 para que no tenga que 'degradar' mi código y aún pueda usar los ayudantes integrados de Magentos para burlarse de todo lo que toca el administrador de objetos? ¿O es incluso una mala práctica usar el administrador de objetos incluso en las pruebas unitarias?

Me faltan muchas guías / ejemplos sobre cuál es la forma correcta de cómo probar unitariamente sus propios módulos personalizados.


1
Que gran pregunta.
camdixon

Respuestas:


17

Usar la versión de PHPUnit incluida, incluso si es antigua, es probablemente la mejor manera de hacerlo, ya que permitirá ejecutar las pruebas para todos los módulos juntos durante CI.

Creo que escribir pruebas de una manera que sea incompatible con el marco de prueba incluido reduce en gran medida el valor de las pruebas.
Por supuesto, puede configurar CI para ejecutar sus pruebas con una versión diferente de PHPUnit, pero eso agrega mucha complejidad al sistema de compilación.

Dicho esto, estoy de acuerdo con usted en que no vale la pena admitir PHP 5.6. Utilizo sugerencias de tipo escalar PHP7 y sugerencias de tipo de retorno tanto como sea posible (además, no me importa el mercado).

Para evitar las limitaciones de la biblioteca de simulación PHPUnit 4.1, hay al menos dos soluciones alternativas bastante sencillas que he usado en el pasado:

  1. Use clases anónimas o regulares para construir sus dobles de prueba, por ejemplo

    $fooIsFalseStub = new class extends Foo implements BarInterface() {
        public function __construct(){};
        public function isSomethingTrue(string $something): bool
        {
            return false;
        }
    };
  2. Use el PHPUnit incluido, pero una biblioteca de burla de terceros que se puede incluir a través del compositor require-dev, por ejemplo, https://github.com/padraic/mockery . Todas las bibliotecas de prueba que probé se pueden usar muy fácilmente con cualquier marco de prueba, incluso una versión muy antigua de PHPUnit como 4.1.

Ninguno de los dos tiene ninguna ventaja técnica sobre el otro. Puede implementar cualquier lógica doble de prueba requerida con cualquiera.

Personalmente, prefiero usar clases anónimas porque eso no aumenta la cantidad de dependencias externas, y también es más divertido escribirlas de esa manera.

EDITAR :
para responder a sus preguntas:

¿Mockery 'resuelve' el problema de que PHPUnit 4.1.0 no pueda manejar correctamente las sugerencias de tipo PHP7?

Sí, vea el ejemplo a continuación.

¿Y cuáles son los beneficios de las clases anónimas sobre la burla?

Usar clases anónimas para crear dobles de prueba también es "burlón", no es realmente diferente de usar una biblioteca burlona como PHPUnits o Mockery u otra.
Un simulacro es solo para un tipo específico de prueba doble , independientemente de cómo se cree.
Una pequeña diferencia entre usar clases anónimas o una biblioteca simulada es que las clases anónimas no tienen una dependencia de biblioteca externa, ya que es simplemente PHP. De lo contrario, no hay beneficios ni inconvenientes. Es simplemente una cuestión de preferencia. Me gusta porque ilustra que la prueba no se trata de ningún marco de prueba o biblioteca simulada, la prueba es solo escribir código que ejecute el sistema bajo prueba y valide automáticamente que funcione.

¿Y qué hay de actualizar la versión de PHPUnit en el archivo principal composer.json a 5.3.5 (la última versión compatible con PHP7 y tener métodos de burla públicos (requerido por las propias pruebas de Magento 2))?

Esto puede ser problemático ya que las pruebas en otros módulos y el núcleo solo se prueban con PHPUnit 4.1, y como tal, puede encontrar fallas falsas en CI. Creo que es mejor quedarse con la versión incluida de PHPUnit por esa razón. @maksek dijo que actualizarán PHPUnit, pero no hay ETA para eso.


Ejemplo de una prueba con una prueba doble de una clase que requiere PHP7 ejecutándose con PHPUnit 4.1, utilizando la biblioteca 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'));
    }
}

¿Mockery 'resuelve' el problema de que PHPUnit 4.1.0 no pueda manejar correctamente las sugerencias de tipo PHP7? ¿Y cuáles son los beneficios de las clases anónimas sobre la burla? ¿Y qué hay de actualizar la versión de PHPUnit en el composer.jsonarchivo principal a 5.3.5 (la última versión compatible con PHP7 y tener métodos de burla pública (requerido por las propias pruebas de Magento 2))? Tantas preguntas más ahora ...
Giel Berkers

Actualicé mi respuesta en respuesta a tu pregunta @GielBerkers
Vinai

Gracias por tu gran respuesta. ¡Está totalmente claro ahora! Creo que iré a probar Mockery entonces. Parece que tengo que volver a inventar muchas cosas anónimas que Mockery ya ofrece. Primero quería aprender los conceptos básicos de PHPUnit y seguir desde allí. Creo que ahora es el momento.
Giel Berkers

¡Excelente! Disfrute explorando Mockery, una gran biblioteca. Mientras lo hace, tal vez vea también Hamcrest, una biblioteca de afirmaciones: se instalará con Mockery automáticamente.
Vinai

3

En este momento Magento 2 es compatible con las próximas versiones de PHP:

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

Significa que todo el código escrito por Magento Team funciona en todas las versiones compatibles.

Por lo tanto, Magento Team no utiliza las características de PHP 7 únicamente. Las características de PHP 5.6 se pueden cubrir con PHPUnit 4.1.0.

Al escribir su propio código, puede hacer todo lo que quiera y escribir pruebas de la forma que desee. Pero creo que no podrá publicar su extensión en Magento Marketplace debido a una violación de los requisitos.


En realidad, PHPUnit 5.7 es compatible con PHP 5.6, PHP 7.0 y PHP 7.1. PHPUnit 4.8 fue compatible con PHP 5.3 - 5.6. Entonces, aunque Magento 2 es compatible con PHP 5.6, aún podría actualizarse a PHPUnit 5.7.
Vinai
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.