Nom de test lisible dans JS et le modèle comportemental

Lors du prochain examen d'une requête Pull épaisse, je suis tombé sur des tests unitaires avec un nom incorrect des cas de test. La discussion du libellé dans les cas de test s'est avérée similaire à la conversation entre Yanychar et Legkostupov dans le film "72 mètres" ("si c'était si intelligible à l'école ..."). La conversation a sonné l'idée que dans les ressources en langue russe, il est difficile de trouver un guide explicatif précisément par des libellés textuels. J'ai décidé de me chercher en russe (en général, j'utilise uniquement des sources anglophones). On a trouvé plusieurs manuels sur les tests unitaires, mais tous contournent les détails des formulations dans les cas de test. Sous la coupe, ma tentative de combler cette lacune.


Disclamer


Il y a une chance que je regarde mal / je lis trop en diagonale. Voici un exemple de la façon dont le sujet de cet article est couvert dans ces articles qui ont attiré mon attention.



TDD pour débutants


À la demande de collègues qui ne sont pas à l'aise avec la lecture de manuels en anglais, j'ai décidé de traduire et de compiler des manuels en anglais.


Du traducteur


J'ai pris ces deux matériaux comme base de l'article:



Je dois également noter que dans certains exemples de test, j'ai dû faire une traduction partielle en russe. Le libellé dans les blocs «décrivent» reste intentionnellement en anglais, avec une forte probabilité, ils contiendront le nom des fonctions, des modules JS ou d'autres entités dans le code, mais dans les blocs «it», le texte est déjà traduit pour plus de lisibilité.


Ma préférence personnelle est que tout dans le code soit en anglais.


Tests de dénomination


Le nom du test doit décrire son objectif aussi brièvement et explicitement que possible. Le nom et la description du test est la première chose qui devrait indiquer la cause du dysfonctionnement. Le résultat du test dans la console doit être lu correctement en termes de grammaire. Les développeurs tiers ne devraient pas résoudre d'énigmes dans leur tête, essayant de deviner à quoi pensait l'auteur du test. Les tests font partie de la documentation du programme et doivent également être correctement écrits.


Exemple MAUVAIS :


describe('discoveryService => initDiscoveries', () => { it(' discoveries ( ,    ..)', () => { // ... }); }); describe('MyGallery', () => { it('init      ( , - )', () => { }); // ... }); 

À partir des exemples ci-dessus, il est difficile de comprendre à quelle (s) action (s) spécifique (s) est-elle exécutée et à quel résultat concret l'action doit conduire.


BON exemple:


 describe('discoveryService => initDiscoveries', () => { it('   discoveries', () => { // ... }); it('     discoveries', () => { // ... }); }); describe(' Gallery', () => { it('       ', () => { }); it('       ', () => { }); // ... }); 

Remarque perev. # 1: notez que le bloc de texte qu'il contient commence par une majuscule, comme est une continuation de la phrase qui a commencé en descibe .


Remarque perev. # 2: dans les exemples ci-dessus, "discoveryService => initDiscoveries" est néanmoins plus correctement divisé en deux blocs de description (l'un est imbriqué dans l'autre).


Remarque perev. # 3: notez que dans les exemples de découverte ci-dessus, il n'y a pas de deuxième partie de la description du cas de test; elle implique un texte de la forme «lors de son invocation», ce qui n'est pas très bon du point de vue de la manifestation; dans les cas simples, le copier-coller "quand on l'appelle" n'est pas particulièrement rentable, à mon humble avis.


Dans le bloc de description, une description du travail élémentaire (Unité de travail, UoW) est généralement placée. Le libellé du bloc informatique doit continuer avec le modèle " unité d'oeuvre - scénario / contexte - comportement attendu " qui a commencé dans la description:
[ ] [ / ] ( | ) [ ]


ou comme code:


 describe('[unit of work]', () => { it(' [ ] / [/]', () => { }); }); 

Si plusieurs groupes de tests suivent le même script ou s'inscrivent dans le même contexte, vous pouvez utiliser les blocs décrits imbriqués.


 describe('[unit of work]', () => { describe('// [scenario/context]', () => { it('/ [expected behaviour]', () => { }); }); }); describe(' Gallery', () => { describe(' ', () => { it('    ', () => { }); it('    ', () => { }); }); // ... }); 

UN TEST - UN PROBLÈME


Chaque test doit se concentrer sur un scénario spécifique dans l'application. Le test, responsable d'un aspect spécifique, est capable d'identifier la cause spécifique du dysfonctionnement. Plus le test est spécifique, moins il est probable qu'il puisse y avoir plusieurs raisons à un comportement incorrect. Essayez de placer un seul bloc expect dans un bloc it .


Exemple MAUVAIS :


 describe('isUndefined function', ()=> { it('  true or false    undefined', () => { expect(isUndefined(undefined)).toEqual(true); expect(isUndefined(true)).toEqual(false); }); }); 

Le bloc it contient deux blocs expect . Cela signifie que le développeur, voyant un résultat négatif de ce test, ne pourra pas déterminer exactement ce qui est incorrect dans son code et comment y remédier.


BON exemple:


 describe('isUndefined function', ()=> { it('  true,    undefined', () => { expect(isUndefined(undefined)).toEqual(true); }); it('  false      ', () => { expect(isUndefined(true)).toEqual(false); }); }); 

Chaque test de l'exemple ci-dessus évalue un problème spécifique. De plus, la description du test décrit clairement dans quel cas elle sera réussie. Dans les deux cas, dans la console, le développeur lira une liste des résultats attendus dans quelles actions / conditions de la fonctionnalité utilisateur testée.


Tester le comportement


Regardez l'image, ne regardez pas les traits. Testez le script / comportement personnalisé, pas les détails d'implémentation. La modification des détails d'implémentation n'affectera pas les résultats du test. Un résultat de test négatif doit indiquer si le programme se comporte correctement du point de vue de l'utilisateur. Le test ne doit pas contrôler / limiter les détails d'implémentation.


Exemple MAUVAIS :


 it('   discovery  ', () => { discoveriesCache.addDiscovery('57463', 'John'); expect(discoveriesCache._discoveries[0].id).toBe('57463'); expect(discoveriesCache._discoveries[0].name).toBe('John'); }); 

Qu'est-ce qui est mauvais ici? Tout d'abord, deux attendent des blocs, mais ce n'est pas l'essentiel. Deuxièmement, ce n'est pas le comportement qui est testé, mais les détails de l'implémentation. Les détails de l'implémentation changeront (les champs privés sont renommés) - le test deviendra invalide et devra être réécrit.


BON exemple:


 it('   discovery  ', () => { discoveriesCache.addDiscovery('57463', 'John'); expect(discoveriesCache.isDiscoveryExist('57463', 'John')).toBe(true); }); 

Cet exemple teste une API publique qui doit être aussi stable que possible.


CONCLUSION D'UN TRADUCTEUR


"Onegin était un pédant ..." J'ai l'impression que la plupart des développeurs prêtent peu d'attention à la précision et à la lisibilité des noms de test. Souvent, j'observe des discussions assez longues comme «Que fait ce code» ou «Pourquoi ce code». Cela s'applique à la fois au code principal de JS (noms flous et flous des modules, services, fonctions et variables) et aux tests (cas flous, détails de l'implémentation des tests, descriptions floues). Tout cela conduit au fait que le code ne correspond pas tout à fait à ce qui est attendu.


Dans l'une de ses interviews, David Heinemeier Hansson (créateur du framework Rails) a dit quelque chose comme ceci:
"Les tests unitaires montrent seulement que votre programme fait la chose attendue%: o."


Il voulait dire que le comportement devait être testé, pas les unités de code. Et le langage textuel devrait avoir un modèle comportemental. C'est-à-dire "L'entité A doit se comporter de telle ou telle manière dans telle ou telle condition." Une chaîne de la forme décrire [- décrire ] - elle - devrait - devrait se transformer en une telle formulation pliante.


Merci de votre attention!

Source: https://habr.com/ru/post/fr454884/


All Articles