Asserções

As asserções são usadas para confirmar que o valor real corresponde ao valor esperado. São métodos da classe Tester\Assert.

Escolha as asserções mais adequadas. É melhor Assert::same($a, $b) do que Assert::true($a === $b), porque em caso de falha exibe uma mensagem de erro significativa. No segundo caso, apenas false should be true, o que não nos diz nada sobre o conteúdo das variáveis $a e $b.

A maioria das asserções também pode ter uma descrição opcional no parâmetro $description, que será exibida na mensagem de erro se a expectativa falhar.

Os exemplos pressupõem a criação de um alias:

use Tester\Assert;

Assert::same ($expected, $actual, ?string $description=null)

$expected deve ser idêntico a $actual. O mesmo que o operador PHP ===.

Assert::notSame ($expected, $actual, ?string $description=null)

O oposto de Assert::same(), ou seja, o mesmo que o operador PHP !==.

Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)

$expected deve ser igual a $actual. Ao contrário de Assert::same(), a identidade dos objetos, a ordem dos pares chave ⇒ valor em arrays e números decimais marginalmente diferentes são ignorados, o que pode ser alterado definindo $matchIdentity e $matchOrder.

Os seguintes casos são idênticos do ponto de vista de equal(), mas não de same():

Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
	['first' => 11, 'second' => 22],
	['second' => 22, 'first' => 11],
);

No entanto, atenção, os arrays [1, 2] e [2, 1] não são iguais, porque diferem apenas na ordem dos valores, não nos pares chave ⇒ valor. O array [1, 2] também pode ser escrito como [0 => 1, 1 => 2] e, portanto, [1 => 2, 0 => 1] será considerado igual.

Além disso, em $expected pode-se usar as chamadas expectations.

Assert::notEqual ($expected, $actual, ?string $description=null)

O oposto de Assert::equal().

Assert::contains ($needle, string|array $actual, ?string $description=null)

Se $actual for uma string, deve conter a substring $needle. Se for um array, deve conter o elemento $needle (comparado estritamente).

Assert::notContains ($needle, string|array $actual, ?string $description=null)

O oposto de Assert::contains().

Assert::hasKey (string|int $needle, array $actual, ?string $description=null)

$actual deve ser um array e deve conter a chave $needle.

Assert::notHasKey (string|int $needle, array $actual, ?string $description=null)

$actual deve ser um array e não deve conter a chave $needle.

Assert::true ($value, ?string $description=null)

$value deve ser true, ou seja, $value === true.

Assert::truthy ($value, ?string $description=null)

$value deve ser verdadeiro, ou seja, satisfaz a condição if ($value) ....

Assert::false ($value, ?string $description=null)

$value deve ser false, ou seja, $value === false.

Assert::falsey ($value, ?string $description=null)

$value deve ser falso, ou seja, satisfaz a condição if (!$value) ....

Assert::null ($value, ?string $description=null)

$value deve ser null, ou seja, $value === null.

Assert::notNull ($value, ?string $description=null)

$value não deve ser null, ou seja, $value !== null.

Assert::nan ($value, ?string $description=null)

$value deve ser Not a Number. Para testar valores NAN, use exclusivamente Assert::nan(). O valor NAN é muito específico e as asserções Assert::same() ou Assert::equal() podem funcionar inesperadamente.

Assert::count ($count, Countable|array $value, ?string $description=null)

O número de elementos em $value deve ser $count. Ou seja, o mesmo que count($value) === $count.

Assert::type (string|object $type, $value, ?string $description=null)

$value deve ser do tipo especificado. Como $type, podemos usar uma string:

  • array
  • list – array indexado por uma sequência crescente de chaves numéricas a partir de zero
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • nome da classe ou diretamente o objeto, então $value instanceof $type deve ser verdadeiro

Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)

Ao chamar $callable, uma exceção da classe $class deve ser lançada. Se especificarmos $message, a mensagem da exceção também deve corresponder ao padrão e, se especificarmos $code, os códigos também devem corresponder estritamente.

O seguinte teste falhará porque a mensagem da exceção não corresponde:

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is to low',
);

Assert::exception() retorna a exceção lançada, permitindo testar também exceções aninhadas.

$e = Assert::exception(
	fn() => throw new MyException('Something is wrong', 0, new RuntimeException),
	MyException::class,
	'Something is wrong',
);

Assert::type(RuntimeException::class, $e->getPrevious());

Assert::error (string $callable, int|string|array $type, ?string $message=null)

Verifica se a função $callable gerou os erros esperados (ou seja, avisos, notices, etc.). Como $type, especificamos uma das constantes E_..., por exemplo, E_WARNING. E se especificarmos $message, a mensagem de erro também deve corresponder ao padrão. Por exemplo:

Assert::error(
	fn() => $i++,
	E_NOTICE,
	'Undefined variable: i',
);

Se o callback gerar múltiplos erros, devemos esperá-los todos na ordem exata. Nesse caso, passamos um array em $type:

Assert::error(function () {
	$a++;
	$b++;
}, [
	[E_NOTICE, 'Undefined variable: a'],
	[E_NOTICE, 'Undefined variable: b'],
]);

Se você especificar um nome de classe como $type, ele se comporta da mesma forma que Assert::exception().

Assert::noError (callable $callable)

Verifica se a função $callable não gerou nenhum aviso, erro ou exceção. É útil para testar pedaços de código onde não há outra asserção.

Assert::match (string $pattern, $actual, ?string $description=null)

$actual deve corresponder ao padrão $pattern. Podemos usar duas variantes de padrões: expressões regulares ou caracteres curinga.

Se passarmos uma expressão regular como $pattern, devemos usar ~ ou # para delimitá-la, outros delimitadores não são suportados. Por exemplo, um teste onde $var deve conter apenas dígitos hexadecimais:

Assert::match('#^[0-9a-f]$#i', $var);

A segunda variante é semelhante à comparação normal de strings, mas em $pattern podemos usar vários caracteres curinga:

  • %a% um ou mais caracteres, exceto caracteres de fim de linha
  • %a?% nenhum ou mais caracteres, exceto caracteres de fim de linha
  • %A% um ou mais caracteres, incluindo caracteres de fim de linha
  • %A?% nenhum ou mais caracteres, incluindo caracteres de fim de linha
  • %s% um ou mais espaços em branco, exceto caracteres de fim de linha
  • %s?% nenhum ou mais espaços em branco, exceto caracteres de fim de linha
  • %S% um ou mais caracteres, exceto espaços em branco
  • %S?% nenhum ou mais caracteres, exceto espaços em branco
  • %c% qualquer caractere único, exceto o caractere de fim de linha
  • %d% um ou mais dígitos
  • %d?% nenhum ou mais dígitos
  • %i% valor inteiro com sinal
  • %f% número de ponto flutuante
  • %h% um ou mais dígitos hexadecimais
  • %w% um ou mais caracteres alfanuméricos
  • %% o caractere %

Exemplos:

# Novamente, teste para número hexadecimal
Assert::match('%h%', $var);

# Generalização do caminho do arquivo e número da linha
Assert::match('Error in file %a% on line %i%', $errorMessage);

Assert::matchFile (string $file, $actual, ?string $description=null)

A asserção é idêntica a Assert::match(), mas o padrão é carregado do arquivo $file. Isso é útil para testar strings muito longas. O arquivo com o teste permanecerá claro.

Assert::fail (string $message, $actual=null, $expected=null)

Esta asserção sempre falha. Às vezes, isso é útil. Opcionalmente, podemos especificar também o valor esperado e o atual.

Expectations

Quando queremos comparar estruturas mais complexas com elementos não constantes, as asserções acima podem não ser suficientes. Por exemplo, testamos um método que cria um novo usuário e retorna seus atributos como um array. Não conhecemos o valor do hash da senha, mas sabemos que deve ser uma string hexadecimal. E sobre outro elemento, sabemos apenas que deve ser um objeto DateTime.

Nessas situações, podemos usar Tester\Expect dentro do parâmetro $expected dos métodos Assert::equal() e Assert::notEqual(), com os quais a estrutura pode ser facilmente descrita.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # esperamos um número inteiro
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # esperamos uma string que corresponda ao padrão
	'created_at' => Expect::type(DateTime::class), # esperamos uma instância da classe
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Com Expect, podemos realizar quase as mesmas asserções que com Assert. Ou seja, temos à disposição os métodos Expect::same(), Expect::match(), Expect::count(), etc. Além disso, podemos encadeá-los:

Expect::type(MyIterator::class)->andCount(5);  # esperamos MyIterator e número de elementos 5

Ou podemos escrever nossos próprios manipuladores de asserções.

Expect::that(function ($value) {
	# retornamos false se a expectativa falhar
});

Investigando asserções falhas

Quando uma asserção falha, o Tester exibe onde está o erro. Se compararmos estruturas mais complexas, o Tester criará dumps dos valores comparados e os salvará no diretório output. Por exemplo, em caso de falha do teste fictício Arrays.recursive.phpt, os dumps serão salvos da seguinte forma:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # valor atual
	│   └── Arrays.recursive.expected  # valor esperado
	│
	└── Arrays.recursive.phpt          # teste falho

O nome do diretório pode ser alterado através de Tester\Dumper::$dumpDir.