L'image globale des tests unitaires



Ce n'est pas un guide sur les caractères que vous devez entrer dans l'éditeur de code pour obtenir des tests unitaires. C'est une nourriture pour l'esprit, qui doit être consommée avant de prendre ces mesures.

Le sujet des tests unitaires n'est pas aussi simple qu'il y paraît. Beaucoup d'entre nous, les développeurs, viennent aux tests unitaires sous la pression des clients, des employés, des collègues, de leurs idoles, etc. Nous comprenons rapidement sa valeur et, après avoir terminé les préparatifs techniques, nous oublions le tableau général, si jamais nous l'avons compris. Dans cet article, je parlerai brièvement de ce que les tests unitaires sont à la fois et de ce qui ne l'est pas en général, et en PHP, et en même temps, je décrirai la place qu'occupent les tests unitaires dans l'AQ.

Qu'est-ce que le test?


Avant de plonger dans les tests unitaires, vous devez étudier la théorie du test lui-même afin de ne pas faire d'erreurs comme celles faites par les auteurs de l'un des frameworks PHP les plus populaires: ils ont montré des tests d'intégration sur leur site Web et les ont appelés tests unitaires. Non, Laravel, ce ne sont pas des tests unitaires. Bien que cela ne m'empêche pas de toujours aimer ce cadre.

Les tests de logiciels sont définis comme «une enquête menée pour fournir aux parties intéressées des informations sur la qualité des produits». Cela s'oppose au "test logiciel est un gaspillage du budget du projet par les développeurs qui ne font rien d'important et demandent ensuite plus de temps et d'argent, car" rien "peut être très cher." Rien de nouveau ici.

Voici ma brève histoire de devenir un test:

  • 1822 - Moteur de différence (Charles Babbage).
  • 1843 - Moteur analytique (Ada Lovelace).
  • 1878 - Edison introduit le terme «bug».
  • 1957 - Test et débogage de programmes (Charles Baker).
  • 1958 - La première équipe de test de logiciels (Gerald Weinberg).
  • 1968 - Crisis PO (Friedrich Bauer).
  • Années 1970 - Modèle de cascade, modèle relationnel, décomposition, analyse critique ( procédure pas à pas ), conception et inspection de code, qualité et métriques, modèles de conception.
  • Années 80 - Analyse CRUD, architecture système, autotest, modèle V, fiabilité, coût de la qualité, méthodes d'utilisation, modèles de conception OOP.
  • Années 1990 - Scrum, tests d'utilisabilité, MoSCoW, tests heuristiques, automatisation et tests de logiciels.

Si vous vous référez à une génération de milléniaux comme moi, vous pourriez être étonné que les équipes de test aient existé LONGTEMPS avant votre naissance. Arrêtez-vous un instant, inspirez, expirez, calmez-vous.
L'histoire montre comment le type de test considéré comme «suffisamment bon» pour les parties intéressées a changé au fil du temps. Phases approximatives qui ont été guidées lors des tests:

  • ... - Débogage 1956
  • 1957-1978 Démonstration
  • 1979 - 1982 destruction
  • 1983 - 1987 estimation
  • 1988 - ... prévention

Par conséquent, des tests unitaires sont nécessaires pour éviter les écarts entre le projet et la mise en œuvre.

Qu'est-ce que le test vraiment?


Il existe différentes classifications des tests de logiciels. Pour mieux comprendre la place du test unitaire, je ne citerai que les approches les plus répandues.

Les tests sont: statiques et dynamiques, «boîte» (boîte blanche, boîte noire, boîte grise), niveaux et types. Chaque approche utilise des critères de classification différents.

Tests statiques et dynamiques


Les tests statiques sont effectués sans exécution de code. Cela comprend la relecture, la vérification, la révision du code (lors de l'observation du travail d'une autre programmation / paire), l'analyse critique, les inspections, etc.

Les tests dynamiques pour obtenir les résultats corrects nécessitent l'exécution de code. Par exemple, pour les tests unitaires , l'intégration, le système, l'acceptation et d'autres tests. Autrement dit, les tests sont effectués à l'aide de données dynamiques, d'entrée et de sortie.

Approche boîte


Selon cette approche, tous les tests logiciels sont divisés en trois types de boîtes:

  • Les tests en boîte blanche vérifient les structures et modules internes, ignorent les fonctionnalités attendues pour les utilisateurs finaux. Il peut s'agir de tests API, d'injection de pannes, de tests unitaires, de tests d' intégration.
  • Les tests de la boîte noire s'intéressent davantage à ce que fait le logiciel, et non à la façon dont il le fait. Cela signifie que les testeurs ne sont pas tenus de comprendre l'objet de test ou de comprendre comment il fonctionne sous le capot. Ce type de test s'adresse aux utilisateurs finaux, leur expérience d'interaction avec une interface visible. Les boîtes noires incluent les tests basés sur des modèles, les tests d'utilisation, les tables de transition d'état, les tests de spécifications, etc.
  • Les tests de type « boîte grise » sont conçus avec une connaissance des algorithmes logiciels et des structures de données (boîte blanche), mais sont effectués au niveau de l'utilisateur (boîte noire). Cela comprend les tests de régression et les tests de modèle.

Maintenant, pour vous dérouter, je dirai que les tests unitaires peuvent également s'appliquer à la «boîte noire», car vous pouvez comprendre le module testé, mais pas l'ensemble du système. Bien que pour moi, ce soit toujours une "boîte blanche", et je vous suggère d'être d'accord avec cela.

Niveaux de test


Leur nombre varie, généralement entre 4 et 6, et ils sont tous utiles. Les noms peuvent également être différents, selon la culture adoptée par l'entreprise, vous pouvez connaître les tests «d'intégration» comme «fonctionnels», les tests «système» comme «automatisés», etc. Pour simplifier, je décrirai 5 niveaux:

  1. Tests unitaires
  2. Test d'intégration.
  3. Test des interfaces des composants.
  4. Test du système.
  5. Test d'acceptation opérationnelle.

Les tests unitaires testent la fonctionnalité d'un morceau de code particulier, généralement une fonction à la fois. Les tests d'intégration vérifient les interfaces entre les composants afin que les modules assemblés forment un système qui fonctionne comme prévu. C'est un point important, car un grand nombre de tests, appelés tests unitaires, sont en fait des tests d'intégration et les développeurs les considèrent comme des modules. Si vous avez l'intention d'utiliser plusieurs modules - cela teste l'intégration entre eux, et non les modules eux-mêmes. Le test des interfaces des composants vérifie les données transférées entre les différents modules. Par exemple, nous avons reçu des données du module 1 - vérifiées - transférées au module 2 - vérifiées. Les tests du système sont des tests de bout en bout pour vérifier la conformité à toutes les exigences. Des tests d'acceptation opérationnelle sont effectués pour vérifier l'état de préparation opérationnelle. Il n'est pas fonctionnel, seule la fonctionnalité des services est vérifiée, si des sous-systèmes endommagent l'environnement et d'autres services.

Types de tests


Chaque type de test, quel que soit son niveau, peut également être divisé en d'autres types. Il existe plus de 20 types courants. Le plus courant:

  • Test de régression .
  • Test d'acceptation.
  • Test de fumée
  • Uat
  • Essais destructifs .
  • Test de performance.
  • Test continu .
  • Tests d'utilisabilité.
  • Test de sécurité.

D'après le nom, il est clair pourquoi tel ou tel type de test est prévu. Gras sont les tests unitaires en PHP. Si vous le souhaitez, vous pouvez appliquer chacun de ces termes aux tests unitaires. Cependant, la principale variété de tests unitaires sont des tests de régression, qui vérifient si tous les modules du système sont exécutés correctement après avoir apporté des modifications au code.

Vous savez maintenant que les tests unitaires sont dynamiques, appartiennent à la classe «boîte blanche», sont effectués au niveau du module, sont des tests de régression, mais les tests unitaires peuvent être compris comme de nombreux types de tests. Alors, quels sont vraiment les tests unitaires?

Qu'est-ce qu'un test unitaire?


Un modèle en V est une représentation graphique des niveaux, types et objectifs ci-dessus dans le cycle de vie du développement logiciel.



Après avoir vérifié et approuvé les exigences détaillées du produit, lorsqu'ils ont commencé à écrire du code, les tests unitaires deviennent la première ligne de défense contre toute incohérence. Par conséquent, les entreprises qui comprennent ce qu'elles font forcent les développeurs à utiliser des tests unitaires ou même TDD, car il est beaucoup moins cher de corriger les bogues dans les phases initiales que dans les phases ultérieures.

Et c'est juste. Les tests unitaires présentent de nombreux avantages. Ce sont:

  • Isolez chaque partie du programme et vérifiez son exactitude.
  • Aidez à détecter les problèmes tôt.
  • Ils incitent les développeurs à penser en termes d'entrée, de sortie et de conditions erronées.
  • Ils donnent au code un aspect pratique pour les tests, facilitent la refactorisation future.
  • Simplifiez l'intégration des modules de travail (!).
  • Remplacez partiellement la documentation technique.
  • Obligé de séparer l'interface de l'implémentation.
  • Ils prouvent que le code du module fonctionne comme prévu (au moins mathématiquement).
  • Peut être utilisé comme suite de tests de régression de bas niveau.
  • Démontrer les progrès de l'intégration système incomplète.
  • Réduisez le coût de la correction des bogues (avec TDD - encore plus).
  • Ils vous permettent d'améliorer l'architecture de l'application en déterminant la responsabilité des modules.
  • Si vous pouvez le tester, vous pouvez le connecter à votre système.
  • Les tests unitaires sont amusants!

Cependant, il y a certaines limitations auxquelles vous avez pensé, probablement en lisant cette liste:

  • Les tests unitaires ne détectent pas les erreurs d'intégration.
  • Chaque expression booléenne nécessite au moins deux tests et le nombre augmente rapidement.
  • Les tests unitaires sont tout aussi bogués que le code qu'ils testent.
  • La liaison des tests à quelques cadres ou bibliothèques spécifiques peut limiter le flux de travail.
  • La plupart des tests sont écrits une fois le développement terminé. C'est triste. Utilisez TDD!
  • Peut-être qu'après un peu de refactoring, le système fonctionnera comme avant, mais les tests échoueront.
  • Le coût du développement augmente.
  • Erreur humaine: commenter les tests cassés.
  • Erreur humaine: ajout de solutions de contournement au code spécifiquement pour réussir les tests unitaires.

Ce dernier me tue le plus. (Presque) dans chaque projet, directement dans le code source de l'application de travail, je trouve des lignes comme «s'il s'agit d'un test unitaire, charge une base de données SQLite de substitution, sinon charge une autre base de données», ou «s'il s'agit d'un test unitaire, n'envoie pas d'e-mail, sinon envoyer ", etc. Si votre application a une mauvaise architecture, ne prétendez pas que vous pouvez réparer un logiciel moche avec un bon test, cela ne s'améliorera pas.

J'ai souvent discuté avec des collègues et des clients de ce qu'est un bon test unitaire. Il:

  • Vite.
  • Automatisé.
  • Contrôle entièrement toutes ses dépendances.
  • Fiable: il peut être lancé dans n'importe quel ordre, indépendamment des autres tests.
  • Il ne peut être exécuté qu'en mémoire (pas d'interaction avec la base de données, lecture / écriture dans le système de fichiers).
  • Renvoie toujours un seul résultat.
  • Pratique pour la lecture et l'accompagnement.
  • Ne teste pas la configuration SUT (système sous test).
  • A une TÂCHE UNIQUE clairement définie.
  • Il est bien nommé (et suffisamment compréhensible pour éviter le débogage juste pour comprendre ce qui échoue).

Pour ceux qui ont souri après avoir lu «automatisé»: je ne voulais pas intégrer PHPUnit ou JUnit dans les pipelines CI. Le fait est que si vous modifiez le code, enregistrez-le et ne savez pas si les modules réussissent leurs tests, ils ne sont pas automatisés, mais le devraient. L'option gagnante est le suivi des fichiers.

Que devrait-on soumettre aux tests unitaires?


Dans les systèmes normaux, des tests unitaires doivent être écrits pour:

  • Modules - parties isolées et indivisibles du système qui exécutent une tâche (fonction, méthode, classe).
  • Méthodes publiques.
  • Des méthodes protégées, mais seulement dans de rares cas et quand personne ne le voit.
  • Bugs et leurs correctifs.

La définition d'un test unitaire dépend du développeur qui a écrit le code. En PHP, c'est presque toujours une méthode ou une fonction de classe, car c'est un logiciel indivisible qui a du sens en soi . Plusieurs fois, j'ai vu comment les développeurs utilisaient un tableau de miniclasses à une méthode en tant que module unique. Cela est logique si une fonctionnalité minimale nécessite plusieurs objets.

Vous pouvez donc déterminer vous-même ce qu'est un module pour vous. Ou vous pouvez tester les méthodes une par une, rendant la vie plus facile à ce type qui travaillera ensuite avec le code.

Si vous n'effectuez pas de tests unitaires, je vous propose de le faire après le prochain gros bug. Vérifiez à quelle méthode il sera associé, écrivez un test qui a échoué avec les bons arguments et le bon résultat, corrigez le bogue, relancez le test unitaire. S'il est passé, vous pouvez être sûr que ce bogue a dû être corrigé pour la dernière fois (en tenant compte de vos scénarios d'entrée spécifiques).

Cette approche facilite la compréhension des tests unitaires. Analysez chaque méthode séparément. Les fournisseurs de données peuvent vous aider à déterminer les entrées et les sorties de tout scénario qui vous vient à l'esprit, donc quoi qu'il arrive, vous saurez à quoi vous attendre.

Ce qui n'a PAS besoin d'être testé


Il est un peu plus difficile de déterminer que vous n'avez pas besoin de tester. J'ai essayé de compiler une liste d'éléments qui n'ont pas besoin d' être soumis à des tests unitaires:

  • Fonctionnalité hors du champ des modules (!)
  • Intégration de modules avec d'autres modules (!)
  • Comportement non isolé (dépendances immuables, bases de données réelles, réseau)
  • Méthodes privées et sécurisées.
  • Méthodes statiques.
  • Bibliothèques externes.
  • Votre cadre.

Je suis sûr que les tests unitaires ne doivent être appliqués à aucune des solutions ci-dessus, à l'exception des méthodes statiques. J'aime faire valoir que statique, par essence, signifie procéduralité et, dans de nombreux cas, la procéduralité est globale. Si la méthode statique appelle une autre méthode statique, cette dépendance ne peut pas être remplacée. Cela signifie que vous testez maintenant de manière isolée. Et puis ce n'est plus un test unitaire. D'un autre côté, c'est la partie du code qui peut vivre seule, elle a un but, et elle doit être testée pour s'assurer que, quelle que soit la partie de ce système stupide que la partie testée du code appelle, elle ne cassera pas. Par conséquent, je pense que vous pouvez tester des méthodes statiques si vous êtes sûr que la sortie de votre test ne peut pas être modifiée par un autre test et que le langage ou le framework vous permet de tester nativement.

Comment écrire des tests unitaires?


  • Écrivez un code adapté aux tests unitaires, puis testez-le.
  • Écrivez un code adapté aux tests unitaires, puis testez-le.
  • Écrivez un code adapté aux tests unitaires, puis testez-le.

Si «alors testez-le» ne suffit pas, alors laracasts.com a de très bonnes vidéos sur les tests unitaires PHP. Il existe de nombreux sites dédiés à la même tâche dans d'autres langues. Je ne vois aucune raison d'expliquer comment je fais des tests unitaires, car les outils changent assez rapidement, et lorsque vous lisez ce texte, je peux passer de PHPUnit à Kahlan. Ou pas. Qui sait.

Mais répondre à la première question (comment écrire du code adapté aux tests unitaires) est beaucoup plus facile et il est peu probable que la situation change beaucoup au fil du temps:

  • SOLIDE
  • SEC
  • L'absence de nouveaux mots clés dans le constructeur.
  • L'absence de boucles dans le constructeur (et de transitions, si spécifié).
  • Manque de méthodes, paramètres et classes statiques.
  • Manque de méthodes setup (): les objets doivent être complètement initialisés après la construction.
  • L'absence de singleton (statut mondial) et d'autres antipatternes non testables.
  • Le manque d'objets omnipotents (objets de Dieu).
  • Manque de classes à fonctionnalités mixtes (classes à préoccupations mixtes).
  • Pas de dépendances cachées.

Maintenant, sachant ce que sont les tests unitaires et ce qui ne sont pas, ce dont vous avez besoin et ce que vous n'avez pas besoin de tester, quelle place prennent les tests unitaires dans le cycle de vie du développement logiciel, il vous sera plus facile de les implémenter. Reste à trouver un framework ou une bibliothèque à votre goût. En cas de doute, prenez le cadre / langage standard de facto.

En conclusion: les tests unitaires sont très importants pour les développeurs et les entreprises. Ils doivent être écrits, il existe des méthodes éprouvées qui vous aideront à couvrir facilement les modules avec des tests, principalement en préparant les modules eux-mêmes. Mais toutes ces techniques n'ont pas de sens sans la connaissance de la théorie des tests décrite dans cet article. Vous devez être capable de distinguer les tests unitaires des tests d'autres types. Et lorsque vous avez une compréhension claire dans votre tête, il vous sera alors beaucoup plus facile d'écrire des tests.

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


All Articles