Disons que je commence à développer un jeu de rôle avec des personnages qui attaquent d'autres personnages et ce genre de choses.
En appliquant TDD, je fais quelques cas de test pour tester la logique à l'intérieur de la méthode Character.receiveAttack(Int)
. Quelque chose comme ça:
@Test
fun healthIsReducedWhenCharacterIsAttacked() {
val c = Character(100) //arg is the health
c.receiveAttack(50) //arg is the suffered attack damage
assertThat(c.health, is(50));
}
Disons que j'ai 10 méthodes pour tester la méthode receiveAttack
. Maintenant, j'ajoute une méthode Character.attack(Character)
(qui appelle la méthode receiveAttack
), et après quelques cycles TDD la testant, je prends une décision: Character.receiveAttack(Int)
devrait être private
.
Que se passe-t-il avec les 10 précédents cas de test? Dois-je les supprimer? Dois-je conserver la méthode public
(je ne pense pas)?
Cette question n'est pas de savoir comment tester des méthodes privées mais comment les gérer après une nouvelle conception lors de l'application de TDD
Dans TDD, les tests servent de documentation exécutable de votre conception. Votre conception a changé, alors évidemment, votre documentation doit aussi!
Notez que, dans TDD, la seule façon dont la méthode attack
aurait pu apparaître, est le résultat de l'échec d'un test. Ce qui signifie que attack
est en cours de test par un autre test. Ce qui signifie que indirectementreceiveAttack
est couvert par les tests de attack
. Idéalement, toute modification de receiveAttack
devrait interrompre au moins un des tests de attack
.
Et si ce n'est pas le cas, il y a dans receiveAttack
une fonctionnalité qui n'est plus nécessaire et qui ne devrait plus exister!
Donc, puisque receiveAttack
est déjà testé via attack
, peu importe que vous gardiez ou non vos tests. If votre framework de test facilite le test des méthodes privées, et if vous décidez de tester les méthodes privées, vous pouvez les conserver. Mais vous pouvez également les supprimer sans perdre la couverture et la confiance des tests.
Si la méthode est suffisamment complexe pour nécessiter des tests, elle doit être publique dans une classe. Vous refactorisez donc:
public class X {
private int complexity(...) {
...
}
public void somethingElse() {
int c = complexity(...);
}
}
à:
public class Complexity {
public int calculate(...) {
...
}
}
public class X {
private Complexity complexity;
public X(Complexity complexity) { // dependency injection happiness
this.complexity = complexity;
}
public void something() {
int c = complexity.calculate(...);
}
}
Déplacez le test actuel de X.complexity vers ComplexityTest. Ensuite, envoyez X.quelque chose en se moquant de Complexité.
D'après mon expérience, la refactorisation vers des classes plus petites et des méthodes plus courtes rapporte d'énormes avantages. Ils sont plus faciles à comprendre, à tester et finissent par être réutilisés plus que ce à quoi on pourrait s'attendre.
Disons que j'ai 10 méthodes pour tester la méthode receiveAttack. Maintenant, j'ajoute une méthode Character.attack (Character) (qui appelle la méthode receiveAttack), et après quelques cycles TDD la testant, je prends une décision: Character.receiveAttack (Int) doit être privé.
Quelque chose à garder à l'esprit ici est que la décision que vous prenez est pour supprimer une méthode de l'API. La courtoisie de la rétrocompatibilité suggérerait
Les tests sont supprimés/ou remplacés lorsque votre API ne prend plus en charge la méthode. À ce stade, la méthode privée est un détail d'implémentation que vous devriez pouvoir refactoriser.
À ce stade, vous êtes de retour dans la question standard de savoir si votre suite de tests doit accéder directement aux implémentations, plutôt interagir uniquement via l'API publique. Une méthode privée est quelque chose que nous devrions pouvoir remplacer sans que la suite de tests ne gêne. Je m'attendrais donc à ce que le couple de tests disparaisse - soit retiré, soit déplacé avec l'implémentation vers un composant testable séparément.