Como devo unidade de teste de um código-gerador?
-
08-06-2019 - |
Pergunta
Este é um difícil e pergunta aberta eu sei, mas eu pensei que eu iria jogá-lo no chão e ver se alguém tinha alguma sugestões interessantes.
Eu tenho desenvolvido um código de gerador que leva o nosso python interface para o nosso código C++ (gerado através GOLE) e gera o código necessário para expor isso como WebServices.Quando eu desenvolvi esse código que eu fiz usando TDD, mas encontrei meus testes a ser frágil como o inferno.Porque cada teste, essencialmente, queria verificar que, para um dado bit de entrada de código (o que acontece para ser um de cabeçalho C++) gostaria de obter um determinado bit de saída de código que eu escrevi um pequeno motor que lê as definições de teste a partir de XML arquivos de entrada e gera casos de teste a partir dessas expectativas.
O problema é que eu querer ir para modificar o código.Isso e o fato de que os testes de unidade em si são um:complexo, e b:frágil.
Então, eu estou tentando pensar em abordagens alternativas para este problema, e parece-me que eu sou, talvez, abordando-o de forma errada.Talvez eu preciso focar mais no resultado, ou seja:o código que gera, na verdade, executar e fazer o que eu quero que ele, ao invés do que, o código de olhar a maneira que eu quiser.
Alguém tem alguma experiência de algo semelhante a este que teria o cuidado de compartilhar?
Solução
Eu comecei a escrever um resumo de minha experiência com o meu próprio gerador de código e, em seguida, voltou-se e re-ler sua pergunta e descobri que você já tinha tocado com o mesmo problemas de si mesmo, o foco sobre os resultados da execução, em vez de o código de layout/visual.
O problema é que isto é difícil de teste, o código gerado pode não ser adequado para realmente executar no ambiente de teste de unidade do sistema, e como você codificar os resultados esperados?
Eu achei que você precisa para quebrar o gerador de código em pedaços menores e teste de unidade, aqueles.O teste de unidade de um completo gerador de código é mais parecido com o teste de integração de testes de unidade se você me perguntar.
Outras dicas
Lembre-se que "teste de unidade" é apenas um tipo de teste.Você deve ser capaz de teste de unidade o interno as peças do seu gerador de código.O que você está realmente procurando aqui é o sistema de teste de nível (de um.k.um.teste de regressão).Não é apenas semântica...existem diferentes modos de pensar, abordagens, expectativas, etc.É certamente mais trabalho, mas provavelmente você precisará morder a bala e configurar um fim-a-fim de o teste de regressão suite:fixo arquivos C++ - > GOLE interfaces -> módulos python -> conhecido saída.Você realmente quer para verificar o conhecido de entrada (fixo código C++) contra o resultado esperado (o que vem de fora da grande final programa Python).Verificar o código do gerador de resultados diretamente como seria diffing objeto de arquivos...
Sim, os resultados são a ÚNICA coisa que importa.A tarefa real é a escrita de uma estrutura que permite que o código gerado para executar de forma independente...gastar o seu tempo lá.
Se você estiver executando em *nux você pode considerar despejar o unittest quadro em favor de um script bash ou makefile.no windows, você pode considerar a construção de um shell app/função que executa o gerador e, em seguida, usa o código (como outro processo) e unittest que.
Uma terceira opção seria gerar o código e, em seguida, criar um aplicativo que inclui nada, mas um unittest.Novamente, você precisará de um shell script ou outros enfeites para executar este procedimento para cada entrada.Como codificar o comportamento esperado, ocorre-me que isso poderia ser feito da mesma forma como você faria para o código C++, usando apenas a interface gerada em vez de C++ um.
Só queria salientar que você ainda pode alcançar o fine-grained o teste, apesar de verificar os resultados:você pode testar individuais blocos de código aninhando-los dentro de alguns instalação e o código de verificação:
int x = 0;
GENERATED_CODE
assert(x == 100);
Desde que você tenha o código gerado montado a partir de pedaços menores, e as partes não são alterados com freqüência, pode ter mais condições de teste e um pouco melhor, e espero que evitar todos os seus testes de quebra de quando você alterar os detalhes de um bloco.
O teste de unidade é apenas que o teste de uma unidade específica.Então, se você estiver escrevendo uma especificação para a classe A, ele é ideal se a classe não tem o real concreto versões do classe B e C.
Ok, eu notei depois, a tag para esta questão inclui C++ / Python, mas os princípios são os mesmos:
public class A : InterfaceA
{
InterfaceB b;
InterfaceC c;
public A(InterfaceB b, InterfaceC c) {
this._b = b;
this._c = c; }
public string SomeOperation(string input)
{
return this._b.SomeOtherOperation(input)
+ this._c.EvenAnotherOperation(input);
}
}
Porque o Sistema acima Um injeta interfaces para sistemas B e C, pode-unidade de teste de sistema apenas Um, sem ter a real funcionalidade a ser executada por qualquer outro sistema.Este é o teste de unidade.
Aqui é uma inteligente forma de se aproximar de um Sistema, desde a criação até a conclusão, com um diferente Quando especificação para cada peça de comportamento:
public class When_system_A_has_some_operation_called_with_valid_input : SystemASpecification
{
private string _actualString;
private string _expectedString;
private string _input;
private string _returnB;
private string _returnC;
[It]
public void Should_return_the_expected_string()
{
_actualString.Should().Be.EqualTo(this._expectedString);
}
public override void GivenThat()
{
var randomGenerator = new RandomGenerator();
this._input = randomGenerator.Generate<string>();
this._returnB = randomGenerator.Generate<string>();
this._returnC = randomGenerator.Generate<string>();
Dep<InterfaceB>().Stub(b => b.SomeOtherOperation(_input))
.Return(this._returnB);
Dep<InterfaceC>().Stub(c => c.EvenAnotherOperation(_input))
.Return(this._returnC);
this._expectedString = this._returnB + this._returnC;
}
public override void WhenIRun()
{
this._actualString = Sut.SomeOperation(this._input);
}
}
Portanto, em conclusão, uma única unidade / especificação pode ter vários comportamentos, e a especificação cresce à medida que você desenvolver a unidade / sistema;e se o sistema sob teste depende de outros sistemas concretos dentro dela, assista fora.
Minha recomendação seria a de descobrir um conjunto conhecido de entrada-saída de resultados, tais como alguns casos mais simples que você já tem no local, e teste de unidade o código que é produzido.É inteiramente possível que a medida que você alterar o gerador que a exata seqüência de caracteres que é produzido pode ser ligeiramente diferente...mas o que realmente importa é se ela é interpretada da mesma maneira.Assim, se os resultados do teste como você poderia testar esse código se fosse seu recurso, você vai descobrir se é bem sucedido da maneira que você quer.
Basicamente, o que você realmente quer saber é se o seu gerador vai produzir o que você espera sem fisicamente teste todas as combinações possíveis (também:impossível).Garantindo que o gerador é consistente da maneira que você espera, você pode sentir-se melhor que o gerador vai ter sucesso em cada vez mais situações complexas.
Desta forma, você também pode construir um conjunto de testes de regressão (testes de unidade que precisa para se manter a funcionar correctamente).Isso ajudará você a se certificar de que as alterações para o seu gerador não quebrar outras formas de código.Quando você encontrar um bug que seus testes de unidade não pegar, você pode querer incluí-lo para evitar semelhante a ruptura.
Eu acho que você precisa testar o que você está gerando mais do que a forma como o gere.
No meu caso, o programa gera muitos tipos de código (C#, HTML, ESCS, JS, etc.) que compilar em um aplicativo web.A melhor maneira que eu encontrei para reduzir erros de regressão geral é testar a aplicação web em si, não pelo teste do gerador.
Não me interpretem mal, há ainda testes de unidade a verificação de alguns de que o gerador de código, mas o nosso maior estrondo para o nosso investimento tem sido testes de INTERFACE do usuário gerado aplicativo em si.
Desde que nós somos a geração que nós também gerar uma boa abstração em JS podemos usar programaticamente o teste o aplicativo.Seguimos algumas idéias descritas aqui: http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089
A grande parte é que realmente nos testes de seu sistema end-to-end, desde a geração de código para o que você realmente está gerando.Uma vez que um teste falhar, a sua fácil rastreá-lo de volta para onde o gerador quebrou.
É muito doce.
Boa sorte!