Escrevendo Testes
Escrever testes para o Nette Tester é único porque cada teste é um script PHP que pode ser executado independentemente. Isso esconde um grande potencial. Já quando você escreve o teste, pode simplesmente executá-lo e verificar se funciona corretamente. Se não, pode facilmente depurá-lo no IDE e procurar o erro.
Você pode até abrir o teste no navegador. Mas acima de tudo – ao executá-lo, você executa o teste. Você descobre imediatamente se ele passou ou falhou.
No capítulo introdutório, mostramos um teste realmente trivial de trabalho com array. Agora criaremos nossa própria classe que testaremos, embora também seja simples.
Começaremos com a estrutura de diretórios típica para uma biblioteca ou projeto. É importante separar os testes do resto do código, por exemplo, para implantação, pois não queremos enviar os testes para o servidor de produção. A estrutura pode ser, por exemplo, assim:
├── src/ # código que testaremos
│ ├── Rectangle.php
│ └── ...
├── tests/ # testes
│ ├── bootstrap.php
│ ├── RectangleTest.php
│ └── ...
├── vendor/
└── composer.json
E agora criaremos os arquivos individuais. Começaremos com a classe testada, que colocaremos no
arquivo src/Rectangle.php
<?php
class Rectangle
{
private float $width;
private float $height;
public function __construct(float $width, float $height)
{
if ($width < 0 || $height < 0) {
throw new InvalidArgumentException('The dimension must not be negative.');
}
$this->width = $width;
$this->height = $height;
}
public function getArea(): float
{
return $this->width * $this->height;
}
public function isSquare(): bool
{
return $this->width === $this->height;
}
}
E criaremos um teste para ela. O nome do arquivo de teste deve corresponder à máscara *Test.php
ou
*.phpt
, escolheremos, por exemplo, a variante RectangleTest.php
:
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
// retângulo geral
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # verificamos os resultados esperados
Assert::false($rect->isSquare());
Como você pode ver, os chamados métodos de asserção como
Assert::same()
são usados para confirmar que o valor real corresponde ao valor esperado.
Resta o último passo, que é o arquivo bootstrap.php
. Ele contém código comum a todos os testes, como
autoloading de classes, configuração de ambiente, criação de diretório temporário, funções auxiliares e similares. Todos
os testes carregam o bootstrap e se dedicam apenas aos testes. O bootstrap pode ter a seguinte aparência:
<?php
require __DIR__ . '/vendor/autoload.php'; # carrega o autoloader do Composer
Tester\Environment::setup(); # inicialização do Nette Tester
// e outras configurações (é apenas um exemplo, no nosso caso não são necessárias)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
O bootstrap fornecido pressupõe que o autoloader do Composer será capaz de carregar também a classe
Rectangle.php
. Isso pode ser alcançado, por exemplo, configurando a seção autoload em
composer.json
, etc.
Podemos agora executar o teste a partir da linha de comando como qualquer outro script PHP independente. A primeira execução revelará possíveis erros de sintaxe e, se não houver erro de digitação, será exibido:
$ php RectangleTest.php
OK
Se alterarmos a afirmação no teste para a falsa Assert::same(123, $rect->getArea());
, acontecerá
o seguinte:
$ php RectangleTest.php Failed: 200.0 should be 123 in RectangleTest.php(5) Assert::same(123, $rect->getArea()); FAILURE
Ao escrever testes, é bom cobrir todas as situações extremas. Por exemplo, quando a entrada for zero, um número negativo, em outros casos, talvez uma string vazia, null, etc. Na verdade, isso o força a pensar e decidir como o código deve se comportar nessas situações. Os testes então fixam o comportamento.
No nosso caso, um valor negativo deve lançar uma exceção, o que verificamos usando Assert::exception():
// a largura não pode ser negativa
Assert::exception(
fn() => new Rectangle(-1, 20),
InvalidArgumentException::class,
'The dimension must not be negative.',
);
E adicionamos um teste semelhante para a altura. Finalmente, testamos se isSquare()
retorna true
se
ambas as dimensões forem iguais. Tente escrever esses testes como exercício.
Testes mais claros
O tamanho do arquivo de teste pode aumentar e rapidamente se tornar confuso. Portanto, é prático agrupar as áreas testadas individuais em funções separadas.
Primeiro, mostraremos uma variante mais simples, mas elegante, usando a função global test()
. O Tester não a
cria automaticamente para evitar colisões caso você tenha uma função com o mesmo nome em seu código. Ela é criada pelo
método setupFunctions()
, que você chama no arquivo bootstrap.php
:
Tester\Environment::setup();
Tester\Environment::setupFunctions();
Usando esta função, podemos dividir o arquivo de teste de forma organizada em unidades nomeadas. Durante a execução, os rótulos serão exibidos sequencialmente.
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
test('retângulo geral', function () {
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());
Assert::false($rect->isSquare());
});
test('quadrado geral', function () {
$rect = new Rectangle(5, 5);
Assert::same(25.0, $rect->getArea());
Assert::true($rect->isSquare());
});
test('dimensões não podem ser negativas', function () {
Assert::exception(
fn() => new Rectangle(-1, 20),
InvalidArgumentException::class,
);
Assert::exception(
fn() => new Rectangle(10, -1),
InvalidArgumentException::class,
);
});
Se você precisar executar código antes ou depois de cada teste, passe-o para a função setUp()
ou
tearDown()
, respectivamente:
setUp(function () {
// código de inicialização que é executado antes de cada test()
});
A segunda variante é orientada a objetos. Criamos o chamado TestCase, que é uma classe onde as unidades individuais são
representadas por métodos cujos nomes começam com test
.
class RectangleTest extends Tester\TestCase
{
public function testGeneralOblong()
{
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());
Assert::false($rect->isSquare());
}
public function testGeneralSquare()
{
$rect = new Rectangle(5, 5);
Assert::same(25.0, $rect->getArea());
Assert::true($rect->isSquare());
}
/** @throws InvalidArgumentException */
public function testWidthMustNotBeNegative()
{
$rect = new Rectangle(-1, 20);
}
/** @throws InvalidArgumentException */
public function testHeightMustNotBeNegative()
{
$rect = new Rectangle(10, -1);
}
}
// Execução dos métodos de teste
(new RectangleTest)->run();
Para testar exceções, desta vez usamos a anotação @throws
. Você aprenderá mais no capítulo TestCase.
Funções Auxiliares
O Nette Tester contém várias classes e funções que podem facilitar, por exemplo, o teste do conteúdo de um documento HTML, o teste de funções que trabalham com arquivos e assim por diante.
Sua descrição pode ser encontrada na página Classes Auxiliares.
Anotações e Pular Testes
A execução dos testes pode ser influenciada por anotações na forma de comentário phpDoc no início do arquivo. Pode ter a seguinte aparência:
/**
* @phpExtension pdo, pdo_pgsql
* @phpVersion >= 7.2
*/
As anotações fornecidas dizem que o teste deve ser executado apenas com a versão PHP 7.2 ou superior e se as extensões
PHP pdo e pdo_pgsql estiverem presentes. Essas anotações são seguidas pelo executor de testes da linha de comando, que, caso as condições não sejam
atendidas, pula o teste e o marca na saída com a letra s
– skipped. No entanto, na execução manual do teste,
elas não têm efeito.
A descrição das anotações pode ser encontrada na página Anotações de Teste.
Um teste pode ser pulado com base no cumprimento de uma condição própria usando Environment::skip()
. Por
exemplo, isso pulará os testes no Windows:
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
Tester\Environment::skip('Requires UNIX.');
}
Estrutura de Diretórios
Recomendamos, para bibliotecas ou projetos um pouco maiores, dividir o diretório de testes em subdiretórios de acordo com o namespace da classe testada:
└── tests/
├── NamespaceOne/
│ ├── MyClass.getUsers.phpt
│ ├── MyClass.setUsers.phpt
│ └── ...
│
├── NamespaceTwo/
│ ├── MyClass.creating.phpt
│ ├── MyClass.dropping.phpt
│ └── ...
│
├── bootstrap.php
└── ...
Você poderá então executar testes de um único namespace ou subdiretório:
tester tests/NamespaceOne
Situações Especiais
Um teste que não chama nenhum método de asserção é suspeito e será avaliado como errôneo:
Error: This test forgets to execute an assertion.
Se realmente um teste sem chamadas de asserção deve ser considerado válido, chame, por exemplo,
Assert::true(true)
.
Também pode ser traiçoeiro usar exit()
e die()
para encerrar um teste com uma mensagem de erro. Por
exemplo, exit('Error in connection')
encerra o teste com o código de retorno 0, o que sinaliza sucesso. Use
Assert::fail('Error in connection')
.