[Esta postagem também aparece em Blog do Dustin no github].
Recentemente, tive um Membase o usuário aponta uma sequência de operações que levou a um estado indesejável. Tenho muitos testes de mecanismo realmente bons que escrevi, mas não este caso:
O erro é bastante simples: a expiração é preguiçosa e acontece que não estou verificando a expiração nesse caso. Foi muito fácil
escrever esse teste, mas imediatamente me fez pensar sobre o que outros casos não estavam sendo executados.
Agora, sei que existem inúmeras ferramentas para ajudar nos testes. Eu já escrevi outra. Provavelmente passei cerca de uma hora escrevendo uma estrutura para escrever e executar todos os testes de que precisava. A diferença entre o que estou descrevendo aqui e, por exemplo, verificação rápida é que eu quero algo muito simples para expressar ações que esperam que o ambiente esteja em um determinado estado e que deixará o ambiente em outro estado. Em seguida, quero atingir todos os arranjos possíveis dessas ações para garantir que elas não interfiram umas nas outras de forma inesperada.

Isso aumenta muito rapidamente - especificamente o número de testes gerados para uma sequência de testes de n ações de a ações possíveis é de aproximadamente an.
Considere três ações definidas permutadas em sequências de duas. Isso resulta em nove possibilidades, conforme mostrado no diagrama à direita.
As ações no diagrama são definidas com a semântica do memcached em uma única chave, portanto adicionar tem como pré-requisito que o item não deve existem e del tem como pré-requisito que o item deve existem.
O teste gerado espera sucesso em cada caixa branca, falha em cada caixa vermelha e rastreia as mutações de estado esperadas para criar asserções.

Meu primeiro teste... hum, teste realizado com 11 ações em sequências de 4 ações. Tenho mais ações a realizar, mas 4 é um comprimento muito bom, portanto, o gráfico à esquerda demonstrará minha taxa de crescimento.
O mais impressionante é que ele apontou o bug original com bastante facilidade e outros dois bugs com pouco esforço.
Como posso usar isso?

Até o momento, a API é bastante simples e compostável. Há basicamente cinco classes (três são mostradas na imagem à direita).
Condição
A Condição é um chamável simples que é usado para pré-condições e pós-condições. Uma determinada classe não se importa com qual delas é usada e, em muitos casos, será usada para ambas.
Por exemplo, considere minha implementação de Não existe:
def __call__(autônomo, estado):
retorno TESTKEY não em estado
Efeito
Um Efeito altera nossa visão do estado (e, dependendo do driver, pode realmente fazer com que algo no mundo mude com ele). Por exemplo, o StoreEffect funciona da seguinte forma:
def __call__(autônomo, estado):
estado[TESTKEY] = ‘0’
Ação
Um Ação traz um Efeito e um ou mais Condição como condições pré e pós. Por exemplo, examinaremos duas ações, uma Adicionar ação e um Conjunto ação:
classe Set(Action):
efeito = StoreEffect()
postconditions = [Exists()]
class Add(Action):
condições prévias = [DoesNotExist()]
efeito = StoreEffect()
postconditions = [Exists()]
A parte interessante disso é que Set e Add têm semântica diferente, mas são expressos como composições diferentes das mesmas condições e efeitos.
Motorista
O driver é uma parte maior (sete métodos definidos!). Ele faz o suficiente para que eu possa fazer qualquer coisa, desde gerar um conjunto de testes em C para mecanismos memcached até realmente executar testes em um protocolo remoto.
Não descreverei todo o processo aqui, pois ele está documentado no fonte. No entanto, fecharei o ciclo mostrando um exemplo de código gerado por ele que demonstra o erro que não conseguimos encontrar em primeiro lugar:
ENGINE_HANDLE_V1 *h1) {
adicionar(h, h1);
assertHasNoError(); // valor é “0”
adicionar(h, h1);
assertHasError(); // valor é “0”
atraso(expiração+<extensão classe="mi">1);
assertHasNoError(); // valor é não definido
adicionar(h, h1);
assertHasNoError(); // valor é “0”
checkValue(h, h1, “0”);
retorno SUCESSO;
}
</span>
Isso demonstra a quantidade de informações que você conhece em cada etapa do caminho. A partir daí, podemos fazer todo tipo de coisa com nossos stubs (o atraso acima é implementado com o recurso de "viagem no tempo" do memcached testapp, por exemplo).
A partir daí, é menos empolgante. Nós fornecemos restrições, ele escreve testes e garante que haja outra área em que seja impossível para os usuários encontrarem algo que não tenhamos visto antes.