Qual é a maneira correta de testar o código PHP7 com o PHPUnit 4.1 no Magento 2?

23

Quando estou escrevendo meus módulos, estou tentando fornecer a eles testes de unidade para as partes mais críticas do aplicativo. No entanto, existem no momento (Magento 2.1.3) várias maneiras de como escrever testes de unidade:

Diferentes maneiras de testar

  • Integre-o bin/magento dev:tests:run unite execute-o sobre as configurações padrão do phpunit incluídas no Magento.
  • Escreva-os separadamente, execute-os vendor/bin/phpunit app/code/Vendor/Module/Test/Unite zombe de tudo o que é Magento.
  • Escreva-os separadamente, zombe de tudo e use uma versão global do sistema do PHPUnit.
  • Escreva-os separadamente, execute-os com vendor/bin/phpunit, mas ainda faça uso \Magento\Framework\TestFramework\Unit\Helper\ObjectManager.

Magento 2 e PHPUnit

Além disso, o Magento 2 vem com o PHPUnit 4.1.0, que não é compatível com o PHP7. Os nativos de dicas de tipo (como stringe `int) e a declaração de tipos de retorno em suas assinaturas gerarão erros. Por exemplo, uma interface / classe com uma assinatura de método como esta:

public function foo(string $bar) : bool;

... não poderá ser ridicularizado pelo PHPUnit 4.1.0. :-(

Minha situação atual

É devido a isso que agora estou escrevendo principalmente meus testes de unidade da terceira maneira (chamando uma versão PHPUnit global do sistema).

Na minha configuração, tenho o PHPUnit 5.6 instalado globalmente, para que eu possa resolver escrever o código PHP7 adequado, mas preciso fazer alguns ajustes. Por exemplo:

phpunit.xml tem que se parecer com isso para que eu possa usar o autoloader do compositor:

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

... e em todos os meus setUp()métodos, tenho a seguinte verificação para que eu possa escrever meus testes com compatibilidade direta:

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

Dessa forma, quando meus testes são executados pelo PHPUnit interno do Magentos, ele não gera um erro.

Minha pergunta

Então, eis a minha pergunta: essa é uma maneira 'saudável' de escrever testes de unidade? Porque não me parece correto que o Magento venha com um monte de ferramentas para ajudar nos testes e não posso usá-las porque estou usando o PHP7. Sei que existem tickets no GitHub que abordam esse problema, mas estou me perguntando como a comunidade está escrevendo seus testes.

Existe uma maneira de escrever testes de unidade no Magento 2 para que eu não precise 'rebaixar' meu código e ainda possa usar os auxiliares internos do Magentos para zombar de tudo o que o gerenciador de objetos toca? Ou é uma prática ruim usar o gerenciador de objetos mesmo em seus testes de unidade?

Estou perdendo muitas orientações / exemplos de como é a maneira correta de testar os seus próprios módulos personalizados.

Giel Berkers
fonte
1
Que ótima pergunta.
camdixon

Respostas:

17

Usar a versão PHPUnit incluída, mesmo que seja antiga, é provavelmente o melhor caminho a seguir, pois isso permitirá executar os testes de todos os módulos juntos durante o IC.

Eu acho que escrever testes de uma maneira que é incompatível com a estrutura de teste agrupada reduz bastante o valor dos testes.
É claro que você pode configurar o CI para executar seus testes com uma versão diferente do PHPUnit, mas isso adiciona muita complexidade ao sistema de compilação.

Dito isto, concordo com você que não vale a pena suportar o PHP 5.6. Eu uso dicas do tipo escalar PHP7 e dicas do tipo retorno, tanto quanto possível (além disso, não me importo com o mercado).

Para contornar as limitações da biblioteca de zombaria do PHPUnit 4.1, existem pelo menos duas soluções simples que eu usei no passado:

  1. Use classes anônimas ou regulares para criar duplas de teste, por exemplo

    $fooIsFalseStub = new class extends Foo implements BarInterface() {
        public function __construct(){};
        public function isSomethingTrue(string $something): bool
        {
            return false;
        }
    };
  2. Use o PHPUnit incluído, mas uma biblioteca de simulação de terceiros que pode ser incluída via compositor require-dev, por exemplo, https://github.com/padraic/mockery . Todas as bibliotecas de simulação que tentei podem ser usadas com facilidade em qualquer estrutura de teste, mesmo uma versão muito antiga do PHPUnit como a 4.1.

Nenhum deles tem vantagem técnica sobre o outro. Você pode implementar qualquer lógica dupla de teste necessária com qualquer um deles.

Pessoalmente, prefiro usar classes anônimas, porque isso não aumenta o número de dependências externas e também é mais divertido escrevê-las dessa maneira.

EDIT :
Para responder às suas perguntas:

Mockery 'resolve' o problema de o PHPUnit 4.1.0 não conseguir lidar adequadamente com as dicas do tipo PHP7?

Sim, veja o exemplo abaixo.

E quais são os benefícios das classes anônimas em detrimento da zombaria?

Usar classes anônimas para criar duplas de teste também é "zombaria", não é realmente diferente de usar uma biblioteca de zombaria, como PHPUnits ou Mockery ou outra.
Um mock é apenas um tipo específico de teste duplo , independentemente de como ele é criado.
Uma pequena diferença entre o uso de classes anônimas ou uma biblioteca de zombaria é que as classes anônimas não têm uma dependência de biblioteca externa, pois é apenas PHP. Caso contrário, não há benefícios ou desvantagens. É simplesmente uma questão de preferência. Gosto porque ilustra que o teste não se refere a nenhuma estrutura de teste ou biblioteca de simulação, o teste é apenas a escrita de código que executa o sistema em teste e valida automaticamente que ele funciona.

E que tal atualizar a versão do PHPUnit no arquivo principal composer.json para 5.3.5 (a versão mais recente que suporta o PHP7 e ter métodos de simulação públicos (exigidos pelos próprios testes do Magento 2))?

Isso pode ser problemático, pois os testes em outros módulos e no núcleo são testados apenas com o PHPUnit 4.1 e, como tal, você pode encontrar falhas falsas no IC. Eu acho que é melhor ficar com a versão empacotada do PHPUnit por esse motivo. O @maksek disse que atualizará o PHPUnit, mas não há ETA para isso.


Exemplo de teste com o dobro de uma classe que requer o PHP7 em execução no PHPUnit 4.1, usando a 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'));
    }
}
Vinai
fonte
Mockery 'resolve' o problema de o PHPUnit 4.1.0 não conseguir lidar adequadamente com as dicas do tipo PHP7? E quais são os benefícios das classes anônimas em detrimento da zombaria? E que tal atualizar a versão do PHPUnit no composer.jsonarquivo principal para 5.3.5 (a versão mais recente suportando o PHP7 e ter métodos públicos de zombaria (exigidos pelos próprios testes do Magento 2))? Então, muitas mais perguntas agora ...
Giel Berkers
Atualizei minha resposta em resposta à sua pergunta @GielBerkers
Vinai
Obrigado pela sua ótima resposta. Está totalmente claro agora! Acho que vou tentar Mockery então. Aulas anônimas parecem ter que reinventar muito o que Mockery já oferece. Eu queria primeiro aprender o básico do PHPUnit e seguir em frente. Eu acho que agora é a hora.
Giel Berkers
Ótimo! Desfrute de explorar Mockery, uma ótima biblioteca. Enquanto você estiver nisso, talvez verifique também o hamcrest, uma biblioteca de asserções - ela será instalada automaticamente com o Mockery.
Vinai
3

No momento, o Magento 2 suporta as próximas versões do PHP:

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

Isso significa que todo o código escrito pela Magento Team funciona em todas as versões suportadas.

Portanto, o Magento Team não usa apenas os recursos do PHP 7. Os recursos do PHP 5.6 podem ser cobertos com o PHPUnit 4.1.0.

Escrevendo seu próprio código, você pode fazer tudo o que quiser e escrever testes da maneira que desejar. Mas acredito que você não poderá publicar sua extensão no Magento Marketplace devido à violação de requisitos.

yaronish
fonte
Na verdade, o PHPUnit 5.7 é suportado no PHP 5.6, PHP 7.0 e PHP 7.1. O PHPUnit 4.8 foi suportado no PHP 5.3 - 5.6. Portanto, embora o Magento 2 suporte o PHP 5.6, ele ainda pode ser atualizado para o PHPUnit 5.7.
Vinai