Testes de escrita
Os testes de escrita para Nette Tester são únicos, pois cada teste é um script PHP que pode ser executado de forma autônoma… Isto tem um grande potencial. Enquanto você escreve o teste, você pode simplesmente executá-lo para ver se funciona corretamente. Caso contrário, você pode facilmente entrar na IDE e procurar por um bug.
Você pode até mesmo abrir o teste em um navegador. Mas, acima de tudo – ao executá-lo, você realizará o teste. Você descobrirá imediatamente se ele passou ou não.
No capítulo introdutório, mostramos um teste realmente trivial de utilização da matriz PHP. Agora vamos criar nossa própria classe, que vamos testar, embora também seja simples.
Vamos começar com um layout típico de diretório para uma biblioteca ou projeto. É importante separar os testes do resto do código, por exemplo, devido à implantação, porque não queremos carregar os testes no servidor. A estrutura pode ser a seguinte:
├── src/ # code that we will test
│ ├── Rectangle.php
│ └── ...
├── tests/ # tests
│ ├── bootstrap.php
│ ├── RectangleTest.php
│ └── ...
├── vendor/
└── composer.json
E agora vamos criar arquivos individuais. Vamos começar 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 vamos criar um teste para isso. O nome do arquivo do teste deve corresponder à máscara *Test.php
ou
*.phpt
, escolheremos a variante RectangleTest.php
:
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
// oblongo geral
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # vamos verificar os resultados esperados
Assert::false($rect->isSquare());
Como você pode ver, métodos de afirmação como Assert::same()
são usados para
afirmar que um valor real corresponde a um valor esperado.
O último passo é criar o arquivo bootstrap.php
. Ele contém um código comum para todos os testes. Por
exemplo, classes auto-carga, configuração de ambiente, criação de diretório temporário, helpers e similares. Cada teste
carrega o bootstrap e presta atenção apenas aos testes. O bootstrap pode ser parecido:
<?php
require __DIR__ . '/vendor/autoload.php'; # Carga Composer autoloader
Tester\Environment::setup(); # inicialização do Tester Nette
// e outras configurações (apenas um exemplo, em nosso caso não são necessárias)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');
Este bootstrap assume que o Composer autoloader será capaz de carregar a classe Rectangle.php
também. Isto pode ser conseguido, por exemplo, ajustando a seção de
autocarga em composer.json
, etc.
Agora podemos executar o teste a partir da linha de comando como qualquer outro script PHP autônomo. A primeira execução revelará quaisquer erros de sintaxe, e se você não fez um erro de digitação, você vai ver:
$ php RectangleTest.php
OK
Se mudarmos no teste a declaração para falso Assert::same(123, $rect->getArea());
, isso acontecerá:
$ php RectangleTest.php Failed: 200.0 should be 123 in RectangleTest.php(5) Assert::same(123, $rect->getArea()); FAILURE
Ao escrever testes, é bom pegar todas as situações extremas. Por exemplo, se a entrada for zero, um número negativo, em outros casos um fio vazio, nulo, etc. Na verdade, isso força você a pensar e decidir como o código deve se comportar em tais situações. Os testes então corrigem o comportamento.
Em nosso caso, um valor negativo deve lançar uma exceção, o que verificamos com Assert::exception():
// a largura não deve ser um número negativo
Assert::exception(
fn() => new Rectangle(-1, 20),
InvalidArgumentException::class,
"A dimensão não deve ser negativa",
);
E acrescentamos um teste semelhante para a altura. Finalmente, testamos que isSquare()
retorna true
se as duas dimensões forem as mesmas. Tente escrever tais testes como um exercício.
Testes Bem-Arrangidos
O tamanho do arquivo de teste pode aumentar e tornar-se rapidamente desorganizado. Portanto, é prático agrupar áreas individuais testadas em funções separadas.
Primeiro, mostraremos uma variante mais simples, mas elegante, usando a função global test()
. O testador não a
cria automaticamente, para evitar uma colisão se você tivesse uma função com o mesmo nome em seu código. Ela só é criada
pelo método setupFunctions()
, que você chama no arquivo bootstrap.php
:
Tester\Environment::setup();
Tester\Environment::setupFunctions();
Usando esta função, podemos dividir bem o arquivo de teste em unidades nomeadas. Quando executado, as etiquetas serão exibidas uma após a outra.
<?php
use Tester\Assert;
require __DIR__ . '/bootstrap.php';
test('general oblong', function () {
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea());
Assert::false($rect->isSquare());
});
test('general square', function () {
$rect = new Rectangle(5, 5);
Assert::same(25.0, $rect->getArea());
Assert::true($rect->isSquare());
});
test('dimensions must not be negative', function () {
Assert::exception(
fn() => new Rectangle(-1, 20),
InvalidArgumentException::class,
);
Assert::exception(
fn() => new Rectangle(10, -1),
InvalidArgumentException::class,
);
});
Se você precisar executar o código antes ou depois de cada teste, passe-o para setUp()
ou
tearDown()
:
setUp(function () {
// código de inicialização a ser executado antes de cada teste()
});
A segunda variante é o objeto. Vamos criar o chamado TestCase, que é uma classe onde as unidades individuais são representadas por métodos cujos nomes começam com teste.
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);
}
}
// Métodos de teste de funcionamento
(new RectangleTest)->run();
Desta vez usamos uma anotação @throw
para testar as exceções. Veja o capítulo TestCase para mais informações.
Funções dos auxiliares
Nette Tester inclui várias classes e funções que podem facilitar os testes, por exemplo, ajudantes para testar o conteúdo de um documento HTML, para testar as funções de trabalhar com arquivos, e assim por diante.
Você pode encontrar uma descrição deles na página Ajudantes.
Anotação e testes de pular
A execução do teste pode ser afetada por anotações no comentário do phpDoc no início do arquivo. Por exemplo, pode parecer assim:
/**
* @phpExtension pdo, pdo_pgsql
* @phpVersion >= 7.2
*/
As anotações dizem que o teste só deve ser executado com PHP versão 7.2 ou superior e se as extensões PHP pdo e pdo_pgsql estiverem presentes. Estas anotações são controladas por um corredor de teste de linha de comando, que, se as condições não forem atendidas, pula o teste e o marca com a letra `s' – pulada. Entretanto, elas não têm efeito quando o teste é executado manualmente.
Para uma descrição das anotações, ver Anotações de teste.
O teste também pode ser pulado com base na própria condição com Environment::skip()
. Por exemplo, nós
pularemos este teste no Windows:
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
Tester\Environment::skip('Requires UNIX.');
}
Estrutura do diretório
Para bibliotecas ou projetos apenas ligeiramente maiores, recomendamos dividir o diretório de testes em subdiretórios de acordo com o espaço de nomes da classe testada:
└── tests/
├── NamespaceOne/
│ ├── MyClass.getUsers.phpt
│ ├── MyClass.setUsers.phpt
│ └── ...
│
├── NamespaceTwo/
│ ├── MyClass.creating.phpt
│ ├── MyClass.dropping.phpt
│ └── ...
│
├── bootstrap.php
└── ...
Você será capaz de executar testes a partir de um único espaço de nomes, ou seja, subdiretório:
tester tests/NamespaceOne
Estojos de borda
Um teste que não chama nenhum método de asserção é suspeito e será avaliado como errado:
Error: This test forgets to execute an assertion.
Se o teste sem fazer afirmações é realmente para ser considerado válido, ligue, por exemplo, para
Assert::true(true)
.
Também pode ser traiçoeiro usar exit()
e die()
para terminar o teste com uma mensagem de erro. Por
exemplo, exit('Error in connection')
termina o teste com um código de saída 0, o que sinaliza sucesso. Use
Assert::fail('Error in connection')
.