Teste de unidade para uma biblioteca de computação científica

15

Eu já tinha um pouco de experiência com testes de unidade no que chamo (não pejorativamente) de projeto clássico de engenharia de software: um MVC, com uma GUI de usuário, um banco de dados, lógica de negócios na camada intermediária, etc. estou escrevendo uma biblioteca de computação científica em C # (sim, eu sei que o C # é muito lento, use C, não reinvente a roda e tudo isso, mas temos muitas pessoas fazendo computação científica na minha faculdade em C #, e nós meio que precisamos disso). É um projeto pequeno, em termos da indústria de desenvolvimento de software, porque estou escrevendo principalmente sozinho e, de tempos em tempos, com a ajuda de alguns colegas. Além disso, não sou pago por isso, e o mais importante é um projeto acadêmico. Quero dizer, espero que tenha qualidade profissional algum dia, porque estou pensando em abrir código-fonte,

De qualquer forma, o projeto está ficando grande (cerca de 18.000 linhas de código, o que eu acho que é grande para o projeto de um homem), e está saindo das minhas mãos. Estou usando o git para controle de origem e acho que fiquei bem, mas estou testando como a velha escola, ou seja, escrevendo aplicativos de console completos que testam grande parte do sistema, principalmente porque não tenho idéia de como para fazer testes de unidade nesse cenário, embora eu sinta que é o que devo fazer. O problema é que a biblioteca contém principalmente algoritmos, por exemplo, algoritmos de gráficos, classificadores, solucionadores numéricos, distribuições aleatórias etc. Eu simplesmente não sei como especificar casos de teste minúsculos para cada um desses algoritmos, e como muitos deles são estocástico Não sei como validar a correção. Para classificação, por exemplo, existem algumas métricas como precisão e recall, mas essas métricas são melhores para comparar dois algoritmos do que para julgar um único algoritmo. Então, como posso definir correção aqui?

Finalmente, há também o problema de desempenho. Eu sei que é um conjunto totalmente diferente de testes, mas o desempenho é um dos recursos importantes de uma ferramenta científica, em vez de satisfação do usuário ou outras métricas de engenharia de software.

Um dos meus maiores problemas é com estruturas de dados. O único teste que posso encontrar para uma árvore kd é um teste de estresse: insira muitos vetores aleatórios e, em seguida, realiza muitas consultas aleatórias e compara com uma pesquisa linear ingênua. O mesmo para desempenho. E com os otimizadores numéricos, tenho funções de benchmark que posso testar, mas, novamente, esse é um teste de estresse. Eu não acho que esses testes possam ser classificados como testes de unidade e, o mais importante, sejam executados continuamente, pois a maioria deles é bastante pesada. Mas também acho que esses testes precisam ser feitos, não posso simplesmente inserir dois elementos, abrir a raiz e sim, funciona para o caso 0-1-n.

Então, qual é a abordagem de teste (unitário) para esse tipo de software? E como organizo os testes de unidade e os pesados ​​em torno do ciclo de código-compilação-confirmação-integração?

Alejandro Piad
fonte

Respostas:

19

Eu diria que a computação científica é realmente muito adequada para testes de unidade. Você tem entradas e saídas definidas, pré e pós-condições claramente definidas que provavelmente não serão alteradas a cada duas semanas, de acordo com o capricho de algum designer, e sem requisitos de interface do usuário difíceis de testar.

Você cita alguns elementos que podem causar problemas; aqui está o que fazer com eles:

  • algoritmos aleatórios: existem duas possibilidades. Se você realmente deseja testar a randomização, basta agendar um grande número de repetições e afirmar que a proporção esperada de casos atende ao critério desejado, com margens de erro suficientemente grandes para que falhas espúrias nos testes sejam bastante raras. (Um conjunto de testes que sinaliza de maneira confiável os erros fantasmas é muito pior do que aquele que não detecta todos os defeitos imagináveis.) Como alternativa, use uma fonte aleatória configurável e substitua o relógio do sistema (ou o que você usar) por uma fonte determinística por dependência injeção para que seus testes se tornem totalmente previsíveis.
  • algoritmos definidos apenas em termos de precisão / recall: nada impede você de inserir um conjunto inteiro de casos de entrada e medir a precisão e o recall adicionando todos eles; é apenas uma questão de gerar semi-automaticamente esses casos de teste de forma eficiente, para que o fornecimento dos dados de teste não se torne o gargalo da sua produtividade. Como alternativa, especificar alguns pares de entrada / saída escolhidos criteriosamente e afirmar que o algoritmo calcula exatamente a entrada desejada também pode funcionar se a rotina for previsível o suficiente.
  • requisitos não funcionais: se a especificação realmente fornecer requisitos explícitos de espaço / tempo, você basicamente precisará executar conjuntos inteiros de pares de entrada / saída e verificar se o uso do recurso está em conformidade aproximadamente com o padrão de uso necessário. O truque aqui é calibrar sua própria classe de teste primeiro, para que você não avalie dez problemas com tamanhos diferentes que acabam sendo muito rápidos para medir ou que demoram tanto que a execução do conjunto de testes se torna impraticável. Você pode até escrever um pequeno gerador de casos de uso que cria casos de teste de tamanhos diferentes, dependendo da rapidez com que a PU é executada.
  • testes de execução rápida e lenta: sejam testes de unidade ou de integração, muitas vezes você acaba com muitos testes muito rápidos e alguns muito lentos. Como executar seus testes regularmente é muito valioso, eu costumo seguir a rota pragmática e separar tudo o que tenho em um conjunto rápido e lento, para que o mais rápido possa ser executado o mais rápido possível (certamente antes de cada confirmação), e não importa se dois testes 'semanticamente' pertencem um ao outro ou não.
Kilian Foth
fonte
+1. Muito obrigado, há muito, se uma visão da sua resposta. Apenas algumas perguntas: que tal algoritmos de otimização, como meta-heurística. Eu tenho várias funções de benchmark, mas tudo o que posso fazer com elas é comparar dois algoritmos diferentes. Também preciso encontrar um algoritmo de benchmark? O que significa um algoritmo genético estar correto? E como faço para testar cada uma das estratégias "parametrizáveis", como tipo de recombinação e mutação, etc?
Alejandro Piad
1
Para meta-heurísticas, eu gostaria de escolher alguns pares de E / S característicos, ou seja, os "famosos sucessos" da rotina, e verifico se o método (ou o melhor dos dois) realmente encontra essa solução. Os problemas de "escolha de cereja" que funcionam bem são obviamente um não-não na pesquisa de otimização, mas para testes de software que não são uma preocupação - você não está afirmando a qualidade do algoritmo, apenas a implementação correta. Essa é a única "correção" que você pode provar. Quanto rotinas multiplicam parametrizável: sim, eu tenho medo que requer uma quantidade combinatória de testes ...
Kilian Foth
Então, é como projetar uma referência trivial que todas as implementações corretas devem resolver exatamente? Existe uma maneira de provar a qualidade do algoritmo? Sei que não consigo definir um padrão de qualidade na maioria das vezes, mas pelo menos eu poderia desejar que nenhuma mudança diminua a qualidade alcançada?
Alejandro Piad