Nunit: [Meta] Politique de gestion des versions

Créé le 3 sept. 2017  ·  42Commentaires  ·  Source: nunit/nunit

Motivation

Il m'a semblé que chaque fois que l'un de nous faisait allusion au fait que nous suivons semver, un autre nous rappelait le fait que nous ne le faisions pas. Je voulais mieux comprendre cela depuis un certain temps.

Nous avons récemment décidé d'apporter des modifications révolutionnaires à l'API dans la version 3.8, ce qui a causé quelques problèmes à @BlythMeister :

https://github.com/nunit/nunit/issues/1324 , https://github.com/nunit/nunit/pull/2239 , https://github.com/nunit/nunit/issues/2360

Je n'ai pas pu trouver de politique définitive sur https://github.com/nunit/docs/wiki , je suis donc d'accord avec l'attente de @BlythMeister que nous suivions semver.

Dans ce cas particulier, je pense que nous avons été suffisamment clairs sur les changements de rupture dans notre description de version GitHub, mais notre package NuGet ne contient pas de notes de version ni d'indice indiquant que la version 3.8 apporte des changements de rupture.

Histoire

Dans https://github.com/nunit/nunit/issues/981 , @rprouse suggère :

Majeur - Est actuellement 3 et ne changera pas à moins qu'il n'y ait des changements de rupture à l'API. J'espère pas avant longtemps 😄

@CharliePoole répond que c'est ce sur quoi nous sommes d'accord :

  • La version majeure sera la 3 et elle ne changera pas avant longtemps.

https://github.com/nunit/nunit/pull/932 suppose que nous faisons semver
https://github.com/nunit/nunit/pull/1054#issuecomment -158657202 implique que nous faisons semver
https://github.com/nunit/nunit-console/issues/23#issuecomment -304690092 dit que nous n'avons jamais suivi semver
https://github.com/nunit/nunit/issues/2219#issuecomment -307817965 dit que nous ne sommes pas sûrs de le faire semver
https://github.com/nunit/nunit/pull/2338#issuecomment -318861589 dit que nous ne suivons pas semver
https://github.com/nunit/nunit3-vs-adapter/pull/313#issuecomment -298882194 suppose que nous faisons semver
https://github.com/nunit/nunit/issues/2397#issuecomment -326084812 dit clairement que nous ne suivons plus semver

Sans parler des nombreuses fois où nous parlons d'enregistrer les modifications avec rupture pour la v4, telles que
https://github.com/nunit/nunit/issues/2397#issuecomment -326075254.

Ici, nous avons une discussion sur les changements cassants par rapport aux changements cassants :
https://github.com/nunit/nunit-console/issues/23#issuecomment -303441716 Quelque chose sur lequel nous devrions explicitement nous mettre d'accord et documenter.

(Curieusement, tous les problèmes de nunit qui contiennent les mots "breaking change" sont également des problèmes qui "involves:jnm2"... oO)

Proposition

La prévalence du semver devrait nous amener à reconsidérer et à respecter le modèle mental de la plupart des utilisateurs.

Quoi que nous fassions, en particulier si cela va à l'encontre du courant dominant, une communication parfaitement claire est essentielle.
Même si notre politique est de ne jamais mettre de changements de rupture dans les notes de version et de tirer une pièce pour décider
quelle partie et quand incrémenter, tous ceux qui dépendent de NUnit doivent savoir précisément à quoi nous nous engageons. Cela inclut le contrat API et le contrat comportemental, isolément ainsi qu'une combinaison de projets clés.

Nous devrions attirer l'attention des gens avec un lien vers la politique de version en haut de chaque note de version et page de version GitHub et readme et wiki et page de documentation où cela pourrait éventuellement être pertinent. Idéalement, nous devrions également nous engager à appeler les changements de rupture en haut séparément du reste des notes de publication et sur la page de publication de GitHub, comme Rob l'a fait très précisément, mais dans la description du package NuGet, nous devrions également ajouter une note à la top qu'il y a des changements avec rupture dans la version. La plupart du temps, c'est tout ce que les gens voient.

Avis : Il n'est pas possible d'être trop clair. La portée de la stratégie de gestion des versions doit être soit pour tous les projets clés, soit pour chaque projet clé individuellement.

high

Commentaire le plus utile

SemVer sonne bien. Beaucoup de gens l'ont utilisé, bien sûr, tandis que beaucoup d'autres ont rencontré des problèmes. Jusqu'à présent, nous sommes dans ce dernier groupe, ayant pris la décision provisoire d'utiliser SemVer et réalisant ensuite que nous ne pouvions pas le faire à moins de commencer à publier une nouvelle version majeure tous les six mois environ.

Voici les problèmes que je connais qui se sont dressés (et se dressent probablement encore) sur notre chemin :

  1. Nous n'avons pas de définition convenue de la ou des API que nous ne romprons pas. Historiquement, l'équipe NUnit (y compris la V2) s'est développée dans un style où tout était public pour pouvoir être testé. (C'était avant que "InternalsVisibleTo" n'existe) L'équipe ne considérait essentiellement que les attributs et les assertions comme "vraiment" publics (ou publiés si vous le souhaitez) et n'avait aucun scrupule à changer d'autres choses. Nous savions ce qui était destiné uniquement à notre propre usage, bien que cela n'ait pas toujours été clair pour les autres.

J'ai continué à travailler de la même manière sur NUnit 3 en 2007, simplement parce que c'était comme ça que les choses avaient toujours été faites et aussi parce que je supportais des plates-formes plus anciennes. Après 2010 environ, d'autres personnes ont rejoint l'équipe et certaines d'entre elles avaient des idées différentes sur ce que "casser" signifiait. À l'extrême, cela pourrait signifier "toute classe ou méthode qu'un utilisateur __pourrait__ avoir utilisée, que nous voulions qu'elle soit utilisée ou non". En guise de compromis, nous avons proposé l'espace de noms interne, qui visait à dire aux gens qu'ils utilisaient ces méthodes à leurs risques et périls. Je dois dire que j'ai été surpris de voir combien de programmeurs, même ceux qui utilisent de manière assez sophistiquée les méthodes "internes", semblent ne pas avoir remarqué qu'elles étaient considérées comme "internes" et donc sujettes à changement.

Dans ce contexte, ma propre conclusion est que nous devrions __uniquement__ rendre visibles à l'avenir les méthodes que nous avons l'intention de prendre en charge. Cependant, ce n'est pas la fin de l'histoire.

  1. NUnit prend en charge plusieurs API destinées à plusieurs publics. (Notez que je parle ici du framework. Il y a encore plus d'API si vous tenez compte du moteur, de la console et de l'adaptateur.) Chacune de ces API souffre du même problème de définition que celui indiqué ci-dessus. Chacun d'eux __pourrait__ avoir un niveau différent de déclenchement de ce que nous considérons comme un changement "rupteur". Les principales API auxquelles je pense sont :

    • L'API d'écriture de test - assertions, attributs, TestContext et quelques autres choses.

    • L'API d'extensibilité, principalement des interfaces à utiliser pour créer des attributs personnalisés.

    • L'API d'exécution, une API de réflexion uniquement exposée par le framework pour permettre l'écriture des runners.

    • L'API de ligne de commande NUnitLite, qui est fournie avec le framework.

Une interruption de __n'importe laquelle__ de ces API devrait-elle déclencher une version majeure ? Je ne sais pas, mais j'en doute. Pour être certain, je pense qu'il faudrait définir plus clairement ce qui est inclus dans chacun d'eux. Si on veut que le déclenchement soit différent pour chacun, il faut le dire clairement car la visibilité, en soi, ne suffit plus car chacun d'entre eux doit être visible d'une certaine manière.

  1. Qu'en est-il des bogues ? Nous avons plusieurs fois corrigé (ce que nous considérons être) des bogues, puis nous avons constaté que quelqu'un en dépendait. Je sais que presque tous les bogues de longue durée peuvent finir par être considérés comme une fonctionnalité, mais je pense que nous devons résister à cette tendance dans la mesure du possible.

Ce ne sont que mes pensées. J'essaie personnellement de mieux définir ce qui est public et ce qui ne l'est pas dans mon travail sur TestCentric mais NUnit a une histoire et il est difficile de s'en éloigner !

Tous les 42 commentaires

Je suis d'accord que nous avons besoin de clarté.

Historiquement, nous avions l'intention de suivre semver dans NUnit 3 et c'est pourquoi nous l'avons dit. Cependant, nous avons trouvé de nombreuses raisons de ne pas le faire au fur et à mesure. Nous ne sommes pas seuls dans ce cas et SemVer est loin d'être universellement accepté.

Quelques lectures intéressantes...

Il est important de se rappeler que SemVer est explicitement destiné aux API de bibliothèque. Cela n'a rien à voir avec d'autres choses où nous parlons de compatibilité et de changements avec rupture, comme les options de ligne de commande, le fonctionnement précis des attributs ou la valeur par défaut des paramètres. Avant de pouvoir obtenir des éclaircissements sur la compatibilité et la gestion des versions, nous avons besoin de clarté sur les choses dont nous parlons exactement et nous devrions probablement établir une hiérarchie parmi ces choses qui nous dit ce que nous considérons comme le plus important. Parfois, nous devons choisir entre casser deux choses différentes.

J'aime particulièrement le deuxième post que vous avez lié. Je connais le premier.

Je viens juste d'éprouver le besoin de passer à la version 3.8 pour obtenir un correctif de performances, mais ensuite de trouver 2 changements de rupture entre la version 3.7 et la version 3.8, j'ai été quelque peu surpris

En faisant ces recherches, j'ai trouvé que nunit avait des attributs obsolètes dans une version antérieure qu'il semble que d'autres (y compris specflow utilisaient)
Dans 3.8, ceux-ci ont été supprimés.
Cela force les mises à niveau d'autres bibliothèques. Dans ce cas, specflow a également augmenté ses exigences de nuget, ce qui a causé des problèmes.

J'ai également trouvé une suppression de contient des contraintes (ou quelque chose comme ça) donc quand je suis passé de 3.7 à 3.8, j'ai dû corriger toute une série de tests.

Si j'étais passé de 3.7 à 4, je m'attendrais à des changements de rupture, mais regrouper les correctifs de bogues/performances et les changements de rupture dans la même version rend la tâche très difficile pour les consommateurs qui souhaitent se tenir au courant.

En repensant aux deux changements qui vous ont affecté @BlythMeister , ce sont peut-être de mauvaises décisions. Ni l'un ni l'autre n'étaient particulièrement "nécessaires" - à la fois plus pour la santé mentale du code. Les deux ont été faits avec l'idée qu'ils devraient affecter un minimum d'utilisateurs et être très simples à corriger - il est clair que le scénario specflow n'était pas celui auquel nous pensions.

J'ai également trouvé une suppression de contient des contraintes (ou quelque chose comme ça) donc quand je suis passé de 3.7 à 3.8, j'ai dû corriger toute une série de tests.

Ça, ça m'intéresse plus. Ce changement n'aurait pas dû impliquer de changement avec rupture ? Le seul changement visible aurait dû être la dépréciation du constructeur CollectionContainsConstraint. À quels problèmes avez-vous été confronté ?

Ma principale préoccupation à propos de SemVar est que nous apportons des modifications mineures dans presque toutes les versions. Il y a quelque chose d'une priorité avec la gestion des versions de NUnit selon laquelle les "versions majeures" sont une chose de type une fois tous les x ans. Cela ne veut pas dire que nous ne pouvons pas changer cela - mais ce serait un changement par rapport à ce que nous avons fait historiquement. (Bien que je déteste l'excuse "toujours fait comme ça" ! :sourire: )

Je suis assez ambivalent quant à ce que nous choisissons. Je ne pense pas que nous devrions trop fonder cette décision sur le fait que nous avons peut-être pris quelques décisions fragiles en matière de rétrocompatibilité dans la version 3.8.

@ChrisMaddock je suis d'accord avec vous en principe.
La discussion originale sur Gitter était plus pour souligner les points faibles que j'avais plutôt que de demander à NUnit de changer la façon dont il est versionné :)

en ce qui concerne l'exemple de code, nous avions

Assert.That(myCollection, Contains.Item(expectedItem).Using(new MyCustomComparer()));

La méthode .Using fluent existait en 3.7, mais a été supprimée en 3.8.
Je n'ai pas trouvé d'équivalent, j'ai donc changé pour

Assert.That(myCollection.Contains(expectedItem, new MyCustomComparer()));

Ce qui était un changement assez simple comme 1 off, mais nous avions environ 100 utilisations de cette ancienne syntaxe avec différents noms de collection, des noms attendus comme comparateurs (sur différents projets)

@BlythMeister C'est très malheureux. En fait, @ChrisMaddock a repéré le Using manquant dans aa19bd1e5ca27b0b1ee74af03a0d8e0a9f60968e (voir https://github.com/nunit/nunit/pull/2239#discussion_r126304266), nous avons donc ajouté Using au EqualConstraint dans bd2091b3a0406a5a6653dbcf3979a4c02c81602c. Cependant, le PR entier contenait une erreur, donc Contains.Item a de nouveau été modifié dans 15c27e5b5b55d2aa17d7627d7fa9e5a4a468fd60 pour renvoyer un ContainsConstraint au lieu d'un EqualConstraint . Et actuellement ContainsConstraint n'a pas de méthode Using .

Personnellement, je pense que nous devrions (ré)introduire cela dans la 3.9 (et aussi nous devrions avoir plus de tests, afin de ne pas supprimer des fonctionnalités sans s'en apercevoir).

J'aime le son de le réparer ;)
Cet attribut supprimé pourrait-il également être rajouté afin que je n'aie pas encore besoin de mettre à jour specflow; p

Correctif ?

(et aussi nous devrions avoir plus de tests, afin que nous ne supprimions pas la fonctionnalité sans s'en apercevoir).

J'ai toujours rêvé de faire quelque chose de similaire à ce que l'équipe corefx fait avec l'API publique : générer un fichier pseudo-cs contenant toutes les définitions publiques (et protégées) et le comparer à la liste pré-générée actuellement approuvée et dédiée au contrôle de source et échouer au test s'ils ne sont pas identiques. De cette façon, nous sommes empêchés d'avancer sans modifier le fichier approuvé dans le même PR, ce qui nécessite une discussion de compatibilité.

Fondamentalement, un instantané teste notre API visible de l'extérieur. C'est exactement là où j'ai pensé quand https://github.com/nunit/nunit/issues/2392 est apparu.

J'aime l'idée.

Nous devons être clairs __de laquelle__ de nos différentes API nous parlons.

Je dirais que l'intégralité de l'API du framework moins l'espace de noms interne en bénéficierait.

Le framework présente trois API, du moins comme nous en avons parlé et écrit jusqu'à présent...

  1. Celui utilisé par les utilisateurs écrivant des tests.
  2. L'API d'extensibilité.
  3. L'API utilisée par le moteur et certains exécuteurs tiers.

Le numéro (3) est ce que les docs appellent réellement l'API Framework.

Il serait bon de tester tous ces éléments, mais ils ont des considérations et des priorités différentes.

IMO il n'y a aucun inconvénient à être précis. :-)

Nous pourrions même en faire une étape msbuild dans tous les csprojs du framework afin que les modifications de l'API apparaissent dans git et que la boucle de rétroaction soit plus rapide pour quiconque apporte des modifications. J'ai déjà fait ce genre de chose.

Merde - je pensais que nous avions tout attrapé là-bas. 😞

@BlythMeister @mikkelbu - merci pour l'analyse, j'en ai créé https://github.com/nunit/nunit/issues/2412 . J'ai peur de ne pas avoir le temps de le réparer moi-même pour le moment, l'un de vous est-il intéressé ?

J'aime bien l'idée de réflexion de l'API. Pour NUnit 4 - nous devrions certainement obtenir ce qui devrait être public par rapport à ce qui devrait être interne correctement configuré ! 😄

@ChrisMaddock toutes les chances que l'attribut puisse également faire un retour afin de ne plus se casser sur les anciens specflow.

@BlythMeister - Je pense que cela devrait être discuté plus avant, mais j'y serais favorable, oui. Je pense que lorsque nous l'avons supprimé, nous avons principalement considéré les utilisations dans le code utilisateur qui pourraient être facilement mises à jour, et non les bibliothèques tierces.

Souhaitez-vous créer un nouveau numéro pour le faire ?

@ChrisMaddock terminé, merci.

Chaque changement public est un changement de rupture d'Eric Lippert souligne qu'il est toujours possible de trouver du code qui démontre une rupture au niveau de la source, quelle que soit l'innocuité de votre ajout d'API. Nous ne parlons pas ici de noir et blanc, mais plutôt d'une échelle mobile. Espérons une échelle qui réponde aux attentes générales.

Cela dit, l'équipe .NET a mis sa réflexion et son expérience au fil des ans dans cette directive : https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/breaking-change-rules.md

Je pense que nous devrions faire de notre mieux pour placer très près de ce document sur l'échelle mobile que j'ai mentionnée.

Comme tout ce que les humains font d'autre qui est à distance de la complexité du langage, les hypothèses sont désordonnées.
C'est pourquoi je continue à pousser si fort pour pécher par excès d'explicitation.

@ jnm2 Ce sont deux bonnes références qui illustrent un point que j'ai essayé de faire valoir et que je fais peut-être mal. Les équipes ou les individus doivent faire du travail pour arriver à de telles choses. Nous ne pouvons pas nous contenter d'utiliser le terme "changement de rupture" sans définir exactement ce qu'il signifie __pour nous__.

Une grande partie de mon travail de conseil/coaching précédent consistait à aider les équipes à se remettre d'une situation où quelqu'un en dehors de l'équipe (un patron généralement) avait créé un tas de normes qu'ils étaient tenus d'adopter. Beaucoup de ces normes étaient bonnes - du moins elles l'auraient été si elles avaient été générées par l'équipe elle-même. La nature humaine étant ce qu'elle est, la plupart de ces équipes ont trouvé le moyen de suivre la lettre des normes tout en violant l'esprit.

Dans un plus petit nombre de cas, l'équipe a volontairement adopté la norme de quelqu'un d'autre mais n'a pas réfléchi à la façon dont elle s'appliquait à elle. Ces équipes étaient plus faciles à secourir, car il n'y avait pas de pression extérieure sur elles, mais elles avaient tout de même besoin d'être récupérées.

Les équipes doivent avoir le temps et passer du temps à comprendre ces choses qui s'appliquent à elles. Dans ce cas, je vois à la fois dans l'article d'Eric et dans les directives de l'équipe .NET comment la réflexion a permis de définir ce que l'on entend par un changement avec rupture. Nous n'avons pas fait cela. J'ai essayé de nous inciter à le faire mais - et c'est à cause de mon interprétation de mon propre rôle ici maintenant - sans vraiment le définir moi-même. Cela ne fonctionne pas donc je vais essayer... en effet plusieurs coups.

  1. "Tout changement qui déconcerte un utilisateur de quelque manière que ce soit." Eh bien, c'est idiot, mais je l'explique parce que cela semble souvent être la définition que nous utilisons et c'est la définition que certains de nos utilisateurs semblent vouloir. 😄

  2. "Toute modification entraînant un comportement différent du code utilisateur lors de l'exécution." Il s'agit d'une norme stricte, car elle n'oblige pas l'utilisateur à utiliser correctement le code.

  3. "Tout changement qui entraîne un comportement différent du code utilisateur lors de l'exécution, à condition qu'il utilise le code correctement." Je suis en faveur de cette orientation à long terme, mais cela nécessite une élaboration plus approfondie (ci-dessous).

  4. "Toute modification entraînant l'échec de la compilation du code utilisateur." Ce serait un bon objectif initial à mon avis. C'est celui qu'utilise Eric Lippert. Cependant, il a également besoin d'une sorte de condition qu'ils utilisent le code comme prévu. Par exemple, nous avons un problème qui indique que nous voulons créer des tas de choses internal plutôt que public . Eh bien, ça va être un changement radical.

Mon choix serait de commencer par le niveau 4 et de passer au niveau 3 après avoir maîtrisé le 4. Un prérequis pour tout cela est de définir quelle est l'API ou les API que nous protégeons.

En traitant le cas 4, je pense que nunit sera dans un meilleur endroit.
Si vous considérez .net comme un framework, il est très rare qu'ils sortent réellement quelque chose de public.
Essentiellement, la plupart du temps, vous pouvez mettre à niveau le framework et n'obtenir aucune erreur de compilation.

Comme démontré lorsque j'ai commenté pour ouvrir ce problème, nunit 3.7 à 3.8 avait (au moins) 2 changements que je peux voir sont à l'avantage du cadre, mais ont causé la non-compilation de mon code (de nombreuses quantités).

@BlythMeister Je suis d'accord avec votre problème. Cependant, dans votre cas, vous avez été brisé par __des méthodes publiques destinées à la consommation publique__.

Comme je l'ai déjà dit, NUnit avait historiquement de nombreux types et méthodes publics __non destinés à la consommation publique__. C'était principalement à des fins de test (avant que InternalsVisibleTo n'existe) et secondairement en raison de la tradition des frameworks de tests unitaires développés dans des langages sans visibilité.

Nous devons changer cela, mais nous devons le faire de manière ordonnée. Malheureusement, bon nombre des changements qui seront nécessaires pour y arriver sont en fait des changements de rupture. Mon point de vue est que tout changement de rupture causé par la prise de méthodes qui se trouvent dans notre espace de noms Internal et leur donnant une visibilité internal est regrettable mais nécessaire. Les personnes qui les utilisaient devaient en fait référencer un espace de noms appelé Internal - alors que pensaient-ils qu'il se passerait ? 😄

Les personnes qui les utilisaient devaient en fait référencer un espace de noms appelé Internal - alors que pensaient-ils qu'il se passerait ? 😄

Pour ce que ça vaut, d'autres frameworks laissent une énorme surface publique sous une série d'espaces de noms internes. Je ne pense pas que ce soit plus qu'une gêne mineure (pour les personnes dont les IDE suggèrent l'auto-complétion dans de nouveaux espaces de noms). Cela ne vaut pas le coup que je pense que InternalsVisibleTo est.

Je ne parle pas des espaces de noms imbriqués. C'est courant. Je parle de l'espace de noms que nous avons décidé d'appeler "Interne" en pensant que les gens comprendraient qu'il était sujet à changement.

Pour être honnête, en tant que gars qui tape tout, je n'ai pas pensé aux IDE qui suggèrent d'ajouter de nouvelles instructions using. Interdisons ces IDE ! 😈

Et au cas où ce n'était pas clair, je suis d'accord avec vous. 😁

Haha, mais pas là-dessus ! J'arrêterais d'utiliser un IDE si je ne pouvais pas Ctrl + . pour compléter automatiquement ce que je pense être des tâches répétitives insensées 😁 😈

Voici un exemple de changement cassant non lié à l'API : https://github.com/nunit/nunit/issues/652#issuecomment -331041477

Considérons-nous/devrions-nous considérer qu'il s'agit d'un changement radical ? Sommes-nous contraints de le considérer comme un changement radical simplement parce que c'est ainsi qu'il sera inévitablement perçu lorsque les tests existants des gens commenceront à échouer ?

Quelle que soit la réponse, j'aimerais qu'il y ait un moyen de définir autant que possible les attentes des utilisateurs à l'avance.

Je pense que nous devrions, oui. En fait, ce type de changement cassant est moins préférable qu'un changement cassant du compilateur, car il faudrait plus de diagnostic.

J'avais l'espoir que ce fil conduirait à une définition du "changement de rupture" que nous pourrions tous adopter.

Nous avons passé du temps à définir "l'API publique" - bien que ce ne soit pas encore tout à fait défini - mais nous utilisons toujours le terme "breaking change" sans aucune signification claire de ce que c'est, du moins c'est comme ça que ça me semble.

La raison pour laquelle il me semble important de définir cela est que __chaque correction de bogue__ peut être considérée comme un changement de rupture puisqu'elle modifie le comportement. En particulier, si un utilisateur en est venu à s'appuyer sur le comportement présenté par ce bogue, cela le cassera. Ce n'est pas tiré par les cheveux, puisque nous avons vu des utilisateurs se plaindre de certaines corrections de bugs.

Une bonne définition du changement de rupture serait celle qui nous guiderait pour reconnaître où un changement particulier se situe sur le continuum d'une correction de bogue directe que personne ne peut reprocher à un changement révolutionnaire dans une API publiquement prise en charge et documentée. Quels sont les facteurs qui nous permettent d'inscrire un changement le long de ce continuum ?

Le changement particulier dont nous discutons, soit dit en passant, me semble être une correction de bogue. Le changement d'ordre d'exécution a été annoncé en 3.0. Soit nous ne l'avons pas fait, soit nous avons régressé - je ne sais pas lequel. Au moment de la version 3.0, c'était (aurait été) un changement radical, mais c'était OK parce que nous faisions une mise à jour majeure. À ce stade, lorsque nous avons modifié le comportement pour qu'il fonctionne comme prévu (et je suis à peu près sûr que cela a été documenté), j'appellerais cela une correction de bogue directe. Je suis sûr qu'il y a aussi des arguments qui vont dans l'autre sens. Je pense simplement que nous devons exposer ces arguments plutôt que de simplement crier __Breaking Change!__ sans réfléchir.

Il semble que ce sur quoi nous sommes tous d'accord, c'est que le correctif de bogue de la 3.7 qui a cassé les tests des gens aurait dû être annoncé comme tel : une chose qui a une chance de casser vos tests si vous êtes dans un scénario précis.

Je suis d'accord que "breaking changes" est un terme surchargé. Pour être très clair et littéral : le code des gens s'est brisé. La rupture de code signifie qu'il commence à échouer à la tâche qu'il avait l'habitude d'effectuer. Si le code se brise lorsque le seul changement est qu'une version plus récente du framework est utilisée, peu importe ce que nous appelons le phénomène pour nous-mêmes, mais nous devons leur dire quels scénarios sont affectés et les options pour y faire face.

Les utilisateurs ont besoin de voir ces informations _avant_ d'installer la nouvelle version : dans les notes de version de NuGet dans l'IDE, sur la page des versions de GitHub à partir de laquelle ils téléchargent et, ce qui est le moins important en termes de visibilité, les notes de version contenues dans notre référentiel. (Peu importe les numéros de version qui ont changé depuis la dernière version.)

Chaque correctif a une chance de casser le code de quelqu'un. Et si nous les triions grossièrement par impact et jouions la sécurité en les mettant tous dans une section Modifications ?

Mon rêve du point de vue de l'utilisateur serait quelque chose comme :


NUnit 3.9.0

Cette version [déclaration de motivation].
Consultez la section Modifications pour obtenir une liste des scénarios susceptibles de ne pas fonctionner et nécessitant votre attention.

Nouvelles fonctionnalités

  • Foo [plus d'infos]
  • Bar [plus d'infos]
  • Brillant [plus d'infos]

Changements

  • SomeObsoleteApi a été supprimé. Utilisez X ou la bibliothèque tierce Y à la place. [Plus d'informations]
  • [BeforeTest] s'exécute maintenant avant [SetUp] plutôt qu'après. Cela nécessitera une refactorisation en faisant XYZ. [Plus d'informations]
  • [Apartment(ApartmentState.STA)] fonctionne maintenant correctement. Dans les versions 3.7.0 à 3.8.(?), il n'avait aucun effet lorsqu'il était appliqué aux appareils dont les cas de test sont parallélisés. La solution de contournement consistant à appliquer l'attribut à chaque méthode peut être supprimée. [Plus d'informations]

[plus d'informations] les liens de problème peuvent être négligés pour le texte brut comme l'intégration NuGet VS (si cette chose est toujours en texte brut).

Je serais prêt à aider à passer du temps à faire cela pour chaque fusion dans le maître afin qu'il soit prêt à être publié.
Peut-être que nous pouvons coopter Chris :D parce qu'il a repéré des choses qui nous manquent à tous. 😎

Hé! Nous pourrions même jouer avec l'idée d'apporter des modifications au journal des modifications dans chaque PR affectant le comportement (à l'exception des corrections de fautes de frappe, des corrections de test CI, etc.).

@jnm2 Je suis surpris de votre commentaire "nous sommes tous d'accord", qui suit immédiatement le mien, alors que je n'étais évidemment pas du tout d'accord avec vous. Ce n'est pas une façon juste de plaider votre cause.

Voici un scénario imaginaire... Disons que nous avons créé une assertion Assert.IsPrime et l'avons implémentée (incorrectement) afin qu'elle réussisse pour tous les nombres impairs. Dans la prochaine version, nous voyons notre erreur et la corrigeons. MAIS entre-temps, un utilisateur a détecté qu'il s'agit en fait d'un test pour les nombres impairs et l'a utilisé de cette façon. Selon cet utilisateur, nous avons fait un "changement de rupture" et en fait nous avons - du moins si "changement de rupture" est défini comme signifiant un changement qui casse les tests de n'importe quel utilisateur comme vous le souhaitez. De plus, puisque nous avons promis de ne pas faire de "modifications avec rupture", nous devrions laisser cette affirmation fonctionner comme un test pour les nombres impairs et simplement la documenter.

Bien sûr, l'exemple est absurde. Je l'entends comme une _reductio ad absurdum_ de l'argument selon lequel tout changement qui casse les tests utilisateurs est un changement avec rupture. Bien sûr que oui - au sens littéral des mots - mais ce n'est pas vraiment une définition très utile pour nous. Ce dont nous avons besoin, c'est d'une définition des (types de) changements de rupture que nous promettons de ne pas apporter. Les documenter une fois que tout est terminé, c'est fermer la porte de la grange après le départ du cheval.

Imo, tout changement qui empêcherait la compilation du code des consommateurs est un changement avec rupture.

Deuxièmement, tout changement entraînant l'arrêt des tests.

Troisièmement (et de manière controversée), tout changement qui pourrait amener le test d'un utilisateur à donner un résultat différent.

Cependant, je dirais que les situations où un "correctif" est apporté en raison d'une implémentation incorrecte doivent être corrigées, mais clairement signalées dans le journal des modifications.
J'espère que cela n'arrivera pas souvent, car il semble que les changements soient bien examinés ici

@CharliePoole

alors que je n'étais manifestement pas du tout d'accord avec toi

Wow, j'ai mal lu votre commentaire et je m'en excuse. Mea culpa.

Alors sur quoi sommes - nous d'accord ? 😜

Dans l'esprit du principe de robustesse , pouvons-nous pécher par excès de sécurité en avertissant les gens de l'endroit où leur code pourrait cesser de fonctionner en raison d'une mise à niveau ?

Qu'y a-t-il à gagner en s'assurant que certains changements ne sont pas annoncés comme potentiellement cassants ?

@ jnm2 Vous avez commencé ce fil comme une discussion sur la gestion des versions - de semver en particulier. En semver, le terme breaking change a une signification particulière. Si chaque changement devient "rupture" dans le contexte de semver, alors nous commencerons à faire des versions majeures constantes.

La raison pour laquelle nous ne sommes pas à quelque chose comme NUnit 57.0 est que nous utilisons une définition implicite de la rupture qui est beaucoup moins rigide que celle que vous proposez.

Bien sûr, nous devons avertir les utilisateurs de l'effet des modifications que nous apportons. Je pense que c'est généralement ce que nous faisons. Mais vous n'avez pas présenté votre proposition comme un moyen d'améliorer la documentation, mais comme concernant notre politique de gestion des versions. C'est ce dont je parle encore au cas où vous seriez maintenant sur un nouveau sujet.

Certes, il s'agit de versioning. Je ne demande pas semver, mais je demande que nous nous retrouvions sur la même page.

C'est vraiment toute la décision de Rob - pas de pression, Rob 😁 - et très honnêtement, la stratégie de version n'a pas autant d'importance pour moi que l'utilisation des mots "potentiellement cassant" près du sommet pour attirer l'attention des gens et une liste claire de scénarios que vous pouvez regarder pour voir si vous êtes concerné, visible par vous avant la mise à niveau, où que vous obteniez NUnit.

Aux fins de la gestion des versions, et uniquement dans ce but, je suis heureux de ne pas considérer les corrections de bogues comme des modifications avec rupture.

Je pense que nous sommes tous d'accord sur le fait qu'il existe une chose telle qu'un changement de rupture et qu'au minimum, cela inclut l'arrêt de la compilation du code des gens s'ils s'appuient sur une API non obsolète sans Internal dans le nom.
Plus sur cela plus tard.
Forts de cet accord, nous pouvons nous pencher sur cette question. Voulons-nous :

  1. Enregistrez toutes les modifications majeures jusqu'à ce que nous soyons prêts à effectuer une mise à jour majeure de la version
  2. Utilisez la version majeure pour l'image de marque et ne nécessitez qu'une mise à jour de version mineure lors de l'introduction de modifications avec rupture

Étant donné que nos journaux de modifications seront si impeccables du point de vue de tout consommateur 😀, je ne suis pas vraiment sûr d'avoir une opinion à ce sujet.

Point de départ pour définir le changement de rupture uniquement dans le contexte de la décision de la version et du choix de la version dans laquelle fusionner les changements :

  1. Toute modification source (ou binaire ?) de l'API, à l'exception de :

    • API nommées Internal

    • API marquées [Obsolete] (?)

    • circonstances spécifiques répertoriées comme non valides par la documentation _préexistante_

  2. Tout changement de comportement susceptible d'entraîner une modification des résultats de test d'une personne, à l'exception de :

    • corrections de bogues où le comportement précédent _directement_ est en désaccord avec la documentation préexistante



      • cette exception ne s'étend pas aux effets secondaires d'un correctif de bogue sur d'autres comportements connexes, à moins que ces comportements connexes ne soient également directement en désaccord avec la documentation préexistante



    • circonstances spécifiques qui sont répertoriées comme non valides par la documentation préexistante

Que modifieriez-vous dans le brouillon ci-dessus ?

À un moment donné, j'ai commencé un projet de document sur la gestion des versions sous gouvernance. Quand il est devenu clair que le versioning était un projet plutôt qu'un problème d'organisation, j'ai arrêté de travailler dessus. Maintenant je me demande. Peut-être que cela aiderait si nous avions un document de travail qui décrivait comment nous avons fait la gestion des versions dans le passé. Je ne sais pas si nous devrions le publier, cependant, car cela pourrait dérouter les utilisateurs. Des suggestions sur comment / où faire cela? Serait-ce utile ?

@ jnm2 Je suis un peu maniaque pour m'en tenir au problème dans les problèmes de github. Ce n'est pas seulement une bizarrerie de personnalité de ma part, c'est un vrai problème. Au moins IMO, chaque problème devrait traiter d'une chose afin que nous sachions comment dire quand c'est fait. D'après votre dernier message, je comprends que vous souhaitez nous ramener à la gestion des versions dans ce numéro. Je suis en faveur de cela. Si vous souhaitez démarrer une nouvelle discussion quelque part sur la façon dont nous documentons les modifications dans les notes de version ou le fichier de modifications, voyons où le faire.

C'est une bonne question, je pense...

Où avons-nous des "méta-discussions" sur ce projet ? (Je suppose que je commence maintenant une méta-méta-discussion.)

On pourrait le faire sous gouvernance si on créait des labels par projet, mais je pense que la gouvernance est mieux adaptée aux choses qui doivent être formalisées. Idéalement, un problème sur la façon dont ProjectX est versionné devrait faire partie de ProjectX, je pense. Peut-être que nous avons juste besoin de l'équivalent de [meta] comme étiquette pour le projet. Je pouvais voir quelque chose comme is:process ou is:team fonctionner.

Plus important encore, je suppose, nous devons décider comment documenter les décisions que nous prenons sur des choses comme la gestion des versions. Devrait-il y avoir un fichier versioning.md ? Doit-il aller dans le wiki? Je n'ai pas de réponse préférée moi-même, mais il devrait y avoir un moyen de sauvegarder les résultats de ce qui promet d'être une discussion longue et complexe afin que nous n'ayons pas à le refaire dans six mois.

Fini le méta-méta !

Réflexions sur notre pratique actuelle de gestion des versions...

  1. Nous suivons principalement le __format__ de semver. Cela ne veut pas tout dire mais c'est important. Spécifiquement...

    • Nous utilisons trois composants.
    • Nous n'en incrémentons qu'un seul.
    • Nous mettons les composants inférieurs à zéro lorsque nous incrémentons le premier ou le second.
    • Nous utilisons un suffixe de pré-version après la version précédé d'un trait d'union mais voir le point 2.
  2. Nous n'utilisons __pas__ le format semver pour un suffixe de préversion car NuGet ne prend pas en charge les points dans le suffixe. Au lieu de cela, nous utilisons des traits d'union ou une concaténation et nous remplissons tous les nombres avec des zéros.

  3. Nous ne suivons actuellement __pas__ semver en termes d'identification des changements de rupture. Au lieu de cela, nous autorisons les modifications avec rupture "mineures" dans une version mineure. Nous utilisons notre jugement pour cela et parfois nous avons fait des erreurs. (Nous devrions essayer de ne pas faire d'erreurs !)

  4. Outre la nécessité d'un jugement sur la question des ruptures mineures, nous avons toujours eu notre propre vision de ce qui est réellement couvert par l'API. Dans NUnit V2, il n'y avait pas d'API officielle et nous avons eu des problèmes parce que les gens pensaient qu'ils pouvaient utiliser n'importe quel type ou méthode public et s'attendaient à ce que nous maintenions ces choses inchangées. Nous avons essayé de résoudre ce problème en définissant des API dans NUnit 3. Cela n'a manifestement pas fonctionné. L'idée que chaque méthode publique est un engagement envers les utilisateurs semble avoir gagné encore plus d'adeptes qu'auparavant. Je pense que nous devons faire quelque chose à ce sujet en indiquant clairement ce que nous promettons de ne pas briser.

Cela a été un retour en arrière. Je commenterai un peu la proposition de @jnm2 .

@ jnm2 Votre plan ressemble à une définition du changement de rupture sans dire comment nous l'utiliserions pour la gestion des versions. Je veux dire par là, c'est quelque chose dont nous avons besoin, mais nous avons aussi besoin de plus.

Commentaires sur vos points...

  1. Vous ne dites pas ce que signifie API. Je suppose que cela peut sembler être une argutie, mais je ne trouve pas cela évident, d'autant plus que nous utilisons le mot dans nos documents d'une manière très spécifique. Je pense qu'il est acceptable de l'utiliser de différentes manières à condition que nous sachions clairement comment nous l'utilisons dans chaque cas.

Dans nos docs, le "Framework API" signifie les classes et les méthodes utilisées par le moteur ou un autre exécuteur pour exécuter des tests. Ici, je crois que vous parlez des méthodes utilisées par les personnes qui écrivent des tests. C'est aussi une API, nous ne l'avons tout simplement pas appelée ainsi auparavant.

Vous pourriez être tenté de définir des règles pour toutes les API à la fois, mais je ne pense pas que ce soit une bonne idée. Différentes personnes les utilisent pour différentes raisons, elles peuvent donc avoir besoin de règles différentes. Par exemple, ce que nous avons appelé jusqu'à présent l'API Framework ne peut tout simplement pas être modifié, sauf pour corriger des bogues ou ajouter de nouvelles fonctionnalités sans modifier celles qui existent déjà. Aucun jugement n'est autorisé, car tout autre changement casserait notre propre moteur et tous les coureurs tiers. L'"API" d'écriture de test peut avoir des règles un peu plus lâches si nous le voulons.

  1. Sur Internal, je dirais quelque chose comme ceci : "Les méthodes non publiques et les méthodes publiques dans l'espace de noms interne ne font pas partie de l'API NUnit". IMO, c'est à la fois clair et ce que nous avons toujours voulu. Mon erreur a été de penser que c'est évident et de ne pas le dire. 😞

  2. Si une méthode fait partie de notre API et que nous la rendons obsolète en utilisant Error = true, c'est un changement avec rupture. Avec false, ce n'est pas le cas. Lorsque nous le supprimons finalement, c'est aussi un changement radical, bien sûr. La question pour la gestion des versions est "Quand est-il acceptable de faire ces choses ?" IMO, il s'agit d'une mise à niveau majeure ou mineure et vous ne pouvez pas le dire à moins d'évaluer la chose spécifique qui est obsolète ou supprimée.

  3. J'aurais tendance à être généreux en termes de "documentation préexistante" puisque nos documents ne sont pas si bons. Par exemple, je considérerais les commentaires de code et le nom d'une méthode comme étant de la documentation. Si une méthode ne faisait pas ce que son nom indique qu'elle devrait faire, je la corrigerais sans hésitation. Nous avons une révision du code pour nous assurer que je le regarde de la même manière que les autres développeurs.

  4. Je pense que "faire changer les résultats des tests de quelqu'un" est un critère trop large s'il est pris à la lettre. Si les résultats des tests précédents étaient incorrects, l'objectif est de les modifier. Je suppose que ce serait une exception.

Cependant, comme indiqué au début de ce commentaire, il est vraiment difficile d'évaluer la définition sans savoir comment elle sera utilisée. Si nous allons suivre une règle qui dit que "toutes les modifications avec rupture nécessitent une mise à jour majeure de la version", alors je voudrais une définition différente de la modification avec rupture que celle qui dit "toutes les modifications avec rupture nécessitent une mise à jour majeure ou mineure". Dans ce dernier cas, je voudrais une définition pour me dire comment décider entre majeur et mineur. Je pense donc que nous devons faire cela de haut en bas en décidant d'abord de notre politique de version.

FWIW Je ne suis pas non plus en faveur d'un SemVer strict.

SemVer sonne bien. Beaucoup de gens l'ont utilisé, bien sûr, tandis que beaucoup d'autres ont rencontré des problèmes. Jusqu'à présent, nous sommes dans ce dernier groupe, ayant pris la décision provisoire d'utiliser SemVer et réalisant ensuite que nous ne pouvions pas le faire à moins de commencer à publier une nouvelle version majeure tous les six mois environ.

Voici les problèmes que je connais qui se sont dressés (et se dressent probablement encore) sur notre chemin :

  1. Nous n'avons pas de définition convenue de la ou des API que nous ne romprons pas. Historiquement, l'équipe NUnit (y compris la V2) s'est développée dans un style où tout était public pour pouvoir être testé. (C'était avant que "InternalsVisibleTo" n'existe) L'équipe ne considérait essentiellement que les attributs et les assertions comme "vraiment" publics (ou publiés si vous le souhaitez) et n'avait aucun scrupule à changer d'autres choses. Nous savions ce qui était destiné uniquement à notre propre usage, bien que cela n'ait pas toujours été clair pour les autres.

J'ai continué à travailler de la même manière sur NUnit 3 en 2007, simplement parce que c'était comme ça que les choses avaient toujours été faites et aussi parce que je supportais des plates-formes plus anciennes. Après 2010 environ, d'autres personnes ont rejoint l'équipe et certaines d'entre elles avaient des idées différentes sur ce que "casser" signifiait. À l'extrême, cela pourrait signifier "toute classe ou méthode qu'un utilisateur __pourrait__ avoir utilisée, que nous voulions qu'elle soit utilisée ou non". En guise de compromis, nous avons proposé l'espace de noms interne, qui visait à dire aux gens qu'ils utilisaient ces méthodes à leurs risques et périls. Je dois dire que j'ai été surpris de voir combien de programmeurs, même ceux qui utilisent de manière assez sophistiquée les méthodes "internes", semblent ne pas avoir remarqué qu'elles étaient considérées comme "internes" et donc sujettes à changement.

Dans ce contexte, ma propre conclusion est que nous devrions __uniquement__ rendre visibles à l'avenir les méthodes que nous avons l'intention de prendre en charge. Cependant, ce n'est pas la fin de l'histoire.

  1. NUnit prend en charge plusieurs API destinées à plusieurs publics. (Notez que je parle ici du framework. Il y a encore plus d'API si vous tenez compte du moteur, de la console et de l'adaptateur.) Chacune de ces API souffre du même problème de définition que celui indiqué ci-dessus. Chacun d'eux __pourrait__ avoir un niveau différent de déclenchement de ce que nous considérons comme un changement "rupteur". Les principales API auxquelles je pense sont :

    • L'API d'écriture de test - assertions, attributs, TestContext et quelques autres choses.

    • L'API d'extensibilité, principalement des interfaces à utiliser pour créer des attributs personnalisés.

    • L'API d'exécution, une API de réflexion uniquement exposée par le framework pour permettre l'écriture des runners.

    • L'API de ligne de commande NUnitLite, qui est fournie avec le framework.

Une interruption de __n'importe laquelle__ de ces API devrait-elle déclencher une version majeure ? Je ne sais pas, mais j'en doute. Pour être certain, je pense qu'il faudrait définir plus clairement ce qui est inclus dans chacun d'eux. Si on veut que le déclenchement soit différent pour chacun, il faut le dire clairement car la visibilité, en soi, ne suffit plus car chacun d'entre eux doit être visible d'une certaine manière.

  1. Qu'en est-il des bogues ? Nous avons plusieurs fois corrigé (ce que nous considérons être) des bogues, puis nous avons constaté que quelqu'un en dépendait. Je sais que presque tous les bogues de longue durée peuvent finir par être considérés comme une fonctionnalité, mais je pense que nous devons résister à cette tendance dans la mesure du possible.

Ce ne sont que mes pensées. J'essaie personnellement de mieux définir ce qui est public et ce qui ne l'est pas dans mon travail sur TestCentric mais NUnit a une histoire et il est difficile de s'en éloigner !

Cette page vous a été utile?
0 / 5 - 0 notes