Développement piloté par les tests (TDD) est grand de nos jours. Je le vois souvent recommandé comme une solution pour un large éventail de problèmes ici dans les programmeurs SE et d'autres lieux. Je me demande pourquoi ça marche.
D'un point de vue technique, cela me laisse perplexe pour deux raisons:
En résumé, je suis plus préoccupé par le bit "piloté" dans TDD que par le bit "test". Le test est parfaitement OK; ce que je ne comprends pas, c'est de piloter le design en le faisant.
J'aimerais voir des réponses contenant les raisons pour lesquelles le TDD en génie logiciel est une bonne pratique, et pourquoi les problèmes que j'ai expliqués ci-dessus ne sont pas pertinents (ou pas suffisamment pertinents) dans le cas des logiciels. Je vous remercie.
Je pense qu'il y a une idée fausse ici. En conception logicielle, la conception est très proche du produit. En génie civil, en architecture, le design est découplé du produit réel: il existe des plans qui détiennent le design, qui sont ensuite matérialisés dans le produit fini, et ceux-ci sont séparés par d'énormes quantités de temps et d'efforts.
TDD teste la conception. Mais chaque conception de voiture et conception de bâtiment est également testée. Les techniques de construction sont d'abord calculées, puis testées à plus petite échelle, puis testées à plus grande échelle, avant d'être mises en œuvre dans un vrai bâtiment. Quand ils ont inventé les poutres en H et la charge par exemple, ils ont été assurés que cela avait été essayé et essayé à nouveau avant de construire le premier pont avec.
Les conceptions de voitures sont également testées, en concevant des prototypes, et oui, certainement en ajustant des choses qui ne sont pas exactement correctes, jusqu'à ce qu'elles répondent aux attentes. Une partie de ce processus est cependant plus lente, car comme vous l'avez dit, vous ne pouvez pas beaucoup vous amuser avec le produit. Mais chaque refonte d'une voiture s'appuie sur les expériences apprises des anciennes, et chaque bâtiment a environ un millier d'années de principes fondamentaux sur l'importance de l'espace, de la lumière, de l'isolation, de la résistance, etc. Les détails sont modifiés et améliorés, à la fois dans les bâtiments et en refonte pour les plus récents.
De plus, les pièces sont testées. Peut-être pas exactement dans le même style que le logiciel, mais les pièces mécaniques (roues, allumeurs, câbles) sont généralement mesurées et soumises à une contrainte pour savoir que les tailles sont correctes, aucune anomalie n'est visible, etc. Elles peuvent être radiographiées ou laser- mesurés, ils tapent des briques pour repérer celles cassées, ils peuvent être testés dans une configuration ou une autre, ou ils dessinent une représentation limitée d'un grand groupe pour vraiment la mettre à l'épreuve.
Ce sont toutes des choses que vous pouvez mettre en place avec TDD.
Et en effet, les tests ne sont pas une garantie. Les programmes se brisent, les voitures tombent en panne et les bâtiments commencent à faire des choses drôles quand le vent souffle. Mais ... la "sécurité" n'est pas une question booléenne. Même quand vous ne pouvez jamais tout inclure, être capable de couvrir - disons - 99% des éventualités est mieux que de couvrir seulement 50%. Ne pas tester, puis découvrir que l'acier ne s'est pas bien réglé et est fragile et se brise à la première claque d'un marteau lorsque vous installez votre structure principale est un simple gaspillage d'argent. Le fait qu'il y ait d'autres préoccupations qui pourraient encore nuire au bâtiment ne le rend pas moins stupide pour permettre à un défaut facilement évitable de réduire votre conception.
Quant à la pratique du TDD, c'est une question d'équilibre. Le coût de le faire d'une manière (par exemple, ne pas tester, puis ramasser les morceaux plus tard), par rapport au coût de le faire d'une autre manière. C'est toujours un équilibre. Mais ne pensez pas que les autres processus de conception n'ont pas de test et TDD en place.
OMI, la plupart des histoires à succès pour TDD sont fausses et uniquement à des fins de marketing. Il peut y avoir très peu de succès, mais uniquement pour les petites applications. Je travaille sur une grande application silverlight où les principes TDD sont utilisés. L'application a subi des centaines de tests mais elle n'est toujours pas stable. Plusieurs parties de l'application ne sont pas testables en raison des interactions complexes des utilisateurs. Tests résultants avec beaucoup de simulations et du code difficile à comprendre.
Au départ, lorsque nous avons essayé TDD, tout semble bien. J'ai pu écrire beaucoup de tests et simuler les pièces qui sont difficiles pour un test unitaire. Une fois que vous avez une bonne quantité de code et qu'un changement d'interface est requis, vous êtes foutu. De nombreux tests doivent être corrigés et vous réécrivez plus de tests que la modification réelle du code.
Peter Norvig explique son point de vue sur TDD dans le livre Coders At Work.
Seibel: Et l'idée d'utiliser des tests pour piloter la conception?
Norvig: Je considère les tests plus comme un moyen de corriger les erreurs que comme un moyen de conception. Cette approche extrême consistant à dire: "Eh bien, la première chose que vous faites est d'écrire un test qui dit que j'obtiens la bonne réponse à la fin", puis vous l'exécutez et voyez qu'il échoue, puis vous dites: "Que dois-je besoin? "- cela ne me semble pas être la bonne façon de concevoir quelque chose pour moi. Il ne semble que si c'était si simple que la solution était prédéterminée, cela aurait du sens. Je pense que vous devez y penser en premier. Vous devez dire: "Quelles sont les pièces? Comment puis-je écrire des tests pour des pièces jusqu'à ce que je sache quels sont certains d'entre eux? " Et puis, une fois que vous avez fait cela, alors c'est une bonne discipline d'avoir des tests pour chacun de ces morceaux et de bien comprendre comment ils interagissent entre eux et les cas limites, etc. Ceux-ci devraient tous avoir des tests. Mais je ne pense pas que vous dirigiez l'ensemble du design en disant: "Ce test a échoué."
La conception pilotée par les tests fonctionne pour moi pour les raisons suivantes:
Cela signifie que vous pouvez voir dans les cas de test:
Le code est souvent écrit de manière à ce que vous résolviez d'abord le problème , puis vous réfléchissiez à la façon dont le code que vous venez d'écrire doit être appelé. Cela donne souvent une interface gênante car il est souvent plus facile de "simplement ajouter un indicateur", etc. En pensant au "nous devons le faire pour que les tests ressemblent à CELA" à l'avant, vous inversez la situation. Cela donnera une meilleure modularité, car le code sera écrit en fonction de l'interface d'appel, et non l'inverse.
Cela se traduira généralement par un code plus propre qui nécessite moins de documentation explicative.
Puisque vous avez la spécification sur le formulaire exécutable, vous avez terminé lorsque la suite de tests complète réussit. Vous pouvez ajouter plus de tests au fur et à mesure que vous clarifiez les choses à un niveau plus détaillé, mais comme principe de base, vous avez un indicateur de progrès très clair et visible et quand vous avez terminé.
Cela signifie que vous pouvez savoir quand un travail est nécessaire ou non (cela aide-t-il à passer un test) vous finissez par avoir besoin de faire moins.
Pour ceux qui y réfléchissent peuvent leur être utiles, je vous encourage à utiliser TDD pour votre prochaine routine de bibliothèque. Configurez lentement une spécification exécutable et faites passer le code aux tests. Une fois terminée, la spécification exécutable est disponible pour tous ceux qui ont besoin de voir comment appeler la bibliothèque.
"Les résultats des études de cas indiquent que la densité des défauts avant la sortie des quatre produits a diminué entre 40% et 90% par rapport à des projets similaires qui n'ont pas utilisé la pratique TDD. Subjectivement, les équipes ont connu une augmentation de 15 à 35% temps de développement initial après l'adoption de TDD. " ~ Résultats et expériences de 4 équipes industrielles
Le processus de création d'un logiciel n'est pas le processus d'écriture du code. Aucun projet logiciel ne doit commencer sans un plan de "large portée" en premier. Tout comme un projet de pontage de deux rives d'une rivière a d'abord besoin d'un tel plan.
L'approche TDD se rapporte (principalement) aux tests unitaires - du moins c'est ainsi que les gens ont tendance à y penser - qui créent les bits de code logiciel les plus bas. Lorsque toutes les fonctionnalités et tous les comportements ont déjà été définis et que nous savons réellement ce que nous voulons réaliser.
En génie des structures, cela ressemble un peu à ceci:
"Nous avons ces deux morceaux de métal connectés ensemble, et la connexion doit maintenir des forces de cisaillement de l'ordre de x. Testons quelle méthode de connexion est la meilleure pour ce faire '
Pour tester si le logiciel fonctionne dans son ensemble, nous concevons d'autres types de tests comme les tests d'utilisabilité, les tests d'intégration et les tests d'acceptation. Ceux-ci doivent également être définis avant le début du travail réel d'écriture du code, et sont effectués après que les tests unitaires sont verts.
Voir V-Model: http://en.wikipedia.org/wiki/V-Model_%28software_development%29
Voyons comment cela fonctionnerait pour un pont:
Un gouvernement local dit à une entreprise de construction de ponts: "Nous avons besoin d'un pont pour relier ces deux points. Le pont doit être capable de permettre n quantité de trafic par heure et être prêt pour le 21 décembre 2012 '- ceci est une définition de test d’acceptation. L’entreprise n’obtiendra pas la totalité (ou aucune) somme d’argent si elle ne réussit pas ce test.
La direction de l'entreprise décide du calendrier du projet. Ils mettent en place des équipes de travail et fixent des objectifs pour chaque équipe. Si les équipes n'atteignent pas ces objectifs, le pont ne sera pas construit à temps. Cependant - il y a un certain niveau de flexibilité ici. Si l'une des équipes a des problèmes, l'entreprise peut compenser cela en modifiant les exigences, en changeant de sous-traitants, en embauchant plus de personnes, etc. afin que l'ensemble du projet atteigne toujours l'objectif fixé au point # 1.
Au sein d'une équipe chargée de concevoir des composants de pont particuliers, cela ressemble à l'exemple que j'ai donné ci-dessus. Parfois, la solution est évidente, car nous avons un grand nombre de connaissances concernant la construction de ponts (c'est comme utiliser une bibliothèque bien testée dans le développement de logiciels - vous supposez simplement que cela fonctionne comme annoncé). Parfois, vous devez créer plusieurs modèles et les tester pour choisir le meilleur. Pourtant, les critères sur lesquels le composant est testé sont connus à l'avance.
Dans mon esprit, TDD fonctionne parce que
Plus précisément sur les points que vous soulevez
TL; DR
La programmation est toujours une activité de conception, ce n'est pas de la construction. L'écriture de tests unitaires après coup ne fait que confirmer que le code fait ce qu'il fait, pas qu'il fait quelque chose d'utile. Les échecs de test sont la vraie valeur car ils vous permettent de détecter les erreurs tôt.
Le code est une conception
Dans le chapitre 7 de PPP "Oncle Bob" parle directement de ce problème. Très tôt dans le chapitre, il fait référence à un excellent article de Jack Reeves dans lequel il propose que le code soit design (le lien mène à une page rassemblant ses trois articles sur le sujet).
Ce qui est intéressant à propos de cet argument, c'est qu'il souligne que, contrairement à d'autres disciplines d'ingénierie où la construction est une activité très coûteuse, la construction de logiciels est relativement gratuite (compilez dans votre IDE et vous avez construit votre Si vous voyez l'écriture de code comme une activité de conception au lieu d'une activité de construction, le cycle de refactorisation rouge-vert est essentiellement un exercice de conception. Votre conception évolue au fur et à mesure que vous écrivez des tests, le code pour les satisfaire et intégrer le nouveau code dans le système existant.
TDD comme spécification
Les tests unitaires que vous écrivez pour TDD sont une traduction directe de la spécification telle que vous les comprenez. En écrivant du code qui satisfait le moins possible à vos spécifications (rend vos tests verts), tout le code que vous avez écrit est là dans un but spécifique. La réalisation ou non de cet objectif est validée par un test reproductible.
Ecrire des tests sur la fonctionnalité
Une erreur courante dans les tests unitaires se produit lorsque vous écrivez les tests après le code, vous finissez par tester que le code fait ce qu'il fait. En d'autres termes, vous verrez des tests comme celui-ci
public class PersonTest:Test
{
[Test]
TestNameProperty()
{
var person=new Person();
person.Name="John Doe";
Assert.AreEqual("John Doe", person.Name);
}
}
Bien que je suppose que ce code pourrait être utile (assurez-vous que quelqu'un n'a pas fait quelque chose d'obscène avec une simple propriété). Il ne sert pas à valider une spécification. Et comme vous l'avez dit, écrire ce genre de tests ne vous mène que si loin.
Alors que le vert est bon, la valeur réside en rouge J'ai eu mon premier vrai moment "aha" dans TDD quand j'ai eu un échec de test inattendu. J'avais une suite de tests que j'avais pour un framework que je construisais. Ajout d'une nouvelle fonctionnalité, j'ai écrit un test pour elle. Puis a écrit le code pour réussir le test. Compiler, tester ... a obtenu un vert sur le nouveau test. Mais j'ai également obtenu un rouge sur un autre test auquel je ne m'attendais pas à devenir rouge.
En regardant l'échec, je pousse un soupir de soulagement car je doute que j'aurais attrapé ce bug pendant un certain temps si je n'avais pas eu ce test en place. Et c'était un bug TRÈS désagréable à avoir. Heureusement, j'ai eu le test, et il m'a dit exactement ce que je devais faire pour corriger le bogue. Sans le test, j'aurais continué à construire mon système (avec le bogue infectant d'autres modules qui dépendaient de ce code) et au moment où le bogue a été découvert, cela aurait été une tâche majeure de le corriger correctement.
Le véritable avantage du TDD est qu'il nous permet d'apporter des changements avec un abandon téméraire. C'est comme un filet de sécurité pour la programmation. Pensez à ce qui se passerait si un trapéziste faisait une erreur et tombait. Avec le net, c'est une erreur embarrassante. Sans, c'est une tragédie. Dans le même ordre d'idées, TDD vous évite de transformer des erreurs audacieuses en catastrophes destructrices de projets.
Vous ne trouverez personne qui préconise le développement piloté par les tests, ou même le test piloté Design (ils sont différents), qui dit que les tests prouvent les applications. Alors, appelons simplement cela un homme de paille et terminons.
Vous ne trouverez personne qui n'aime pas ou qui n'est pas impressionné par TDD qui dit que les tests sont une perte de temps et d'efforts. Bien que les tests ne prouvent pas les applications, ils sont très utiles pour trouver des erreurs.
Cela dit, aucune des deux parties ne fait quoi que ce soit de différent en ce qui concerne la réalisation de tests sur le logiciel. Les deux font des tests. Les deux s'appuient sur les tests pour trouver autant de bogues que possible, et les deux utilisent des tests pour vérifier qu'un programme logiciel fonctionne aussi bien qu'il peut être découvert à ce moment-là. Personne avec un demi-indice ne vend de logiciel sans test et personne avec un demi-indice ne s'attend à ce que le test rende le code qu'il vend complètement sans jeu.
Ainsi, la différence entre TDD et non-TDD n'est pas que des tests sont en cours. La différence réside dans le moment où les tests sont écrits. En TDD, les tests sont écrits AVANT le logiciel. Dans les tests non TDD sont écrits après ou en collaboration avec le logiciel.
Le problème que j'ai vu à propos de ce dernier est que les tests ont alors tendance à cibler le logiciel en cours d'écriture plus que le résultat ou la spécification souhaités. Même si l'équipe de test est distincte de l'équipe de développement, l'équipe de test a tendance à regarder le logiciel, à jouer avec lui et à écrire des tests qui le ciblent.
Une chose qui a été remarquée maintes et maintes fois par ceux qui étudient la réussite d'un projet, est la fréquence à laquelle un client exposera ce qu'il veut, les développeurs se précipitent et écrivent quelque chose, et quand ils reviennent vers le client en disant "terminé" il s'avère que ce n'est absolument pas ce que le client a demandé. "Mais ça passe tous les tests ..."
Le but de TDD est de briser cet "argument circulaire" et de fournir une base pour les tests testant le logiciel qui n'est pas le logiciel lui-même. Les tests sont écrits pour cibler le comportement souhaité par le "client". Le logiciel est ensuite écrit pour réussir ces tests.
TDD est en partie de la solution destinée à résoudre ce problème. Ce n'est pas la seule étape que vous faites. Vous devez également vous assurer qu'il y a plus de commentaires des clients et plus souvent.
D'après mon expérience cependant, TDD est une chose très difficile à mettre en œuvre avec succès. Il est difficile de faire écrire des tests avant d'avoir un produit, car de nombreux tests automatisés nécessitent d'avoir quelque chose à jouer pour que le logiciel d'automatisation fonctionne correctement. Il est également difficile d'obtenir des développeurs qui ne sont pas habitués aux tests unitaires pour le faire. Maintes et maintes fois, j'ai dit aux gens de mon équipe d'écrire les tests en PREMIER. Je n'en ai jamais réussi à le faire. En fin de compte, les contraintes de temps et la politique ont détruit tous les efforts afin que nous ne fassions même plus de tests unitaires. Bien sûr, cela entraîne inévitablement un couplage accidentel et sévère de la conception de sorte que même si nous le voulions, sa mise en œuvre serait désormais prohibitive. Éviter CELA est ce que TDD fournit finalement aux développeurs.
TDD est pas une excuse pour ignorer la conception. J'en ai vu beaucoup sauter dans le train "agile" parce qu'ils pensaient pouvoir commencer à coder immédiatement. La véritable agilité vous amènera au codage statistique beaucoup plus rapidement que les bonnes pratiques d'ingénierie (dans d'autres domaines) qui ont inspiré le processus en cascade.
Quand on dit que le test conduit la conception, cela signifie simplement que l'on peut utiliser des tests très tôt dans la phase de conception, bien avant qu'il ne soit terminé. Faire ces tests influencera fortement votre conception en défiant les zones grises et en les opposant au monde réel bien avant la fin du produit. vous obligeant souvent à revenir à la conception et à l'ajuster pour en tenir compte.
À mon avis, TDD apporte simplement le test pour être une partie intégrale de la conception au lieu de quelque chose fait à la fin pour le valider. Au fur et à mesure que vous commencez à utiliser TDD, vous voyez comment détruire/casser votre système au fur et à mesure que vous le concevez. Personnellement, je ne fais pas toujours mes tests en premier. Bien sûr, je fais les tests (unitaires) évidents sur une interface, mais les gains réels proviennent des tests d'intégration et de spécification que je crée lorsque je pense à une manière nouvelle et créative que cette conception peut casser. Dès que je pense à un moyen, je code un test et je vois ce qui se passe. Parfois, je peux vivre avec la conséquence, dans ce cas, je déplace le test dans un projet distinct qui ne fait pas partie de la build principale (car il continuera à échouer).
Dans TDD, piloté ici signifie simplement que vos tests influencent si fortement votre conception que l'on peut sentir qu'ils la conduisent réellement. Mais on s'arrête là, et ici je comprends vos inquiétudes, c'est un peu effrayant ... qui anime la série?
VOUS conduisez, pas les tests. Les tests sont là pour qu'au fur et à mesure que vous avancez, vous gagnez un bon niveau de confiance dans ce que vous avez créé, ce qui vous permet de continuer à construire en sachant qu'il repose sur des bases solides.
Exactement, d'où le piloté dans le TDD. Ce ne sont pas tellement les tests qui conduisent le tout, mais ils auront une influence si profonde sur la façon dont vous faites les choses, sur la façon dont vous concevez et pensez votre système que vous déléguerez une grande partie de votre processus de réflexion aux tests et en retour ils auront une profonde influence sur votre conception.
arrêtez-vous là ... le génie logiciel est TRÈS différent de toutes les autres pratiques d'ingénierie. En fait, le génie logiciel a beaucoup plus en commun avec la littérature. On peut prendre un livre fini, en extraire 4 chapitres et écrire deux nouveaux chapitres pour les remplacer, les coller dans le livre et vous avez toujours un bon livre. Avec de bons tests et logiciels, vous pouvez déchirer n'importe quelle partie de votre système et le remplacer par un autre et le coût de le faire n'est pas beaucoup plus élevé qu'il ne l'avait créé en premier lieu. En fait, si vous avez fait vos tests et leur avez permis d'influer suffisamment sur votre conception, cela peut très bien être moins cher que de le créer en premier lieu, car vous aurez une certaine confiance que ce remplacement ne cassera pas ce que les tests couvrent.
Parce que les tests nécessitent un état d'esprit TRÈS différent de celui de la construction. Tout le monde n'est pas en mesure de revenir en arrière et de partir, en fait, certaines personnes ne seront pas en mesure de créer des tests appropriés simplement parce qu'elles ne peuvent pas se décider à détruire leur création. Cela produira des projets avec trop peu de tests ou des tests juste assez pour atteindre une métrique cible (la couverture du code vient à l'esprit). Ils se réjouiront des tests de trajectoire et des tests d'exception mais oublieront les cas d'angle et les conditions aux limites.
D'autres s'appuieront simplement sur des tests qui renonceront à la conception en tout ou en partie. Chaque membre fait sa chose puis s'intègre les uns aux autres. Le design est avant tout un outil de communication, des enjeux que nous mettons dans le sol pour dire que c'est là que je serai, des croquis qui disent que c'est là que seront les portes et les fenêtres. Sans cela, votre logiciel est condamné, quel que soit le nombre de tests que vous mettez dans la chose. L'intégration et les fusions seront toujours douloureuses et il leur manquera des tests au plus haut niveau d'abstractions.
Pour ces équipes, TDD n'est peut-être pas la voie à suivre.
Avec TDD, vous avez tendance à ne pas écrire de code qui n'est ni facile ni rapide à tester. Cela peut sembler être une petite chose, mais cela peut avoir un impact profond sur un projet car il a un impact sur la facilité de refactorisation, de test, de reproduction des bogues avec les tests et de vérification des correctifs.
Il est également plus facile pour un nouveau développeur du projet de se mettre à jour lorsque vous avez un meilleur code factorisé pris en charge par les tests.
J'y ai beaucoup réfléchi, même si je ne pratique pas beaucoup le TDD moi-même. Il semble y avoir une (forte?) Corrélation positive entre la qualité du code et le suivi du TDD.
1) Ma première prise de position est que, ce n'est (principalement) pas dû au fait que TDD ajoute une "meilleure qualité" dans le code (en tant que tel), c'est plus comme TDD aide à éliminer les pires parties et habitudes, et donc à augmenter indirectement la qualité.
Je dirais même que ce n'est pas le test lui-même - c'est le processus de écriture ces tests. Il est difficile d'écrire des tests pour un mauvais code, et vice versa. Et garder cela à l'arrière de la tête pendant la programmation, élimine beaucoup de mauvais code.
2) Un autre point de vue (cela devient philosophique) est de suivre les habitudes mentales du maître. Vous n'apprenez pas à devenir un maître en suivant ses "habitudes extérieures" (comme une longue barbe, c'est bien), vous devez apprendre ses façons de penser internes, et c'est difficile. Et en quelque sorte, les programmeurs (novices) suivent TDD, alignent leurs façons de penser plus près de celles du maître.
L'approche "test d'écriture + refactorisation jusqu'à réussite" semble incroyablement anti-ingénierie.
Vous semblez avoir une idée fausse sur le refactoring et le TDD.
Refactoring de code est le processus de modification du code source d'un programme informatique sans modifier son comportement fonctionnel externe afin d'améliorer certains des attributs non fonctionnels du logiciel.
Ainsi, vous ne pouvez pas refactoriser coder jusqu'à ce qu'il passe.
Et TDD, en particulier les tests unitaires (que je considère comme l'amélioration principale, car d'autres tests me semblent plutôt plausibles), ne consiste pas à repenser un composant jusqu'à ce qu'il fonctionne. Il s'agit de concevoir un composant et de travailler sur l'implémentation jusqu'à ce que le composant fonctionne comme prévu.
Il est également important de bien comprendre que unité le test consiste à tester unités. En raison de la tendance à toujours écrire beaucoup de choses à partir de zéro, il est important de tester de telles unités. Un ingénieur civil connaît déjà les spécifications des unités qu'il utilise (les différents matériaux) et peut s'attendre à ce qu'elles fonctionnent. Ce sont deux choses qui ne s'appliquent souvent pas aux ingénieurs logiciels, et il est très pro-ingénierie de tester les unités avant de les utiliser, car cela signifie utiliser des composants testés et de haute qualité.
. , etc.) et ensuite tester et affiner jusqu'à ce qu'il les rencontre.
C'est pourquoi TDD fonctionne. Parce que si vous construisez un logiciel d'unités testées, les chances sont bien meilleures que cela fonctionne, lorsque vous les branchez ensemble et si ce n'est pas le cas, vous pouvez vous attendre à ce que le problème soit dans votre code de colle, en supposant que vos tests ont une bonne couverture.
modifier:
Refactoring signifie: aucun changement de fonctionnalité. Un point d'écriture du test unitaire est de s'assurer que le refactoring ne casse pas le code. Donc, TDD est censé garantir que le refactoring n'a pas d'effets secondaires.
La granularité n'est pas un sujet de perspective, car comme je l'ai dit, les tests unitaires testent les unités et non les systèmes, la granularité étant définie exactement.
TDD encourage une bonne architecture. Il vous oblige à définir et implémenter des spécifications pour toutes vos unités, vous obligeant à les concevoir avant l'implémentation, ce qui est tout à fait le contraire de ce que vous semblez penser. TDD impose la création d'unités, qui peuvent être testées individuellement et sont ainsi complètement découplées.
TDD ne signifie pas que je lance un test logiciel sur le code spaghetti et remue les pâtes jusqu'à ce qu'elles passent.
Contrairement au génie civil, en génie logiciel, un projet évolue généralement en permanence. En génie civil, vous devez construire un pont en position A, capable de transporter x tonnes et suffisamment large pour n véhicules par heure.
En génie logiciel, le client peut décider à tout moment (éventuellement après l'achèvement), il veut un pont à double étage, qu'il veut le connecter à l'autoroute la plus proche, et qu'il aimerait que ce soit un ascenseur pont, parce que sa société a récemment commencé à utiliser des voiliers.
Les ingénieurs logiciels sont chargés de modifier les conceptions. Non pas parce que leurs conceptions sont imparfaites, mais parce que c'est le modus operandi. Si le logiciel est bien conçu, il peut être repensé à un niveau élevé, sans avoir à réécrire tous les composants de bas niveau.
TDD consiste à créer un logiciel avec des composants hautement découplés testés individuellement. Bien exécuté, il vous aidera à répondre aux changements d'exigences de manière sensiblement plus rapide et plus sûre que sans.
TDD ajoute des exigences au processus de développement, mais il n'interdit aucune autre méthode d'assurance qualité. Certes, TDD n'offre pas la même sécurité que la vérification formelle, mais là encore, la vérification formelle est extrêmement coûteuse et impossible à utiliser au niveau du système. Et pourtant, si vous le vouliez, vous pourriez combiner les deux.
TDD englobe également les tests autres que les tests unitaires, qui sont effectués au niveau du système. Je trouve cela facile à expliquer mais difficile à exécuter et difficile à mesurer. De plus, ils sont tout à fait plausibles. Bien que je vois absolument leur nécessité, je ne les considère pas vraiment comme des idées.
Au final, aucun outil ne résout réellement un problème. Les outils ne font que faciliter la résolution d'un problème. Vous pouvez demander: Comment un ciseau m'aidera-t-il avec une grande architecture? Eh bien, si vous prévoyez de faire des murs droits, les briques droites sont utiles. Et oui, d'accord, si vous donnez cet outil à un idiot, il finira probablement par le frapper du pied, mais ce n'est pas la faute du ciseau, car ce n'est pas un défaut de TDD qui donne une fausse sécurité aux novices, qui n'écrivent pas de bons tests.
Donc, en fin de compte, on peut dire que TDD fonctionne beaucoup mieux que pas de TDD.
Je pense que vous approchez le premier point du mauvais angle.
D'un point de vue théorique, nous prouvons que quelque chose fonctionne en vérifiant les points de défaillance. C'est la méthode utilisée. Il peut y avoir de nombreuses autres façons de prouver que quelque chose est fonctionnel, mais TDD s'est imposé en raison de la simplicité de son approche bit par bit: s'il ne casse pas, cela fonctionne.
En pratique, cela se traduit tout simplement par: nous pouvons maintenant passer à la chose suivante (après avoir appliqué avec succès TDD pour satisfaire tous les prédicats). Si vous approchez TDD de ce point de vue, il ne s'agit pas de "écrire des tests + refactoriser jusqu'à ce que vous passiez", il s'agit plutôt de avoir terminé cela, je me concentre maintenant entièrement sur la fonctionnalité suivante comme étant maintenant la plus chose importante .
Pensez à comment cela s'applique au génie civil. Nous construisons un stade pouvant accueillir un public de 150000 personnes. Après avoir prouvé que l'intégrité structurelle du stade est saine, nous avons satisfait la sécurité d'abord. Nous pouvons maintenant nous concentrer sur d'autres questions qui deviennent immédiatement importantes, telles que les toilettes, les stands de nourriture, les sièges, etc ... ce qui rend l'expérience du public plus agréable. C'est une simplification excessive, car il y a beaucoup plus à TDD, mais le point crucial est que vous ne faites pas la meilleure expérience utilisateur possible si vous vous concentrez à la fois sur des fonctionnalités nouvelles et passionnantes et que vous maintenez l'intégrité en même temps. Vous obtenez à mi-chemin dans les deux cas. Je veux dire, comment pouvez-vous savoir exactement comment de nombreuses toilettes et où devriez-vous placer pour 150000 personnes? J'ai rarement vu des stades s'effondrer de mon vivant, mais j'ai dû faire la queue à la mi-temps à de nombreuses reprises. Cela signifie que le problème des toilettes est sans doute plus complexe et que si les ingénieurs peuvent consacrer moins de temps à la sécurité, ils pourront enfin résoudre le problème des toilettes.
Votre deuxième point n'est pas pertinent, car nous avons déjà convenu que les absolus sont un effort des fous et parce que Hank Moody dit qu'ils n'existent pas (mais je n'arrive pas à trouver de référence pour cela).
Je n'aime pas que vous disiez "le test, plutôt que l'utilisateur, définit l'exigence". Je pense que vous envisagez uniquement les tests unitaires dans TDD, alors que cela couvre également les tests d'intégration.
En plus de tester les bibliothèques qui constituent la base du logiciel, écrivez les tests qui couvrent les interactions de vos utilisateurs avec le logiciel/site Web/quoi que ce soit. Ceux-ci viennent directement des utilisateurs, et des bibliothèques comme le concombre (http://cukes.info) peuvent même laisser vos utilisateurs écrire les tests eux-mêmes, en langage naturel.
TDD encourage également la flexibilité du code - si vous passez une éternité à concevoir l'architecture de quelque chose, il sera extrêmement difficile d'apporter ces modifications plus tard si nécessaire. Commencez par écrire quelques tests, puis écrivez un petit code qui réussit ces tests. Ajoutez plus de tests, ajoutez plus de code. Si vous devez changer radicalement le code, vos tests sont toujours valides.
Et contrairement aux ponts et aux voitures, un seul logiciel peut subir d'énormes changements au cours de sa durée de vie, et effectuer une refactorisation complexe sans que les tests soient écrits en premier n'est que demander pour des problèmes.
Si vous acceptez que plus les bogues sont détectés tôt, moins le coût de leur correction est important, alors que seul TDD en vaut la peine.
TDD en génie logiciel est une bonne pratique, de la même manière que la gestion des erreurs dans les applications est une bonne pratique ainsi que la journalisation et les diagnostics (bien que cela fasse partie de la gestion des erreurs).
TDD ne doit pas être utilisé comme un outil pour réduire le développement logiciel en codage d'essai et d'erreur. Mais encore, la plupart des programmeurs regardent les journaux d'exécution, observent les exceptions dans le débogueur ou utilisent d'autres signes d'échec/succès pendant leur phase de développement qui consiste à coder/compiler/exécuter l'application - toute la journée.
TDD n'est qu'un moyen de formaliser et d'automatiser ces étapes pour vous rendre en tant que développeur plus productif.
1) Vous ne pouvez pas comparer l'ingénierie logicielle à la construction de ponts, la flexibilité dans la construction de ponts n'est pas du tout proche de celle de la conception d'un logiciel. Construire le pont, c'est comme écrire le même programme encore et encore dans une machine avec perte. Les ponts ne peuvent pas être dupliqués et réutilisés comme le peuvent les logiciels. Chaque pont est unique et doit être fabriqué. Il en va de même pour les voitures et autres modèles.
La chose la plus difficile en génie logiciel est de reproduire les pannes, quand un pont tombe en panne, il est généralement très facile de déterminer ce qui s'est mal passé, et il est facile en théorie de reproduire la panne. Lorsqu'un programme informatique échoue, il peut s'agir d'une chaîne complexe d'événements qui a mis le système dans un état défectueux et il peut être très difficile de déterminer où se trouve l'erreur. Le TDD et le test unitaire facilitent le test de la robustesse des composants logiciels, des bibliothèques et des algorithmes.
2) L'utilisation de tests unitaires faibles et de cas de test superficiels qui n'insistent pas sur le système pour créer un faux sentiment de confiance n'est qu'une mauvaise pratique. Ignorer la qualité architecturale d'un système et simplement remplir les tests est bien sûr aussi mauvais. Mais tricher sur le lieu de construction d'un gratte-ciel ou d'un pont pour économiser du matériel et ne pas suivre les plans est aussi mauvais et cela arrive tout le temps ...
Je vais vous donner une réponse courte. En règle générale, TDD est mal vu, tout comme les tests unitaires. Je n'ai jamais compris les tests unitaires jusqu'à récemment après avoir regardé une bonne vidéo de discussion technique. Essentiellement, TDD indique simplement que vous voulez que les choses suivantes FONCTIONNENT. Ils DOIVENT être mis en œuvre. Ensuite, vous concevez le reste du logiciel comme vous le feriez normalement.
C'est un peu comme écrire des cas d'utilisation pour une bibliothèque avant de concevoir la bibliothèque. Sauf que vous pouvez changer le cas d'utilisation dans une bibliothèque et vous pourriez ne pas le faire pour TDD (j'utilise TDD pour la conception d'API). Vous êtes également encouragé à ajouter plus de test et à penser aux entrées/utilisations sauvages que le test peut obtenir. Je trouve cela utile lors de l'écriture de bibliothèques ou d'API où si vous changez quelque chose, vous devez savoir que vous avez cassé quelque chose. Dans la plupart des logiciels quotidiens, je ne me dérange pas car pourquoi ai-je besoin d'un cas de test pour un utilisateur appuyant sur un bouton ou si je veux accepter une liste CSV ou une liste avec une entrée par ligne ... Cela n'a pas vraiment d'importance, je suis autorisé pour le changer ainsi je ne devrais pas/ne peux pas utiliser TDD.
Pourquoi TDD fonctionne-t-il?
Ce n'est pas le cas.
Clarification: les tests automatisés valent mieux que pas de tests. Cependant, je pense personnellement que la plupart des tests unitaires sont des déchets, car ils sont généralement tautologiques (c'est-à-dire que les choses sont évidentes d'après le code réel testé) et il ne peut pas être facilement prouvé qu'ils sont cohérents, non redondants et couvrent tous les cas de frontière (où des erreurs se produisent généralement ).
Et le plus important: une bonne conception logicielle ne tombe pas comme par magie des tests, comme cela est annoncé par de nombreux évangélistes agiles/TDD. Quiconque prétend le contraire, veuillez fournir des liens vers des recherches scientifiques évaluées par des pairs qui le prouvent, ou du moins une référence à un projet open source où les avantages du TDD peuvent être potentiellement étudiés par son historique de modifications de code.
TDD n'est pas vraiment une question de test. Et ce n'est certainement pas un remplacement pour de bons tests. Ce qu'il vous donne est un design bien pensé, facile à consommer par le consommateur, et facile à entretenir et à refaçonner plus tard. Ces éléments entraînent à leur tour moins de bogues et une conception logicielle meilleure et plus adaptable. TDD vous aide également à réfléchir et à documenter vos hypothèses, constatant souvent que certaines d'entre elles étaient incorrectes. Vous les découvrez très tôt dans le processus.
Et comme avantage côté Nice, vous avez une grande suite de tests que vous pouvez exécuter pour vous assurer qu'un refactoring ne change pas le comportement (entrées et sorties) de votre logiciel.
Le logiciel est organique, lorsque l'ingénierie structurelle est concrète.
Lorsque vous construisez votre pont, il restera un pont et il est peu probable qu'il évolue vers quelque chose d'autre dans un court laps de temps. Des améliorations seront apportées au fil des mois et des années, mais pas des heures et des jours comme dans les logiciels.
Lorsque vous testez de manière isolée, il existe normalement deux types de frameworks que vous pouvez utiliser. Cadre contraint et sans contrainte. Les frameworks sans contrainte (en .NET) vous permettent de tout tester et de tout remplacer, quels que soient les modificateurs d'accès. C'est à dire. vous pouvez coller et simuler des composants privés et protégés.
La plupart des projets que j'ai vus utilisent des frameworks contraints (RhinoMocks, NSubstitute, Moq). Lorsque vous testez avec ces frameworks, vous devez concevoir votre application de manière à pouvoir injecter et remplacer des dépendances lors de l'exécution. Cela implique que vous devez avoir une conception faiblement couplée. Une conception faiblement couplée (lorsqu'elle est bien faite) implique une meilleure séparation des préoccupations, ce qui est une bonne chose.
Pour résumer, je pense que penser derrière cela, c'est que si votre conception est testable, elle est donc faiblement couplée et elle a une bonne séparation des préoccupations.
En passant, j'ai vu des applications qui étaient vraiment testables, mais mal écrites du point de vue de la conception orientée objet.