O problema ocorre ao fazer o TDD. Após algumas passagens no teste, os tipos de retorno de alguma classe / módulo são alterados. Em uma linguagem de programação estaticamente tipada, se um objeto simulado anterior foi usado nos testes de alguma outra classe e não foi modificado para refletir a alteração de tipo, ocorrerão erros de compilação.
No entanto, para idiomas dinâmicos, a alteração nos tipos de retorno pode não ser detectada e os testes da outra classe ainda serão aprovados. Certamente, pode haver testes de integração que falharão mais tarde, mas os testes de unidade serão erroneamente aprovados. Existe alguma maneira de evitar isso?
Atualizando com uma amostra trivial (em alguma linguagem inventada) ...
Versão 1:
Calc = {
doMultiply(x, y) {return x * y}
}
//.... more code ....
// On some faraway remote code on a different file
Rect = {
computeArea(l, w) {return Calc.doMultipy(x*y)}
}
// test for Rect
testComputeArea() {
Calc = new Mock()
Calc.expect(doMultiply, 2, 30) // where 2 is the arity
assertEqual(30, computeArea)
}
Agora, na versão 2:
// I change the return types. I also update the tests for Calc
Calc = {
doMultiply(x, y) {return {result: (x * y), success:true}}
}
... O Rect lançará uma exceção no tempo de execução, mas o teste ainda terá êxito.
fonte
class X
, masclass Y
cujos testes dependemX
e, portanto, são testados contra um contrato diferente do que é executado na produção.Respostas:
Até certo ponto, isso é apenas parte do custo de se fazer negócios com linguagens dinâmicas. Você tem muita flexibilidade, também conhecida como "corda suficiente para se enforcar". Tenha cuidado com isso.
Para mim, o problema sugere o uso de técnicas de refatoração diferentes das usadas em uma linguagem de tipo estaticamente. Em uma linguagem estática, você substitui os tipos de retorno em parte para poder "confiar no compilador" para descobrir quais lugares podem ser quebrados. É seguro e provavelmente mais seguro do que modificar o tipo de retorno no local.
Em uma linguagem dinâmica, você não pode fazer isso; portanto, é muito mais seguro modificar o tipo de retorno no lugar, em vez de substituí-lo. Possivelmente, você a modifica adicionando sua nova classe a ela como membro, para as classes que precisam.
fonte
Se o seu código for alterado e os testes continuarem aprovados, haverá algo errado com os testes (ou seja, você está perdendo uma afirmação) ou o código não foi realmente alterado.
O que quero dizer com isso? Bem, seus testes descrevem os contratos entre partes do seu código. Se o contrato for "o valor de retorno deve ser iterável", a alteração do valor de retorno de, digamos, uma matriz para uma lista não é realmente uma alteração no contrato e, portanto, não provocará necessariamente uma falha no teste.
A fim de evitar afirmações falta, você pode usar ferramentas como análise de cobertura de código (não irá dizer-lhe que partes do seu código são testados, mas irá dizer-lhe que partes definitivamente não são testados), a complexidade ciclomática e complexidade nPath (que fornece um limite mais baixo ao número de asserções necessárias para obter uma cobertura completa dos códigos C1 e C2) e testadores de mutação (que injetam mutações no seu código, como transformar
true
emfalse
números negativos em positivos, objetos emnull
etc.) e verificar se isso faz com que os testes falhem).fonte