Commençons par un exemple.
Disons que j'ai une méthode appelée export
qui dépend fortement du schéma DB. Et par «dépend fortement», je veux dire que je sais que l'ajout d'une nouvelle colonne à une certaine table conduit souvent (très souvent) au export
changement de méthode correspondant (en général, vous devez également ajouter le nouveau champ aux données d'exportation).
Les programmeurs oublient souvent de changer de export
méthode car il n'est pas vraiment clair que vous devriez même y regarder. Mon objectif est de forcer le programmeur à prendre explicitement une décision pour déterminer s'il a oublié de regarder la export
méthode ou s'il ne veut tout simplement pas ajouter un champ aux données d'exportation. Et je cherche la solution de conception pour ce problème.
J'ai deux idées, mais les deux ont des défauts.
Emballage intelligent «Tout lire»
Je peux créer le wrapper intelligent qui s'assure que toutes les données sont lues explicitement.
Quelque chose comme ça:
def export():
checker = AllReadChecker.new(table_row)
name = checker.get('name')
surname = checker.get('surname')
checker.ignore('age') # explicitly ignore the "age" field
result = [name, surname] # or whatever
checker.check_now() # check all is read
return result
Ainsi, checker
affirme si table_row
contient d'autres champs qui n'ont pas été lus. Mais tout cela semble plutôt lourd et (peut-être) affecte la performance.
«Vérifiez cette méthode»
Je peux simplement créer le plus unitaire qui se souvient du dernier schéma de table et échoue à chaque fois que la table est modifiée. Dans ce cas, le programmeur verrait quelque chose comme «n'oubliez pas de vérifier la export
méthode». Pour cacher l'avertisseur, le programmeur vérifierait (ou non - c'est un problème) export
et corrigerait manuellement (c'est un autre problème) le test en y ajoutant de nouveaux champs.
J'ai quelques autres idées mais elles sont trop gênantes à mettre en œuvre ou trop difficiles à comprendre (et je ne veux pas que le projet devienne un puzzle).
Le problème ci-dessus n'est qu'un exemple de la classe plus large de problèmes que je rencontre de temps en temps. Je veux lier certains morceaux de code et / ou infrastructure, donc changer l'un d'eux alerte immédiatement le programmeur pour qu'il en vérifie un autre. Habituellement, vous avez des outils simples comme extraire une logique commune ou écrire des tests fiables, mais je recherche l'outil pour des cas plus complexes: peut-être certains modèles de conception dont je suis maintenant au courant.
la source
export
sur la base du schéma?export
tout ce dont vous avez réellement besoin?Réponses:
Vous êtes sur la bonne voie avec votre idée de test unitaire, mais votre mise en œuvre est incorrecte.
Si le
export
est lié au schéma et que le schéma a changé, il y a deux cas possibles:Soit le
export
fonctionne toujours parfaitement, car il n'a pas été affecté par une légère modification du schéma,Ou ça casse.
Dans les deux cas, l'objectif de la génération est de rechercher cette régression possible. Un tas de tests - que ce soit des tests d'intégration, des tests système, des tests fonctionnels ou autre chose - garantissent que votre
export
procédure fonctionne avec le schéma actuel , indépendamment du fait qu'elle ait changé ou non depuis la validation précédente. Si ces tests réussissent, tant mieux. S'ils échouent, c'est un signe pour le développeur qu'il a peut-être manqué quelque chose, et une indication claire où chercher.Pourquoi votre implémentation est-elle mauvaise? Eh bien, pour plusieurs raisons.
Cela n'a rien à voir avec les tests unitaires ...
... et, en fait, ce n'est même pas un test.
Le pire, c'est que la fixation du «test» nécessite, en fait, de changer réellement le «test», c'est-à-dire d'effectuer une opération qui n'a aucun rapport avec le
export
.Au lieu de cela, en effectuant des tests réels pour la
export
procédure, vous vous assurez que le développeur corrigera leexport
.Plus généralement, lorsque vous rencontrez une situation où un changement dans une classe nécessite toujours ou généralement un changement dans une classe complètement différente, c'est un bon signe que vous avez mal fait votre conception et que vous violez le principe de responsabilité unique.
Bien que je parle spécifiquement des classes, cela s'applique plus ou moins à d'autres entités. Par exemple, une modification du schéma de la base de données doit se refléter automatiquement dans votre code, par exemple via les générateurs de code utilisés par de nombreux ORM, ou au moins être facilement localisée: si j'ajoute une
Description
colonne à laProduct
table et que je n'utilise aucun ORM ni générateur de code, Je m'attends au moins à effectuer un seul changement au sein de laData.Product
classe du DAL, sans avoir besoin de parcourir toute la base de code et de trouver des occurrences deProduct
classe dans, disons, la couche de présentation.Si vous ne pouvez pas raisonnablement limiter le changement à un seul emplacement (soit parce que vous êtes dans un cas où cela ne fonctionne tout simplement pas, soit parce qu'il nécessite une énorme quantité de développement), alors vous créez un risque de régression . Quand je change de classe
A
et que la classeB
quelque part dans la base de code cesse de fonctionner, c'est une régression.Les tests réduisent le risque de régression et, ce qui est beaucoup plus important, vous montre l'emplacement d'une régression. C'est pourquoi, lorsque vous savez que des changements dans un emplacement provoquent des problèmes dans une partie complètement différente de la base de code, assurez-vous d'avoir suffisamment de tests qui déclenchent des alarmes dès qu'une régression apparaît à ce niveau.
Dans tous les cas, évitez de vous fier dans ces cas uniquement aux commentaires. Quelque chose comme:
ne fonctionne jamais. Non seulement les développeurs ne le liront pas dans la plupart des cas, mais il finira souvent par être supprimé ou éloigné de la ligne concernée, et deviendra impossible à comprendre.
la source
If you change the following line...
qui fonctionne automatiquement et ne peut pas être simplement ignoré. Je n'ai jamais vu quelqu'un utiliser de tels pièges, d'où le doute.B
ne cesse de travailler, il peut - être commence à travailler correctement. Mais je ne peux pas prédire l'avenir et je ne peux pas écrire de tests qui prédisent l'avenir, alors j'essaie de rappeler au développeur d'écrire ce nouveau test.Il me semble que vos changements sont sous-spécifiés. Supposons que vous vivez dans un endroit qui n'a pas de codes postaux, vous n'avez donc pas de colonne de code postal dans la table d'adresses. Ensuite, les codes postaux sont introduits, ou vous commencez à traiter avec des clients qui vivent là où il y a des codes postaux, et vous devez ajouter cette colonne au tableau.
Si l'élément de travail indique simplement "ajouter une colonne de code postal à la table d'adresses", alors oui, l'exportation sera interrompue ou, du moins, n'exportera pas de codes postaux. Mais qu'en est-il de l'écran de saisie utilisé pour saisir les codes postaux? Le rapport qui répertorie tous les clients et leurs adresses? Il y a des tonnes de choses qui doivent changer lorsque vous ajoutez cette colonne. Le travail de se souvenir de ces choses est important - vous ne devriez pas compter sur des artefacts de code aléatoires pour "piéger" les développeurs dans la mémoire.
Lorsque la décision est prise d'ajouter une colonne significative (c'est-à-dire, pas seulement une recherche totale ou dénormalisée mise en cache ou une autre valeur qui n'appartient pas à une exportation ou un rapport ou sur un écran de saisie), les éléments de travail créés doivent inclure TOUTES les modifications nécessaire - ajout de la colonne, mise à jour du script de remplissage, mise à jour des tests, mise à jour de l'exportation, des rapports, des écrans de saisie, etc. Ceux-ci ne peuvent pas tous être attribués (ou récupérés) à la même personne, mais ils doivent tous être effectués.
Parfois, les développeurs choisissent d'ajouter eux-mêmes des colonnes dans le cadre de l'implémentation de modifications plus importantes. Par exemple, quelqu'un peut avoir écrit un élément de travail pour ajouter quelque chose à un écran de saisie et un rapport, sans penser à la façon dont il est mis en œuvre. Si cela se produit souvent, vous devrez décider si votre additionneur d'élément de travail doit connaître les détails d'implémentation (afin de pouvoir ajouter tous les bons éléments de travail) ou si les développeurs doivent être conscients que l'élément de travail- l'additionneur laisse parfois les choses de côté. Si c'est le dernier, alors vous avez besoin d'une culture de «ne changez pas simplement le schéma; arrêtez-vous et réfléchissez à ce que cela affecte."
S'il y avait beaucoup de développeurs et que cela se produisait plus d'une fois, je mettrais en place une alerte de consignation pour que le chef d'équipe ou une autre personne senior soit alerté des changements de schéma. Cette personne pourrait alors rechercher des éléments de travail connexes pour faire face aux conséquences de leur changement de schéma et, si les éléments de travail étaient manquants, non seulement les ajouter, mais aussi éduquer ceux qui les avaient exclus du plan.
la source
Presque toujours, lors de la création d'une exportation, je crée également une importation correspondante. Comme j'ai d'autres tests qui remplissent entièrement la structure de données exportée, je peux ensuite créer un test unitaire aller-retour qui compare un original entièrement rempli avec une copie exportée puis importée. S'ils sont identiques, l'exportation / importation est terminée; s'ils ne sont pas identiques, le test unitaire échoue et je sais que le mécanisme d'exportation doit être mis à jour.
la source