Maison  >  Article  >  développement back-end  >  Pourquoi les tests unitaires ? Comment réaliser le test ?

Pourquoi les tests unitaires ? Comment réaliser le test ?

青灯夜游
青灯夜游avant
2022-10-10 19:19:392247parcourir

Pourquoi les tests unitaires ? Comment réaliser le test ?

Voici un lien vers une vidéo de moi parlant de ce sujet

Si vous n'aimez pas les vidéos, voici une version longue.

Logiciel

Le logiciel peut être modifié. C'est pourquoi on l'appelle « logiciel ». Sa plasticité est plus forte que le matériel. Une grande équipe d’ingénieurs devrait être un atout formidable pour une entreprise, écrivant des systèmes qui continuent d’ajouter de la valeur à mesure que l’entreprise se développe.

Alors pourquoi sommes-nous si mauvais dans ce domaine ? De combien de projets avez-vous entendu parler et qui ont complètement échoué ? Ou bien il devient « hérité » et doit être complètement réécrit (les réécritures échouent généralement aussi !)

Comment les systèmes logiciels « échouent » ? Ne peut-il pas être modifié avant d'être correct ? C'est notre promesse !

Beaucoup de gens choisissent de créer des systèmes dans Go parce qu'il a fait de nombreux choix qui, espèrent-ils, le rendront plus lisible. [Recommandations associées : Tutoriel vidéo Go]

  • Par rapport à ma précédente carrière ScalaJe le décris comme vous donnant envie de vous pendre, Go n'a que 25 mots-clés et beaucoup peuvent être téléchargés à partir du standard bibliothèque et un système intégré à d'autres petites bibliothèques. La vision est qu'avec Go, vous pouvez écrire du code et y revenir en 6 mois et cela aura toujours du sens.

  • Les outils de test, d'analyse comparative, d'analyse sémantique et de chargement sont de premier ordre par rapport à la plupart des alternatives.

  • Grande bibliothèque standard.

  • Une boucle de rétroaction stricte rend la compilation très rapide

  • Go a un engagement de compatibilité ascendante. Il semble que Go obtiendra des génériques et d'autres fonctionnalités à l'avenir, mais les concepteurs ont promis que même le code Go que vous avez écrit il y a 5 ans sera toujours construit. J'ai passé quelques semaines à mettre à niveau mon projet de Scala 2.8 vers 2.10.

Même avec toutes ces excellentes propriétés, nous pouvons toujours créer de mauvais systèmes, nous devrions donc le faire. Que le langage que vous utilisez soit excellent ou non, vous devriez revenir sur l'ingénierie logicielle passée et comprendre les leçons apprises.

En 1974, un ingénieur logiciel intelligent nommé [Manny Lehman](https://en.wikipedia.org/wiki/Manny_Lehman...) a écrit La loi de Lehman sur l'évolution logicielle .

Ces lois décrivent l'équilibre entre les forces qui conduisent aux nouveaux développements et les forces qui entravent le progrès.

Si nous ne voulons pas que les systèmes que nous développons deviennent des héritages et soient réécrits encore et encore, alors ces forces sont sur lesquelles nous devons nous concentrer sur la compréhension.

La loi du changement continu

Les systèmes logiciels utilisés dans la vie réelle doivent constamment changer, sinon ils seront éliminés par l'environnement général

Évidemment, un systèmedoitchanger continuellement, sinon il le fera. deviennent de plus en plus inutiles, mais pourquoi cette situation est-elle souvent ignorée ?

Parce que de nombreuses équipes de développement recevront des bonus pour avoir livré un projet à une date spécifiée, puis passeront au projet suivant. Si le logiciel a de la « chance », il sera au moins confié sous une forme ou une autre à un autre groupe de personnes pour le maintenir, mais ils ne continueront certainement pas à l'itérer.

Les gens se soucient généralement de choisir un framework pour les aider à « livrer rapidement » plutôt que de se concentrer sur le développement de la persistance du système.

Même si vous êtes un excellent ingénieur logiciel, vous pouvez toujours être victime de ne pas comprendre les besoins futurs de votre système. À mesure que votre entreprise évolue, l'excellent code que vous avez écrit peut ne plus être pertinent.

Lehman a connu beaucoup de succès dans les années 1970 car il nous a donné une autre règle qui mérite réflexion.

La loi de la complexité croissante

À mesure que le système se développe, à moins que des mesures ne soient prises pour réduire l'augmentation de la complexité du système, la complexité du système continuera d'augmenter

Ce qu'il va dire maintenant, c'est : Nous ne pouvons pas laisser l'équipe logicielle devenir une pure usine de fonctions, concentrant simplement de plus en plus de fonctions dans le logiciel pour permettre au système de continuer à fonctionner sur le long terme.

À mesure que notre paysage des connaissances évolue, nous devrons continuer à gérer la complexité de nos systèmes.

Refactoring

le développement logiciel peut maintenir un logiciel malléable de de nombreuses façons, telles que :

  • Autonomisation des développeurs

  • généralement du « bon » code. Faites attention à la séparation raisonnable du code, etc.

  • Compétences en communication

  • Architecture

  • Observabilité

  • Déployabilité

  • Tests automatisés

  • Boucle fermée

Je le ferai L'accent est mis sur le refactoring. Un refrain courant entendu par les développeurs lors de leur premier jour de programmation est "nous devons refactoriser cela".

Cette phrase vient de et ? En quoi le refactoring est-il différent de l’écriture de code ?

Je sais que moi et beaucoup d'autres pensions que nous étions en train de refactoriser, mais nous avions tort.

Martin Fowler décrit comment les gens font des erreurs

Cependant, le « refactoring » est souvent utilisé à des endroits inappropriés. Si quelqu'un parle d'un système qui est resté en panne pendant quelques jours pendant la refactorisation, vous pouvez être sûr qu'il n'était pas en train de refactoriser.

Qu'est-ce que c'est ?

Factorisation

Lorsque vous avez étudié les mathématiques à l'école, vous avez probablement appris la factorisation. Voici un exemple très simple

Calcul de 1/2 + 1/41/2 + 1/4

为此,将分母 分解,将表达式转换为

2/4 + 1/4 你可以把它变成 3/4.

我们可以从中吸取一些重要的教训。当我们 分解表达式 时,我们没有改变表达式的含义。两者都等于 3/4,但我们通过将 1/2 变为 2/4 后,我们的工作变得更容易了;它更适合我们的「领域」。

当你重构代码时,你应该在「符合」你当前系统需求的情况下,尝试找到一个方法来使你的代码更容易理解。关键是你不应该改变代码原有的行为.

Go 的一个例子

下面这个方法是用特定的 language 问候 name

func Hello(name, language string) string {
    if language == "es" {
        return "Hola, " + name
    }

    if language == "fr" {

        return "Bonjour, " + name

    }

    // 想象一下更多的语言

    return "Hello, " + name

}

如果有几十个 if 语句会让人感觉不舒服,而且我们还要重复的使用特定的 language 去伴随着 , 问候 name。因此,我们来重构代码。

func Hello(name, language string) string {
      return fmt.Sprintf(
          "%s, %s",
          greeting(language),
          name,
      )
}

var greetings = map[string]string {
  es: "Hola",
  fr: "Bonjour",
  //等等...
}

func greeting(language string) string {
  greeting, exists := greetings[language]

  if exists {
     return greeting
  }

  return "Hello"
}

实际上,这个重构的本质并不重要,重要的是我们没有改变代码的行为。

当重构时,你可以做任何你喜欢的事情,添加接口,新类型,函数,方法等等。唯一的规则是你不能改变代码的行为。

重构代码时不要改变功能

这非常重要。如果你重构时改变功能,你相当于同时在做 件事。作为软件工程师,我们应该学习把系统分成不同的文件/包/功能/等等,因为我们知道试图理解一大块东西是困难的。

我们不要一次想很多事情,因为那会使我们犯错误。我目睹了许多重构工作的失败,因为开发人员贪多嚼不烂。

当我在数学课上用笔和纸做因式分解时,我必须手动检查我是否改变了头脑中表达式的意思。当我们重构代码时,尤其是在一个重要的系统上,我们如何知道我们是否改变了功能?

那些选择不编写测试的人通常依赖于手动测试。除非是一个小项目,要不然这将是一个巨大的时间消耗,并且从长远来看不利于系统将来的扩展。

为了安全地重构,您需要单元测试因为它提供了

  • 可以在不担心功能改变的情况下重构代码

  • 有助于开发人员编写关于系统应该如何运行的文档

  • 比手工测试更快更可靠的反馈

Go 的一个例子

我们有一个 Hello 方法的单元测试是这样的:

func TestHello(t *testing.T) {

    got := Hello("Chris", es)

    want := "Hola, Chris"

    if got != want {

        t.Errorf("got %q want %q", got, want)

    }

}

在命令行中,我们可以运行 go test

Pour ce faire, factorisez le dénominateur

, en convertissant l'expression en
  • 2/4 + 1/4 Vous pouvez le changer en 3/4

    Nous pouvons en tirer des leçons importantes. Lorsque nous
  • décomposons une expression
  • , nous

    ne changeons pas le sens

    de l'expression. Les deux sont égaux à 3/4, mais notre travail devient plus facile en changeant 1/2 en 2/4 ; "champ".
  • Lorsque vous refactorisez votre code, vous devriez essayer de trouver un moyen de rendre votre code plus facile à comprendre tout en "adaptant" la configuration système actuelle. La clé est

    Vous ne devez pas modifier le comportement d'origine du code

    .
Un exemple de Go

La méthode suivante utilise un langage spécifique Salutations nom <p>rrreee</p>Ce serait inconfortable s'il y avait des dizaines d'instructions <code>if, et que nous devions utiliser à plusieurs reprises un langage spécifique pour accompagner le , salue <code>nom. Alors refactorisons le code.

rrreee En fait, la nature de ce refactoring n'a pas d'importance, ce qui est important c'est que nous n'avons pas changé le comportement du code.

Lors du refactoring, vous pouvez faire ce que vous voulez, ajouter des interfaces, de nouveaux types, fonctions, méthodes, etc. La seule règle est que vous ne pouvez pas modifier le comportement du code.

Ne modifiez pas la fonctionnalité lors de la refactorisation du code

C'est très important. Si vous modifiez des fonctionnalités lors de la refactorisation, vous faites deux

choses en même temps. En tant qu'ingénieurs logiciels, nous devrions apprendre à séparer le système en différents fichiers/packages/fonctions/etc, car nous savons qu'il est difficile d'essayer de comprendre une grande partie de quelque chose.

Il ne faut pas penser à trop de choses à la fois car cela nous ferait commettre des erreurs. J'ai été témoin de l'échec de nombreux efforts de refactorisation parce que les développeurs ont fait plus que ce qu'ils pouvaient mâcher.

Lorsque je factorise avec un stylo et du papier en cours de mathématiques, je dois vérifier manuellement si j'ai changé le sens de l'expression dans ma tête. Lorsque nous refactorisons du code, en particulier sur un système critique, comment savoir si nous avons modifié les fonctionnalités

Ceux qui choisissent de ne pas écrire de tests s'appuient souvent sur des tests manuels ? À moins qu’il ne s’agisse d’un petit projet, cela prendra beaucoup de temps et ne sera pas propice à une expansion future du système à long terme.

Pour refactoriser en toute sécurité, vous avez besoin de tests unitaires

car ils fournissent

le code peut être refactorisé sans se soucier des changements de fonctionnalité 🎜🎜🎜🎜 aide les développeurs à écrire du code sur la façon dont le système doit se comporter Documentation 🎜🎜🎜🎜 Plus rapide et plus fiable feedback que les tests manuels 🎜🎜🎜

Un exemple dans Go

🎜Nous avons un test unitaire pour la méthode Hello comme ceci : 🎜rrreee 🎜Dans la ligne de commande, nous pouvons exécuter allez tester
et obtenez un retour immédiat indiquant si notre travail de refactoring affecte le fonctionnement du programme d'origine. En fait, il vaut mieux apprendre à exécuter des tests dans l'éditeur/IDE. 🎜🎜Vous souhaitez obtenir un état d'exécution de votre programme🎜🎜🎜🎜Petit refactoring🎜🎜🎜🎜Exécutez le test🎜🎜🎜🎜Répétez🎜🎜🎜🎜Le tout dans une boucle de rétroaction très serrée afin qu'aucune erreur ne soit commise. 🎜🎜Dans un projet, tous vos comportements clés sont testés unitairement et les commentaires sont donnés en une seconde, ce qui constitue un filet de sécurité très puissant pour une refactorisation audacieuse lorsque vous en avez besoin. Cela nous aide à gérer la complexité croissante décrite par Lehman. 🎜🎜🎜Les tests unitaires sont si géniaux, pourquoi rencontrez-vous parfois de la résistance lorsque vous les écrivez ? 🎜🎜🎜D'une part, il y a ceux (comme moi) qui disent que les tests unitaires sont importants pour la santé à long terme de votre système car ils garantissent que vous pouvez continuer à refactoriser en toute confiance. 🎜🎜D'un autre côté, certains disent que les tests unitaires 🎜gênent🎜 le refactoring. 🎜🎜Demandez-vous : à quelle fréquence devez-vous modifier les tests lors du refactoring ? Au fil des années, j'ai participé à de nombreux projets comportant une couverture de tests très élevée, mais les ingénieurs étaient réticents à refactoriser car ils pensaient que modifier les tests serait laborieux. 🎜🎜C'est contraire à notre promesse ! 🎜🎜🎜Pourquoi est-ce ? 🎜🎜🎜Supposons qu'on vous demande de dessiner un carré, nous pensons que la meilleure façon est de coller deux triangles ensemble. 🎜🎜🎜🎜🎜Deux triangles rectangles forment un carré

Nous écrivons des tests unitaires autour du carré pour nous assurer que les deux côtés sont égaux, puis nous écrivons quelques tests autour du triangle. Nous voulons nous assurer que nos triangles sont rendus correctement, nous affirmons donc que la somme des angles est de 180 degrés, nous faisons deux tests pour vérifier, etc. La couverture des tests est très importante et écrire ces tests est si simple, pourquoi pas

Quelques semaines plus tard, les changements de lois ont frappé notre système et un nouveau développeur a apporté quelques modifications. Elle pense désormais qu’il vaudrait mieux faire un carré avec deux rectangles plutôt que deux triangles.

Deux rectangles forment un carré

Il a essayé cette refactorisation et a obtenu quelques indices de certains tests échoués. A-t-il vraiment cassé une fonctionnalité importante du code ? Elle doit maintenant approfondir les tests de ces triangles et essayer de comprendre ce qui se passe réellement à l’intérieur.

Peu importe qu'un carré soit composé de triangles, mais nos tests ont élevé par erreur l'importance d'un détail d'implémentation .

Fonctionnalité des tests et non détails d'implémentation

Quand j'entends des gens se plaindre des tests unitaires, c'est généralement parce que les tests sont au mauvais niveau d'abstraction. Ils testent tous les détails de l'implémentation, observent de manière obsessionnelle le code de leurs collaborateurs et se moquent beaucoup.

Je pense que ce problème est dû à leur incompréhension des tests unitaires et à leur recherche de métriques (couverture des tests).

Si je parle uniquement de tester des fonctionnalités, ne devrions-nous pas simplement écrire des tests système/boîte noire ? Ces types de tests sont très utiles pour valider les parcours des utilisateurs clés, mais ils sont souvent coûteux à écrire et lents à exécuter. Pour cette raison, ils n'aident pas beaucoup avec le refactoring, car la boucle de rétroaction est lente. De plus, par rapport aux tests unitaires, les tests en boîte noire n’aident pas beaucoup à résoudre le problème sous-jacent.

Alors, quel est le bon niveau d'abstraction ?

Écrire des tests unitaires efficaces est une question de conception

Oubliez les tests un instant, il est préférable d'inclure des « unités » autonomes et découplées dans votre système, centrées sur les concepts clés de votre domaine.

J'aime considérer ces unités comme de simples briques Lego avec des API cohérentes que je peux combiner avec d'autres briques pour construire des systèmes plus grands. À l’intérieur de ces API, de nombreux éléments (types, fonctions, etc.) peuvent collaborer pour les faire fonctionner selon les besoins.
Par exemple, si vous utilisez Go pour développer un système bancaire, vous devez disposer d'un package « compte ». Il fournira une API qui ne divulguera pas les détails de mise en œuvre et qui sera facile à intégrer.

Si vos unités sont conformes à ces propriétés, vous pouvez écrire des tests unitaires sur leurs API publiques. Par définition, ces tests ne peuvent tester que des fonctionnalités utiles. Avec ces unités en place, nous sommes libres de refactoriser chaque fois que cela est nécessaire, et les tests ne nous gêneront pas dans la plupart des cas.

S'agit-il de tests unitaires ?

Oui. Les tests unitaires concernent ce que je décris comme des « unités ». Ils ne ciblent jamais qu'une seule classe/fonction/quoi que ce soit. "Combiner ces concepts" chacun autre.

Refactoring

    Fournir des signaux pour nos tests unitaires. Si nous devons vérifier manuellement, nous avons besoin de plus de tests. Si le test échoue, alors notre test est au mauvais niveau d'abstraction (ou n'a aucune valeur et doit être supprimé)
  • nous aide à gérer la complexité au sein et entre les unités.
  • Les tests unitaires

offrent une protection de sécurité pour la refactorisation.

Vérifier et documenter la fonctionnalité de nos unités.

  • Unités (bien conçues)
  • Des tests unitaires faciles à écrire
significatifs

.

    Facile à refactoriser.
  • Existe-t-il un processus qui nous aide à refactoriser constamment le code pour gérer la complexité et maintenir le système évolutif ?
  • Pourquoi le développement piloté par les tests (TDD)

Certaines personnes peuvent être confuses par l'idée de Lehman sur la façon dont faire en sorte que les logiciels changent constamment conduit à une ingénierie excessive et à une perte de temps considérable à essayer de créer à l'avance le système évolutif « parfait », pour ensuite ne rien obtenir. Dans le mauvais vieux temps des logiciels, une équipe d'analystes passait 6 mois à rédiger des documents d'exigences, une équipe d'architectes passait 6 mois à concevoir et quelques années plus tard, l'ensemble du projet échouait.

    J'ai dit que le bon vieux temps était mauvais, mais ils le sont toujours !
  • Le développement agile nous enseigne que nous devons travailler de manière itérative, commencer petit et améliorer continuellement le logiciel afin de pouvoir obtenir rapidement des commentaires sur la conception du logiciel et comment le faire. le logiciel fonctionne avec de vrais utilisateurs ; TDD applique cette approche.

    TDD aborde les lois dont parle Lehman et d'autres leçons historiquement difficiles à apprendre en encourageant une approche de développement de refactorisation constante et de livraison itérative.

    Petites étapes

    • Écrivez un petit test pour une petite fonctionnalité

    • Vérifiez si le test a échoué avec des erreurs évidentes (rouge)

    • Écrivez le code minimum pour que le test réussisse (vert)

    • Refactor

    • Répétez les étapes ci-dessus

    À mesure que vous devenez plus compétent, cela deviendra une façon naturelle de travailler pour vous et votre efficacité au travail deviendra de plus en plus élevée

    Vous commencez à espérer que votre une petite unité de test ne prend pas trop de temps pour terminer l'intégralité du test, car si vous voyez que votre programme est constamment dans un état non "vert", c'est le signe que vous pourriez avoir de petits problèmes.

    Avec ces retours de tests, vous pouvez facilement vous assurer de la stabilité de certaines petites fonctions applicatives.

    Fin satisfaisante

    • L'avantage du logiciel est que je peux le modifier selon mes besoins. Au fil du temps, pour des raisons imprévisibles, la plupart des logiciels doivent apporter les modifications correspondantes en fonction des besoins ; mais ne faites pas trop de travail ni de conception excessive au début, car l'avenir est trop difficile à prédire.

    • Donc, afin de répondre aux besoins ci-dessus, nous devons garder notre logiciel évolutif. Sinon, la situation deviendra très mauvaise lorsque notre logiciel devra être refactorisé et mis à niveau.

    • Un bon test unitaire peut vous aider à refactoriser votre projet rapidement et avec plaisir.

    • Écrire de bons tests unitaires est un problème de conception. Vous devez réfléchir soigneusement à la structure de votre code afin que chacun de vos tests unitaires puisse être intégré de manière aussi intéressante que l'assemblage de blocs Lego pour mener à bien les tests de l'ensemble du projet.

    • Le développement piloté par les tests (TDD Test-Driven Development) peut vous aider et vous inciter à développer de manière itérative un logiciel bien conçu. Son utilisation comme support technique sera d'une grande aide pour votre travail futur.

    Adresse originale : https://quii.gitbook.io/learn-go-with-tests/meta/why

    Adresse de traduction : https://learnku.com/go/t/34095

    Pour plus de connaissances sur la programmation, veuillez visiter : Vidéos de programmation ! !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer