Le problème se produit lors de l'exécution de TDD. Après quelques tests réussis, les types de retour de certaines classes / modules changent. Dans un langage de programmation typé statiquement, si un objet simulé précédent a été utilisé dans les tests d'une autre classe et n'a pas été modifié pour refléter le changement de type, des erreurs de compilation se produisent.
Cependant, pour les langages dynamiques, la modification des types de retour peut ne pas être détectée et les tests de l' autre classe réussiront toujours. Bien sûr, il pourrait y avoir des tests d'intégration qui devraient échouer plus tard, mais les tests unitaires passeraient par erreur. Existe-t-il un moyen d'éviter cela?
Mise à jour avec un échantillon trivial (sur certains langages inventés) ...
Version 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)
}
Maintenant, sur la version 2:
// I change the return types. I also update the tests for Calc
Calc = {
doMultiply(x, y) {return {result: (x * y), success:true}}
}
... Rect lèvera alors une exception lors de l'exécution, mais le test réussira quand même.
la source
class X
, mais dont les testsclass Y
dépendentX
et sont donc testés par rapport à un contrat différent de celui auquel il se heurte en production.Réponses:
Dans une certaine mesure, cela ne représente qu'une partie du coût de faire des affaires avec des langages dynamiques. Vous obtenez beaucoup de flexibilité, autrement connu comme "assez de corde pour vous accrocher". Soyez prudent avec ça.
Pour moi, le problème suggère d'utiliser des techniques de refactorisation différentes de celles que vous utiliseriez dans un langage typé statiquement. Dans un langage statique, vous remplacez en partie les types de retour afin de pouvoir "vous appuyer sur le compilateur" pour trouver les emplacements susceptibles de se briser. Il est sûr de le faire et probablement plus sûr que de modifier le type de retour en place.
Dans un langage dynamique, vous ne pouvez pas faire cela, il est donc beaucoup plus sûr de modifier le type de retour en place, plutôt que de le remplacer. Vous pouvez éventuellement le modifier en y ajoutant votre nouvelle classe en tant que membre, pour les classes qui en ont besoin.
la source
Si votre code change et que vos tests réussissent toujours, alors il y a quelque chose qui ne va pas avec vos tests (c'est-à-dire que vous manquez une assertion), ou le code n'a pas réellement changé.
Qu'est-ce que je veux dire par là? Eh bien, vos tests décrivent les contrats entre les parties de votre code. Si le contrat est "la valeur de retour doit être itérable", le changement de la valeur de retour d'un tableau par exemple à une liste n'est pas en fait un changement de contrat et ne déclenchera donc pas nécessairement un échec de test.
Afin d'éviter les affirmations manquantes, vous pouvez utiliser des outils tels que l' analyse de couverture de code (il ne sera pas vous dire quelles parties de votre code sont testés, mais il va vous dire quelles sont les parties certainement ne sont pas testées), la complexité cyclomatique et de la complexité NPATH (qui vous donnent une limite inférieure sur le nombre d'assertions nécessaires pour obtenir une couverture complète des codes C1 et C2) et des testeurs de mutation (qui injectent des mutations dans votre code telles que se transformer
true
enfalse
, des nombres négatifs en positifs, des objets ennull
etc. et vérifier si cela fait échouer les tests).la source