Teste com o Ponytest
O PonyTest é a estrutura de teste da unidade Pony. Foi projetado para ser o mais simples possível de usar, tanto para o autor do teste unitário quanto para o usuário que executa os testes.
Cada teste de unidade é uma classe, com uma única função de teste. Por padrão, todos os testes são executados concomitantemente. Cada teste de teste é fornecido com um objeto auxiliar. Isto fornece funções de registro e asserção. Por padrão, as mensagens de registro só são mostradas para testes que falham.
Quando qualquer função de asserção falha, o teste é contado como uma falha. Entretanto, os testes também podem indicar falha ao levantar um erro na função de teste.
Exemplo de programa
Para usar o PonyTest simplesmente escreva uma classe para cada teste e um tipo TestList que informe o objeto PonyTest sobre os testes. Tipicamente a TestList será a Principal para o pacote.
O seguinte é um programa completo com 2 testes triviais.
use "ponytest"
actor Main is TestList
new create(env: Env) =>
PonyTest(env, this)
new make() =>
None
fun tag tests(test: PonyTest) =>
test(_TestAdd)
test(_TestSub)
class iso _TestAdd is UnitTest
fun name(): String => "addition"
fun apply(h: TestHelper) =>
h.assert_eq[U32](4, 2 + 2)
class iso _TestSub is UnitTest
fun name(): String => "subtraction"
fun apply(h: TestHelper) =>
h.assert_eq[U32](2, 4 - 2)
O construtor da make() não é necessário para este exemplo. Entretanto, ele permite uma fácil agregação de testes (ver abaixo), por isso é recomendável que todos os testes sejam fornecidos pela Mains.
Main.create() é chamado apenas para invocações de programas no pacote atual. Main.make() é chamado durante a agregação. Se assim desejar, código extra pode ser adicionado a qualquer um destes construtores para realizar tarefas adicionais.
Nomes de teste
Os testes são identificados por nomes, que são usados ao imprimir os resultados dos testes e na linha de comando para selecionar quais testes devem ser executados. Estes nomes são independentes dos nomes das classes de teste no código fonte do Pony. Cordas arbitrárias podem ser usadas para estes nomes, mas para grandes projetos, é fortemente recomendado o uso de um esquema hierárquico de nomenclatura para facilitar a seleção de grupos de testes.
Agregação
Muitas vezes é desejável executar uma coleção de testes unitários a partir de vários arquivos de fontes diferentes. Por exemplo, se vários pacotes dentro de um pacote têm seus próprios testes unitários, pode ser útil executar todos os testes para o pacote em conjunto.
Isto pode ser conseguido escrevendo uma classe de lista de testes agregados, que chama a função de lista para cada pacote. A seguir, um exemplo que agrega os testes dos pacotes foo e bar.
use "ponytest"
use foo = "foo"
use bar = "bar"
actor Main is TestList
new create(env: Env) =>
PonyTest(env, this)
new make() =>
None
fun tag tests(test: PonyTest) =>
foo.Main.make().tests(test)
bar.Main.make().tests(test)
As próprias classes de teste agregadas podem ser agregadas. Cada classe de lista de testes pode conter qualquer combinação de seus próprios testes e listas agregadas.
Testes longos
Os testes simples são executados dentro de uma única função. Quando essa função sai, seja retornando ou levantando um erro, o teste é concluído. Isto não é viável para testes que precisam utilizar atores.
Testes longos permitem uma conclusão tardia. Qualquer teste pode chamar long_test() em seu TestHelper para indicar que ele precisa continuar funcionando. Quando o teste é finalmente concluído, ele chama complete() em seu TestHelper.
A função complete() utiliza um parâmetro Bool para especificar se o teste foi um sucesso. Se qualquer afirmação falhar, o teste será considerado um fracasso, independentemente do valor deste parâmetro. Entretanto, complete() ainda deve ser chamado de complete().
Como os testes fracassados podem ser suspensos, um intervalo de tempo deve ser especificado para cada teste longo. Quando a função de teste sai, um timer é iniciado com o tempo limite especificado. Se este timer dispara antes de completar() é chamado de teste, o teste é marcado como uma falha e o timeout é informado.
Em um timeout, a função timed_out() é chamada no objeto de teste da unidade. Isto deve executar qualquer arrumação específica de teste que seja necessária para permitir que o programa saia. Não há necessidade de chamar complete() se ocorrer um timeout, embora não seja um erro fazer isso.
Note que o timeout só é relevante quando um teste é interrompido e, de outra forma, impediria a conclusão do programa de teste. Estabelecer um tempo limite muito longo em testes que não devem ser capazes de pendurar é perfeitamente aceitável e não fará com que o teste demore mais se for bem sucedido.
Os intervalos de tempo não devem ser usados como método padrão para detectar se um teste falhou.