Nette Documentation Preview

syntax
Testes de escrita
*****************

.[perex]
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 |guide#What Makes Tester Unique?] 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 .{file: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 .{file:tests/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 |Assertions] 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 .{file:tests/bootstrap.php}
<?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');
```

.[note]
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 |best-practices:composer#autoloading] 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:

/--pre .[terminal]
$ php RectangleTest.php

<span style="color:#FFF; background-color:#090">OK</span>
\--

Se mudarmos no teste a declaração para falso `Assert::same(123, $rect->getArea());`, isso acontecerá:

/--pre .[terminal]
$ php RectangleTest.php

<span style="color: #FFF">Failed: </span><span style="color: #FF0">200.0</span><span style="color: #FFF"> should be </span><span style="color: #FF0">123</span>

<span style="color: #CCC">in </span><span style="color: #FFF">RectangleTest.php(5)</span><span style="color: #808080"> Assert::same(123, $rect->getArea());</span>

<span style="color: #FFF; background-color: #900">FAILURE</span>
\--


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() |Assertions#Assert::exception]:

```php .{file:tests/RectangleTest.php}
// 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 .[#toc-well-arranged-tests]
=================================================

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`:

```php .{file:tests/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 .{file:tests/RectangleTest.php}
<?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()`:

```php
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.

```php .{file:tests/RectangleTest.php}
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 |TestCase] para mais informações.


Funções dos auxiliares .[#toc-helpers-functions]
================================================

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 |Helpers].


Anotação e testes de pular .[#toc-annotation-and-skipping-tests]
================================================================

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:

```php .{file:tests/RectangleTest.php}
/**
 * @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 |running-tests], 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 |Test Annotations].

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:

```php
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
	Tester\Environment::skip('Requires UNIX.');
}
```


Estrutura do diretório .[#toc-directory-structure]
==================================================

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:

/--pre .[terminal]
tester tests/NamespaceOne
\--


Estojos de borda .[#toc-edge-cases]
===================================

Um teste que não chama nenhum método de asserção é suspeito e será avaliado como errado:

/--pre .[terminal]
<span style="color: #FFF; background-color: #900">Error: This test forgets to execute an assertion.</span>
\--

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')`.

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').