Pular para o conteúdo

Teste por propriedade

A regra em vez dos casos — e o contraexemplo encolhido até caber numa linha.

Em vez de escrever trinta casos à mão, descreve-se a regra que vale para todos e deixa-se a máquina procurar o contraexemplo.

dataforge
crucible "Propriedades":
    trial "inverter duas vezes volta ao original":
        Crucible.forall(
            Crucible.clusters(Crucible.integers(0, 100)),
            lambda xs: xs.reversed().reversed() is xs)

    trial "ordenar não muda o tamanho":
        Crucible.forall(
            Crucible.clusters(),
            lambda xs: len(sorted(xs)) is len(xs))

Os geradores#

GeradorProduz
Crucible.integers(min, max)inteiros na faixa
Crucible.floats(min, max)reais na faixa
Crucible.texts(tamanho)textos até o tamanho
Crucible.booleans()yes e no
Crucible.clusters(item, max)clusters do gerador dado
Crucible.vaults(valor, max)vaults
Crucible.one_of([a, b])um dos valores

Geradores se combinam:

dataforge
crucible "Combinando":
    trial "só pares":
        pares := Crucible.integers(0, 1000).mapear(lambda n: n * 2)
        Crucible.forall(pares, lambda n: n % 2 is 0)

    trial "com filtro":
        positivos := Crucible.integers(-100, 100)
            .filtrar(lambda n: n bigger 0)
        Crucible.forall(positivos, lambda n: n bigger 0)

O contraexemplo é encolhido#

Um contraexemplo de novecentos dígitos prova que há bug e não ajuda a achar. Quando a propriedade falha, o Crucible encolhe até o menor valor que ainda falha:

bash
$ dataforge crucible

  1) Listas > nunca passa de dois itens

     a propriedade falhou depois de 7 caso(s).
         menor contraexemplo: [0, 0, 0]

Sem o encolhimento, o contraexemplo seria a lista aleatória de 27 itens que falhou primeiro.

Quando vale#

  • Ida e volta. Serializar e desserializar deve devolver o original. Codificar e decodificar. Comprimir e descomprimir.
  • Invariantes. Ordenar não muda o tamanho. Somar zero não muda o valor. Uma soma é comutativa.
  • Comparação com uma implementação óbvia. A versão rápida deve concordar com a versão lenta e evidente.
dataforge
action soma_rapida(xs):
    yield sum(xs)

action soma_obvia(xs):
    total := 0
    cycle x in xs:
        total += x
    yield total

crucible "Equivalência":
    trial "as duas somas concordam":
        Crucible.forall(
            Crucible.clusters(Crucible.integers(-100, 100)),
            lambda xs: soma_rapida(xs) is soma_obvia(xs))