Como você escreve testes de unidade para código com resultados difíceis de prever?

122

Freqüentemente trabalho com programas muito numéricos / matemáticos, em que o resultado exato de uma função é difícil de prever antecipadamente.

Ao tentar aplicar TDD com esse tipo de código, muitas vezes acho que escrever o código em teste é significativamente mais fácil do que escrever testes unitários para esse código, porque a única maneira que sei de encontrar o resultado esperado é aplicar o próprio algoritmo ( seja na minha cabeça, no papel ou pelo computador). Isso parece errado, porque estou usando efetivamente o código em teste para verificar meus testes de unidade, e não o contrário.

Existem técnicas conhecidas para escrever testes unitários e aplicar TDD quando o resultado do código em teste é difícil de prever?

Um exemplo (real) de código com resultados difíceis de prever:

Uma função weightedTasksOnTime que, dada uma quantidade de trabalho por dia workPerDay no intervalo (0, 24), a hora atual initialTime > 0 e uma lista de tarefas taskArray ; um tempo para concluir a propriedade time > 0, data de vencimento due e valor de importância importance ; retorna um valor normalizado no intervalo [0, 1] representando a importância das tarefas que podem ser concluídas antes de seu due date se cada tarefa for concluída na ordem dada por taskArray , começando em initialTime .

O algoritmo para implementar esta função é relativamente simples: iterar sobre tarefas em taskArray . Para cada tarefa, adicione time a initialTime . Se a nova hora < due , adicione importance a um acumulador. O tempo é ajustado por workPerDay inversa. Antes de retornar o acumulador, divida pela soma das importâncias da tarefa para normalizar.

function weightedTasksOnTime(workPerDay, initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time * (24 / workPerDay)
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator / totalImportance(taskArray)
}

Eu acredito que o problema acima pode ser simplificado, mantendo seu núcleo, removendo workPerDay e o requisito de normalização, para dar:

function weightedTasksOnTime(initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator
}

Esta questão aborda situações em que o código em teste não é uma reimplementação de um algoritmo existente. Se o código é uma reimplementação, é intrinsecamente fácil prever os resultados, porque as implementações confiáveis existentes do algoritmo atuam como um oráculo de teste natural.

    
por PaintingInAir 08.10.2018 / 00:06
fonte

15 respostas

250

Existem duas coisas que você pode testar em um código difícil de testar. Primeiro, os casos degenerados. O que acontece se você não tiver elementos em sua matriz de tarefas, ou apenas um, ou dois, mas um estiver além da data de vencimento, etc. Qualquer coisa que seja mais simples que seu problema real, mas ainda razoável calcular manualmente.

O segundo é o teste de sanidade. Estas são as verificações que você faz onde você não sabe se uma resposta é correta , mas você definitivamente saberia se é errado . Estas são coisas como o tempo deve avançar, os valores devem estar em um intervalo razoável, porcentagens devem somar até 100, etc.

Sim, isso não é tão bom quanto um teste completo, mas você ficaria surpreso com a frequência com que você estraga as verificações de integridade e casos degenerados, o que revela um problema no seu algoritmo completo.

    
por 08.10.2018 / 05:08
fonte
80

Eu costumava escrever testes para software científico com saídas difíceis de prever. Nós usamos muito as Relações Metamórficas. Essencialmente, existem coisas que você sabe sobre como o seu software deve se comportar mesmo que você não saiba saídas numéricas exatas.

Um exemplo possível para o seu caso: se você diminuir a quantidade de trabalho que pode fazer todos os dias, a quantidade total de trabalho que você pode fazer será, na melhor das hipóteses, a mesma, mas provavelmente diminuirá. Então, execute a função para um número de valores de workPerDay e certifique-se de que a relação é válida.

    
por 08.10.2018 / 14:03
fonte
38

As outras respostas têm boas ideias para desenvolver testes para casos de borda ou erro. Para os outros, usar o algoritmo em si não é ideal (obviamente), mas ainda é útil.

Ele detectará se o algoritmo (ou dados do qual ele depende) mudou

Se a alteração for um acidente, você poderá reverter um commit. Se a mudança foi deliberada, você precisa rever o teste da unidade.

    
por 08.10.2018 / 05:26
fonte
19

Da mesma forma que você escreve testes de unidade para qualquer outro tipo de código:

  1. Encontre alguns casos de teste representativos e teste-os.
  2. Encontre casos de borda e teste-os.
  3. Encontre condições de erro e teste-as.

A menos que seu código envolva algum elemento aleatório ou não seja determinístico (ou seja, ele não produzirá a mesma saída dada a mesma entrada), ele será testável por unidade.

Evite efeitos colaterais ou funções afetadas por forças externas. Funções puras são mais fáceis de testar.

    
por 08.10.2018 / 00:24
fonte
17

Atualização devido a comentários postados

A resposta original foi removida por questão de brevidade - você pode encontrá-la no histórico de edições.

PaintingInAir For context: as an entrepreneur and academic, most of the algorithms I design are not requested by anyone other than myself. The example given in the question is part of a derivative-free optimizer to maximize the quality of an ordering of tasks. In terms of how I described the need for the example function internally: "I need an objective function to maximize the importance of tasks that are completed on time". However, there still seems to be a large gap between this request and the implementation of unit tests.

Primeiro, um TL; DR para evitar uma resposta demorada:

Think of it this way:
A customer enters McDonald's, and asks for a burger with lettuce, tomato and hand soap as toppings. This order is given to the cook, who makes the burger exactly as requested. The customer receives this burger, eats it, and then complains to the cook that this is not a tasty burger!

This is not the cook's fault - he's only doing what the customer explicitly asked. It's not the cook's job to check if the requested order is actually tasty. The cook simply creates that which the customer orders. It's the customer's responsibility of ordering something that they find tasty.

Similarly, it's not the developer's job to question the correctness of the algorithm. Their only job is to implement the algorithm as requested.
Unit testing is a developer's tool. It confirms that the burger matches the order (before it leaves the kitchen). It does not (and should not) try to confirm that the ordered burger is actually tasty.

Even if you are both the customer and the cook, there is still a meaningful distinction between:

  • I did not prepare this meal properly, it was not tasty (= cook error). A burnt steak is never going to taste good, even if you like steak.
  • I prepared the meal properly, but I don't like it (= customer error). If you don't like steak, you'll never like eating steak, even if you cooked it to perfection.

A questão principal aqui é que você não está fazendo uma separação entre o cliente e o desenvolvedor (e o analista - embora essa função também possa ser representada por um desenvolvedor).

Você precisa distinguir entre testar o código e testar os requisitos de negócios.

Por exemplo, o cliente quer que ele funcione como [this] . No entanto, o desenvolvedor não entende bem e ele escreve o código que faz [that] .

O desenvolvedor irá, portanto, escrever testes de unidade que testam se [que] funciona como esperado. Se ele desenvolveu o aplicativo corretamente, seus testes de unidade passarão mesmo que o aplicativo não faça [this] , que o cliente estava esperando.

Se você deseja testar as expectativas do cliente (os requisitos de negócios), isso precisa ser feito em uma etapa separada (e posterior).

Um fluxo de trabalho de desenvolvimento simples para mostrar quando esses testes devem ser executados:

  • O cliente explica o problema que deseja resolver.
  • O analista (ou desenvolvedor) registra isso em uma análise.
  • O desenvolvedor escreve o código que faz o que a análise descreve.
  • O desenvolvedor testa seu código (testes de unidade) para ver se ele seguiu a análise corretamente
  • Se os testes da unidade falharem, o desenvolvedor volta a se desenvolver. Isso faz um loop indefinidamente, até que os testes da unidade sejam aprovados.
  • Agora, com uma base de código testada (confirmada e passada), o desenvolvedor cria o aplicativo.
  • O aplicativo é entregue ao cliente.
  • O cliente agora testa se a aplicação que ele recebeu realmente resolve o problema que ele buscou resolver (testes de controle de qualidade).

Você pode se perguntar qual é o objetivo de fazer dois testes separados quando o cliente e o desenvolvedor são o mesmo. Como não há "hand off" do desenvolvedor para o cliente, os testes são executados um após o outro, mas ainda são etapas separadas.

  • Testes de unidade são uma ferramenta especializada que ajuda você a verificar se o estágio de desenvolvimento está concluído.
  • Os testes de controle de qualidade são feitos por usando o aplicativo .

Se você quiser testar se o algoritmo está correto, não faz parte do trabalho do desenvolvedor . Essa é a preocupação do cliente, e o cliente testará isso usando usando o aplicativo.

Como empreendedor e acadêmico, você pode estar perdendo uma distinção importante aqui, que destaca as diferentes responsabilidades.

  • Se o aplicativo não obedecer ao que o cliente havia inicialmente solicitado, as alterações subsequentes no código geralmente são feitas sem custo ; desde que é um erro do desenvolvedor. O desenvolvedor cometeu um erro e deve pagar o custo de retificá-lo.
  • Se o aplicativo fizer o que o cliente havia inicialmente solicitado, mas o cliente tiver mudado de ideia (por exemplo, você decidiu usar um algoritmo diferente e melhor), as alterações no código base serão cobradas ao cliente. cliente , uma vez que não é culpa do desenvolvedor que o cliente pediu algo diferente do que eles querem agora. É responsabilidade do cliente (custo) mudar de ideia e, portanto, os desenvolvedores gastam mais esforço para desenvolver algo que não foi previamente acordado.
por 08.10.2018 / 08:13
fonte
7

Teste de propriedade

Às vezes, as funções matemáticas são melhor atendidas por "Teste de propriedade" do que pelo teste de unidade tradicional baseado em exemplo. Por exemplo, imagine que você está escrevendo testes de unidade para algo como uma função "multiplicar" de inteiro. Embora a função em si possa parecer muito simples, se for a única maneira de multiplicar, como você a testa completamente sem a lógica da própria função? Você pode usar tabelas gigantes com entradas / saídas esperadas, mas isso é limitado e propenso a erros.

Nesses casos, você pode testar propriedades conhecidas da função, em vez de procurar resultados esperados específicos. Para multiplicação, você pode saber que multiplicar um número negativo e um número positivo deve resultar em um número negativo, e que multiplicar dois números negativos deve resultar em um número positivo, etc. Usar valores aleatórios e verificar se essas propriedades são preservadas para todos valores de teste é uma boa maneira de testar essas funções. Você geralmente precisa testar mais de uma propriedade, mas geralmente é possível identificar um conjunto finito de propriedades que juntas validam o comportamento correto de uma função sem necessariamente conhecer o resultado esperado para cada caso.

Uma das melhores introduções ao teste de propriedade que eu já vi é este em F #. Espero que a sintaxe não seja uma obstrução para entender a explicação da técnica.

    
por 09.10.2018 / 18:53
fonte
4

É tentador escrever o código e depois ver se o resultado "parece correto", mas, como você corretamente intui, não é uma boa idéia.

Quando o algoritmo é difícil, você pode fazer várias coisas para facilitar o cálculo manual do resultado.

  1. Use o Excel. Configure uma planilha que faça alguns ou todos os cálculos para você. Mantenha-o simples o suficiente para que você possa ver as etapas.

  2. Divida seu método em métodos testáveis menores, cada um com seus próprios testes. Quando tiver certeza de que as partes menores funcionam, use-as para trabalhar manualmente na próxima etapa.

  3. Use propriedades agregadas para verificar a sanidade. Por exemplo, digamos que você tenha uma calculadora de probabilidade; talvez você não saiba quais devem ser os resultados individuais, mas sabe que todos eles precisam somar 100%.

  4. Força bruta. Escreva um programa que gere todos os resultados possíveis e verifique se nenhum é melhor do que o seu algoritmo gera.

por 08.10.2018 / 04:47
fonte
2

TL; DR

Dirija-se à seção "testes comparativos" para obter conselhos que não estejam em outras respostas.

Começos

Comece testando os casos que devem ser rejeitados pelo algoritmo (zero ou% negativoworkPerDay, por exemplo) e os casos que são triviais (por exemplo,% vaziotasks array).

Depois disso, você quer testar os casos mais simples primeiro. Para a entrada tasks , precisamos testar diferentes comprimentos; deve ser suficiente para testar 0, 1 e 2 elementos (2 pertence à categoria "muitos" para este teste).

Se você puder encontrar entradas que possam ser calculadas mentalmente, isso é um bom começo. Uma técnica que às vezes uso é começar de um resultado desejado e trabalhar de volta (na especificação) para entradas que devem produzir esse resultado.

Teste comparativo

Às vezes, a relação da saída com a entrada não é óbvia, mas você tem um relacionamento previsível entre diferentes saídas quando uma entrada é alterada. Se eu entendi o exemplo corretamente, adicionar uma tarefa (sem alterar outras entradas) nunca aumentará a proporção do trabalho em tempo, para que possamos criar um teste que chame a função duas vezes - uma com e outra sem a tarefa extra - e afirma a desigualdade entre os dois resultados.

Fallbacks

Às vezes eu tive que recorrer a um longo comentário mostrando um resultado computado à mão em etapas correspondentes à especificação (esse comentário geralmente é mais longo que o caso de teste). O pior caso é quando você precisa manter a compatibilidade com uma implementação anterior em um idioma diferente ou em um ambiente diferente. Às vezes você só precisa rotular os dados de teste com algo como /* derived from v2.6 implementation on ARM system */ . Isso não é muito satisfatório, mas pode ser aceitável como um teste de fidelidade ao portar ou como uma muleta de curto prazo.

Lembretes

O atributo mais importante de um teste é sua legibilidade - se as entradas e saídas são opacas para o leitor, então o teste tem valor muito baixo, mas se o leitor é ajudado a entender as relações entre eles, o teste serve dois propósitos.

Não se esqueça de usar um "aproximadamente igual" apropriado para resultados inexatos (por exemplo, ponto flutuante).

Evite o excesso de testes - adicione apenas um teste se ele abordar algo (como um valor limítrofe) que não seja alcançado por outros testes.

    
por 09.10.2018 / 10:23
fonte
2

Não há nada muito especial sobre esse tipo de função difícil de testar. O mesmo se aplica ao código que usa interfaces externas (digamos, uma API REST de um aplicativo de terceiros que não está sob seu controle e certamente não está sendo testado pelo seu conjunto de testes; ou usando uma biblioteca de terceiros onde você não tem certeza da formato de byte exato dos valores de retorno).

É uma abordagem bastante válida simplesmente executar seu algoritmo para alguma entrada sã, ver o que ele faz, certificar-se de que o resultado está correto e encapsular a entrada e o resultado como um caso de teste. Você pode fazer isso em alguns casos e, assim, obter várias amostras. Tente fazer os parâmetros de entrada tão diferentes quanto possível. No caso de uma chamada de API externa, você faria algumas chamadas contra o sistema real, rastreasse-as com alguma ferramenta e depois as ridicularizaria em seus testes de unidade para ver como o programa reage - o que é o mesmo que escolher alguns executa seu código de planejamento de tarefas, verificando-os manualmente e, em seguida, codificando o resultado em seus testes.

Então, obviamente, traga casos de borda como (no seu exemplo) uma lista vazia de tarefas; coisas assim.

Sua suíte de testes talvez não seja tão boa quanto para um método em que você pode prever resultados com facilidade; mas ainda 100% melhor do que nenhuma suíte de testes (ou apenas um teste de fumaça).

Se o seu problema, porém, é que você acha difícil decidir se um resultado está correto, então esse é um problema completamente diferente. Por exemplo, digamos que você tenha um método que detecte se um número arbitrariamente grande é primo. Você dificilmente pode lançar qualquer número aleatório para ele e, em seguida, apenas "olhar" se o resultado estiver correto (supondo que você não pode decidir o primeiro-ness em sua cabeça ou em um pedaço de papel). Neste caso, há realmente pouco que você possa fazer - você precisaria obter resultados conhecidos (isto é, alguns primos grandes), ou implementar a funcionalidade com um algoritmo diferente (talvez até mesmo uma equipe diferente - a NASA parece gostar de que) e espero que, se qualquer uma das implementações estiver com bugs, pelo menos o bug não levará aos mesmos resultados errados.

Se este for um caso regular para você, então você tem que ter uma boa conversa com seus engenheiros de requisitos. Se eles não podem formular suas necessidades de uma maneira que seja fácil  (ou em todos os possíveis) para verificar por você, então quando você sabe se você está acabado?

    
por 09.10.2018 / 17:52
fonte
1

Incorpore o teste de asserção ao seu conjunto de testes de unidade para testar o algoritmo baseado em propriedade. Além de escrever testes de unidade que verificam a saída específica, escreva testes projetados para falhar, acionando falhas de asserção no código principal.

Muitos algoritmos confiam em suas provas de correção na manutenção de certas propriedades ao longo dos estágios do algoritmo. Se você puder verificar essas propriedades observando a saída de uma função, o teste de unidade sozinho será suficiente para testar suas propriedades. Caso contrário, o teste baseado em asserção permite testar se uma implementação mantém uma propriedade toda vez que o algoritmo a assume.

Testes baseados em asserções exporão falhas de algoritmos, erros de codificação e falhas de implementação devido a problemas como instabilidade numérica. Muitas linguagens possuem mecanismos que retiram declarações em tempo de compilação ou antes de o código ser interpretado de forma que, quando executadas no modo de produção, as asserções não acarretem uma penalidade de desempenho. Se seu código passar nos testes de unidade, mas falhar em um caso real, você poderá ativar as asserções como uma ferramenta de depuração.

    
por 10.10.2018 / 20:56
fonte
1

Outras respostas são boas, então vou tentar acertar alguns pontos que eles perderam coletivamente até agora.

Eu escrevi (e testei exaustivamente) software para fazer o processamento de imagens usando o Synthetic Aperture Radar (SAR). É de natureza científica / numérica (há muita geometria, física e matemática envolvidas).

Algumas dicas (para testes gerais científicos / numéricos):

1) Use inversos. Qual é o fft de [1,2,3,4,5] ? Nenhuma idéia. O que é ifft(fft([1,2,3,4,5])) ? Deve ser [1,2,3,4,5] (ou próximo a isso, erros de ponto flutuante podem aparecer). O mesmo vale para o caso 2D.

2) Use afirmações conhecidas. Se você escrever uma função determinante, pode ser difícil dizer qual é o determinante de uma matriz aleatória de 100x100. Mas você sabe que o determinante da matriz de identidade é 1, mesmo que seja 100x100. Você também sabe que a função deve retornar 0 em uma matriz não-invertível (como 100x100 de todos os 0s).

3) Use afirmações aproximadas em vez de exata afirma. Eu escrevi um código para o processamento de SAR que registraria duas imagens gerando pontos de empate que criam um mapeamento entre imagens e, em seguida, fazendo uma dobra entre eles para fazê-los corresponder. Ele poderia se registrar em um nível de sub-pixel. A priori, é difícil dizer qualquer coisa sobre como o registro de duas imagens pode parecer. Como você pode testá-lo? Coisas como:

EXPECT_TRUE(register(img1, img2).size() < min(img1.size(), img2.size()))

já que você só pode se registrar em partes sobrepostas, a imagem registrada deve ser menor ou igual à sua menor imagem, e também:

scale = 255
EXPECT_PIXEL_EQ_WITH_TOLERANCE(reg(img, img), img, .05*scale)

desde que uma imagem registrada a si mesma deve estar CLOSE a si mesma, mas você pode experimentar um pouco mais do que erros de ponto flutuante devido ao algoritmo em mãos, então verifique se cada pixel está dentro de +/- 5% da faixa dos pixels pode assumir (0-255 é em escala de cinzentos, comum no processamento de imagens). O resultado deve ter pelo menos o mesmo tamanho da entrada.

Você pode até mesmo testar a fumaça (ou seja, chamá-lo e verificar se ele não falha). Em geral, essa técnica é melhor para testes maiores em que o resultado final não pode ser (facilmente) calculado a priori para executar o teste.

4) Use OR STORE uma semente numérica aleatória para o seu RNG.

As execuções do precisam ser reproduzíveis. É falso, no entanto, que a única maneira de obter uma execução reproduzível é fornecer uma semente específica para um gerador de números aleatórios. Às vezes, o teste de aleatoriedade é valioso. Eu vi / ouvi falar de bugs no código científico que surgem em casos degenerados que foram gerados aleatoriamente (em algoritmos complicados, pode ser difícil ver o que o caso degenerado é mesmo ). Em vez de sempre chamar sua função com a mesma semente, gere uma semente aleatória, use essa semente e registre o valor da semente. Dessa forma, cada execução tem uma semente aleatória diferente, mas se ocorrer uma falha, você poderá executar novamente o resultado usando a semente que você registrou para depurar. Eu realmente usei isso na prática e isso atrapalhou um bug, então eu imaginei que eu iria mencionar isso. É certo que isso só aconteceu uma vez, e tenho certeza de que nem sempre vale a pena fazer, então use essa técnica com prudência. Aleatório com a mesma semente é sempre seguro, no entanto. Desvantagem (ao invés de usar a mesma semente o tempo todo): Você precisa registrar suas execuções de teste. Upside: correção e bug nuking.

Seu caso particular

1) Teste que um taskArray vazio retorna 0 (confirmação conhecida).

2) Gere entradas aleatórias de forma que task.time > 0 , task.due > 0 , e task.importance > 0 para todos task s, e asseverar que o resultado é maior que 0 (afirmação aproximada, entrada aleatória) . Você não precisa ficar louco e gerar sementes aleatórias, seu algoritmo não é complexo o suficiente para justificar isso. Há cerca de 0 chance de pagar: basta manter o teste simples.

3) Teste se task.importance == 0 para todos task s, o resultado é 0 (afirmativa )

4) Outras respostas abordaram isso, mas pode ser importante para o seu caso particular : Se você estiver fazendo uma API para ser consumida por usuários fora de sua equipe, é necessário para testar os casos degenerados. Por exemplo, se workPerDay == 0 , certifique-se de lançar um erro bonito que informe ao usuário que é uma entrada inválida. Se você não está fazendo uma API, e é apenas para você e sua equipe, você provavelmente pode pular esta etapa e simplesmente se recusar a chamá-la com o caso degenerado.

HTH.

    
por 16.10.2018 / 05:38
fonte
0

Algumas das outras respostas aqui são muito boas:

  • Testar casos de base, borda e canto
  • Realize verificações de integridade
  • Realizar testes comparativos

... Eu adicionaria algumas outras táticas:

  • Decomponha o problema.
  • Prove o algoritmo fora do código.
  • Teste que o algoritmo [comprovado externamente] é implementado conforme projetado.

A decomposição permite garantir que os componentes do seu algoritmo façam o que você espera que eles façam. E uma "boa" decomposição permite que você também garanta que estão colados corretamente. Uma decomposição ótima generaliza e simplifica o algoritmo na medida em que você pode prever os resultados (do (s) algoritmo (s) genérico (s) simplificado (s)) bem o suficiente para escrever testes completos .

Se você não conseguir se decompor nessa extensão, prove que o algoritmo fora do código é suficiente para satisfazer você e seus colegas, partes interessadas e clientes. E então, basta se decompor o suficiente para provar que sua implementação corresponde ao design.

    
por 10.10.2018 / 21:59
fonte
0

Isso pode parecer uma resposta idealista, mas ajuda a identificar diferentes tipos de testes.

Se as respostas estritas são importantes para a implementação, então os exemplos e as respostas esperadas devem ser realmente fornecidos nos requisitos que descrevem o algoritmo. Esses requisitos devem ser revisados em grupo e, se você não obtiver os mesmos resultados, o motivo precisa ser identificado.

Mesmo que você esteja desempenhando o papel de analista e implementador, você deve criar requisitos e revisá-los muito antes de escrever os testes unitários. Nesse caso, você saberá os resultados esperados e poderá escrever seus testes de acordo.

Por outro lado, se você estiver implementando uma parte que não faça parte da lógica de negócios ou que ofereça suporte a uma resposta lógica de negócios, convém executar o teste para ver quais são os resultados e modificar o teste para esperar esses resultados. Os resultados finais já foram verificados com seus requisitos, portanto, se estiverem corretos, todo o código que os alimenta deve ser numericamente correto e, nesse ponto, seus testes de unidade são mais para detectar falhas de borda e futuras alterações de refatoração do que provar que algoritmo produz resultados corretos.

    
por 12.10.2018 / 18:39
fonte
0

Acho que é perfeitamente aceitável em algumas ocasiões seguir o processo:

  • crie um caso de teste
  • use seu software para obter a resposta
  • verifique a resposta manualmente
  • escreva um teste de regressão para que versões futuras do software continuem a dar essa resposta.

Esta é uma abordagem razoável em qualquer situação em que verificar a exatidão de uma resposta à mão seja mais fácil do que calcular a resposta à mão a partir dos primeiros princípios.

Conheço pessoas que escrevem softwares para renderizar páginas impressas e têm testes que verificam se exatamente os pixels corretos estão definidos na página impressa. A única maneira sensata de fazer isso é escrever o código para renderizar a página, verificar se está com bom aspecto e, em seguida, capturar o resultado como um teste de regressão para versões futuras.

Só porque você lê em um livro que uma determinada metodologia encoraja escrever os casos de teste primeiro, não significa que você sempre tenha que fazer isso dessa maneira. As regras estão aí para serem quebradas.

    
por 12.10.2018 / 19:48
fonte
0

Outras respostas já possuem técnicas para a aparência de um teste quando o resultado específico não pode ser determinado fora da função testada.

O que eu faço além disso, que eu não vi nas outras respostas, é gerar testes de alguma forma:

  1. Entradas 'aleatórias'
  2. Iteração entre intervalos de dados
  3. Construção de casos de teste a partir de conjuntos de limites
  4. Todas as anteriores.

Por exemplo, se a função usar três parâmetros, cada um com o intervalo de entrada permitido [-1,1], teste todas as combinações de cada parâmetro, {-2, -1.01, -1, -0.99, -0.5, -0.01, 0,0,01,0.5,0.99,1,1,01,2, alguns mais aleatórios em (-1,1)}

Resumindo: Às vezes baixa qualidade pode ser subsidiada por quantidade.

    
por 15.10.2018 / 07:19
fonte