Teste Unitário no PHPUnit para aplicar em arquivos do Magento 2
Tags: php, braziliandevs, magento2, testing
Published: 2022-10-14 12:49:18

## PHPUnit

O **PHPUnit** é uma ferramenta de testes de unidade que disponibiliza um ecossistema necessário para realizar testes de forma automatizada em PHP. O PHPUnit está disponível para aplicações com o PHP a partir da versão 5.5.
Ele é instalado como uma dependência ao projeto e se adéqua com a estrutura do mesmo. Possui uma CLI (Interface de Linha de Comando) própria com opções de personalização para a forma de executar os testes.
O PHPUnit é baseado na ideia que o desenvolvedor deve encontrar erros no código-fonte o mais rápido possível. Semelhante a outros frameworks de teste unitário, ele usa *assertions* para verificar se o código-fonte está se comportando como o esperado.
Também suporta a declaração de dependências explícitas entre métodos de teste. Tais dependências não definem a ordem em que os métodos de teste devem ser executados, mas permitem o retorno de uma instância do ambiente do teste por um produtor e a passagem dele para os consumidores dependentes.

Para executar os testes com o PHPUnit, deve-se chamar o comando `phpunit` que estará dentro de `./vendor/bin/phpunit` e referenciar o arquivo `phpunit.xml` com as configurações que serão utilizados para a execução dos testes.
O Magento 2 por padrão já possui um arquivo `phpunit.xml` que pode ser utilizado, ele se encontra no caminho `./dev/tests/unit/phpunit.xml.dist`. O terceiro argumento passado no comando para a execução de testes será o caminho do módulo.

```bash
./vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist app/code/{Vendor}/{Module}
./vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist vendor/{vendor}/{module}
```

### phpunit.xml

Caso seja necessário personalizar a execução de testes (**exemplo:** ignorar a pasta API do módulo) é possível criar um arquivo `phpunit.xml` no próprio módulo e configurar para que os testes sejam executados de acordo com a configuração deste arquivo.

```bash
./vendor/bin/phpunit -c app/code/{Vendor}/{Module}/phpunit.xml.dist app/code/{Vendor}/{Module}
./vendor/bin/phpunit -c vendor/{vendor}/{module}/phpunit.xml.dist vendor/{vendor}/{module}
```

- [O Arquivo de Configuração XML](https://phpunit.readthedocs.io/pt_BR/latest/configuration.html)

---

## Código base para a implantação

Para escrever testes com o PHPUnit no Magento 2 basta criar a estrutura de pastas `\{Vendor}\{Module}\Test\Unit\{ClassDirectory}\{ClassName}Test` estender a classe que `\PHPUnit\Framework\TestCase`. O PHPUnit localiza os métodos de teste pelo prefixo `test` no nome do método, e neste contém asserções para validar que o método retorne um valor real equivalente ao esperado.

**Classe a ser testada:**

```php
<?php

namespace {Vendor}\{Module}\{ClassDirectory};

class {ClassName}
{
    public function {methodName}(): {type}
    {
        // Code here
    }
}
```

**Classe de teste:**

```php
<?php

namespace {Vendor}\{Module}\Test\Unit\{ClassDirectory};

use PHPUnit\Framework\TestCase;

class {ClassName}Test extends TestCase
{
    public function test{MethodName}(): {type}
    {
        // Code here
    }
}
```

### Asserções

Asserções são métodos desenvolvidos pelo PHPUnit para assegurar que o valor de um teste é um valor esperado, ou seja, sãos métodos que executam a unidade e certificam que o resultado gerado no teste é o esperado com sucesso ou falham.

Os métodos da classe `\PHPUnit\Framework\TestCase` que começam com o prefixo *assert*, são parte importante dos casos de teste e avaliam se certas condições, decisivas para determinar o sucesso ou falha do teste, geram um resultado esperado.

```php
<?php

namespace {Vendor}\{Module}\Test\Unit\{ClassDirectory};

use PHPUnit\Framework\TestCase;

class {ClassName}Test extends TestCase
{
    public function test{MethodName}(): {type}
    {
        // Code here
        $this->assert{Name}({condition});
    }
}
```

É possível verificar a documentação com todas as asserções fornecidas pelo PHPUnit no link “[Asserções do PHPUnit](https://phpunit.readthedocs.io/pt_BR/latest/assertions.html#uso-estatico-vs-nao-estatico-de-metodos-de-assercao)“.

### Exceções

Usando o método `expectException()`, o PHPUnit verifica se durante a exceção do teste é lançada uma exceção dentro do código testado. Além do método `expectException()` os métodos  `expectExceptionCode()`, `expectExceptionMessage()` e `expectExceptionMessageRegExp()` existem para configurar expectativas de exceções lançadas pelo código sob teste.

```php
<?php

namespace {Vendor}\{Module}\Test\Unit\{ClassDirectory};

use PHPUnit\Framework\TestCase;

class {ClassName}Test extends TestCase
{
    public function test{MethodName}(): {type}
    {
        $this->expectException(\Exception::class);
        // Code here
    }
}
```

### Provedor de Dados

Com um método provedor de dados (*data provider*) o PHPUnit permite que o um método de teste seja executado em diferentes cenários, ou seja, consiste em preparar os dados de entradas para que o teste seja executado diversas vezes com dados diferentes.

Um método provedor de dados deve ser do tipo `public`, possuir a anotação `@dataProvider` como o nome do método e retornar um *array* de *arrays*. Cada item do *array* de retorno, o método de teste chamara como argumento.

```php
<?php

namespace {Vendor}\{Module}\Test\Unit\{ClassDirectory};

use PHPUnit\Framework\TestCase;

class {ClassName}Test extends TestCase
{
    /**
     * @dataProvider {dataProviderName}
     */
    public function test{MethodName}({type} $data): {type}
    {
        // Code to test here
    }

    public function {dataProviderName}(): array
    {
        // Code to provide different input data

        return [
          [$data1],
          [$data2],
          [$data3],
          ...
          [$dataN]
        ];
    }
}
```

### Métodos setUp e tearDown

O PHPUnit suporta compartilhamento do código de configuração. Os métodos-modelo `setUp()` e `tearDown()` são executados uma vez para cada método de teste (e em novas instâncias) da classe do caso de teste. Ambos os métodos devem ser do tipo `protected` e não possuir nenhum dado de retorno (`void`).

Antes que um método seja executado, um método modelo chamado `setUp()` é invocado. O método `setUp()` é onde os objetos que serão alvo dos testes são criados.

Uma vez que o método de teste tenha terminado sua execução, seja bem-sucedido ou falho, outro método modelo é invocado `tearDown()`. O método `tearDown()` é onde os objetos que foram alvo dos testes são limpos, normalmente, implementa-se o método `tearDown()` ao alocar recursos externos como arquivos ou sockets no `setUp()`.

```php
<?php

namespace {Vendor}\{Module}\Test\Unit\{ClassDirectory};

use PHPUnit\Framework\TestCase;

class {ClassName}Test extends TestCase
{
    protected function setUp(): void
    {
        // Code to set up test objects
    }

    protected function tearDown(): void
    {
        // Code to tear down test object
    }

    public function test{MethodName}(): {type}
    {
        // Code to test here
    }
}
```

### Dublê de Testes

É possível verificar a documentação com tudo sobre os dublês de testes do  PHPUnit no link “[Dublês de Testes do PHPUnit](https://phpunit.readthedocs.io/pt_BR/latest/test-doubles.html)“.

Os métodos `createMock($type)` e `getMockBuilder($type)` fornecidos pelo PHPUnit podem ser usados em um teste para gerar automaticamente um objeto que possa atuar como um dublê de teste para a classe original especificada. Esse objeto de dublê de teste pode ser usado em cada contexto onde um objeto da classe original é esperado ou requerido.

`$this->createMock({Class}::class)`:  este método retorna um objeto de dublê de teste para o tipo especificado (interface ou classe). A criação desse dublê de teste é realizada usando os padrões de boas práticas (os métodos `__construct()` e `__clone()` da classe original não são executados) e os argumentos passados para um método do dublê de teste não serão clonados.

`$this->getMockBuilder({Class}::class)`: este método serve para customizar a geração do dublê de teste usando uma interface fluente. Por padrão, todos os métodos da classe original são substituídos com uma implementação simulada que apenas retorna `null` (sem chamar o método original).  É possível configurar essas implementações simuladas para retornar um valor quando chamadas.

- `setMethods(array $methods)`: pode ser chamado no objeto `getMockBuilder()` para especificar os métodos que devem ser substituídos com um dublê de teste configurável. O comportamento dos outros métodos não muda. Se você chamar `setMethods(null)`, então nenhum dos métodos serão substituídos (utilizado até a versão 7.3.X do PHP);
- `onlyMethods(array $methods)`: pode ser chamado no objeto `getMockBuilder()` para especificar os métodos que devem ser substituídos por um dublê de teste configurável. O comportamento dos outros métodos não muda. Cada método deve existir na classe que está sendo criada como dublê de teste (utilizado a partir da versão 7.4.X do PHP);
- `addMethods(array $methods)`: pode ser chamado no objeto `getMockBuilder()` para especificar os métodos que não existem (ainda) na classe de dublê de teste. O comportamento dos outros métodos permanece o mesmo (utilizado a partir da versão 7.4.X do PHP);
- `setConstructorArgs(array $args)`: pode ser chamado para fornecer um vetor de parâmetros que é passado ao construtor da classe original (que por padrão não é substituído com uma implementação falsa);
- `setMockClassName($name)`: pode ser usado para especificar um nome de classe para a classe de dublê de teste gerada;
- `disableOriginalConstructor()`: pode ser usado para desabilitar a chamada ao construtor da classe original;
- `disableOriginalClone()`: pode ser usado para desabilitar a chamada ao construtor clone da classe original;
- `disableAutoload()`: pode ser usado para desabilitar o `__autoload()` durante a geração da classe de dublê de teste.

`$this->method('{methodName}')`:  o método `method('{methodName}')` recebe o nome do método que será testado na classe é tera um retorno `void`.

`$this->method('{methodName}')->willReturn('{valueToReturn}')`:  o método `method('{methodName}')` recebe o nome do método que será testado na classe. O método `willReturn('{valueToReturn}')` retorna um valor simples para corresponder ao teste, esta sintaxe é o mesmo que `$this->method('{methodName}')->will($this->returnValue($value))`.

`$this->method('{methodName}')->returnSelf()`: este método retorna o próprio objeto de dublê de teste.

`$objectMock->expects($this->once())->method('{methodName}')->with($this->equalTo('something'))`: verifica se um método foi chamado, e com quais argumentos foi chamado, os métodos `expects()` e `with()` são para especificar como essa interação deve se parecer.

O método `with()` pode receber qualquer número de argumentos, correspondendo ao número de argumentos sendo falsos. É possível especificar restrições mais avançadas do que uma simples igualdade no argumento do método.