Tests unitaires - pour commencer

14

Je ne fais que commencer les tests unitaires, mais je ne sais pas si je comprends vraiment l'intérêt de tout cela. J'ai lu des tutoriels et des livres sur tout cela, mais j'ai juste deux questions rapides:

  1. Je pensais que le but des tests unitaires était de tester le code que nous avions réellement écrit. Cependant, il me semble que pour pouvoir simplement exécuter le test, nous devons modifier le code d'origine, auquel cas nous ne testons pas vraiment le code que nous avons écrit mais plutôt le code que nous avons écrit pour le test.

  2. La plupart de nos codes reposent sur des sources externes. Cependant, après refactorisation de notre code, même s'il cassait le code d'origine, nos tests fonctionneraient toujours très bien, car les sources externes ne sont que des saletés dans nos cas de test. Cela ne va-t-il pas à l'encontre du but des tests unitaires?

Désolé si j'ai l'air stupide ici, mais j'ai pensé que quelqu'un pourrait m'éclairer un peu.

Merci d'avance.

treecoder
la source

Réponses:

7

Mon 0,02 $ ... c'est un peu subjectif, alors prenez un grain de sel, mais j'espère que cela vous fera réfléchir et / ou suscitera un dialogue:

  1. Le but principal des tests unitaires pour moi est de s'assurer que le code que vous avez écrit remplit les contrats et les cas limites que votre code est censé respecter. Avec les tests unitaires en place, vous pouvez mieux vous assurer que lorsque vous (ou quelqu'un d'autre à l'avenir) refactorisez votre code, les consommateurs externes de votre code ne devraient pas être affectés si vous avez une couverture d'État appropriée. (au moins dans la mesure où vous souhaitez qu'ils ne soient pas affectés).

    Dans la majorité des cas, vous devriez être en mesure d'écrire du code qui peut à la fois être expédié en production et facilement testable à l'unité. Un bon point de départ peut être d'examiner les schémas et les cadres d'injection de dépendance. (Ou d'autres philosophies pour votre langue / plate-forme de choix).

  2. Il est exact que les implémentations externes pourraient affecter votre code. Cependant, s'assurer que votre code fonctionne correctement dans le cadre d'un système plus vaste est une fonction des tests d' intégration . (Qui pourrait également être automatisé avec différents degrés d'effort).

    Idéalement, votre code ne devrait s'appuyer que sur les contrats d'API de tout composant tiers, ce qui signifierait que tant que vos maquettes respectent l'API correcte, vos tests unitaires fournissent toujours de la valeur.

    Cela dit, j'admettrai volontiers qu'il y a eu des moments où j'ai renoncé aux tests unitaires au profit des tests d'intégration seuls, mais ce n'est que dans les cas où mon code a dû interagir si abondamment avec des composants tiers avec des API mal documentées. (c'est-à-dire l'exception plutôt que la règle).

Charlie
la source
5
  1. Essayez d'abord d'écrire vos tests. De cette façon, vous aurez une base solide pour le comportement de votre code et votre test deviendra un contrat pour le comportement requis de votre code. Ainsi, changer le code pour réussir le test devient "changer le code pour remplir le contrat proposé par le test" au lieu de "changer le code pour réussir le test".
  2. Eh bien, faites attention à la différence entre les talons et les faux. Ne pas être affecté par les modifications du code est le comportement caractéristique des talons, mais pas des moqueries. Commençons par la définition de la maquette:

    Un objet Mock remplace un objet réel dans des conditions de test et permet de vérifier les appels (interactions) par rapport à lui-même dans le cadre d'un système ou d'un test unitaire.

    -L'art des tests unitaires

Fondamentalement, vos simulations devraient vérifier le comportement requis de vos interactions. Ainsi, si votre interaction avec la base de données échoue après une refactorisation, votre test utilisant la maquette devrait également échouer. Bien sûr, cela a des limites, mais avec une planification minutieuse, vos simulateurs feront bien plus que de "rester là" et cela "ne va pas à l'encontre du but des tests unitaires".

ruhsuzbaykus
la source
1

Poser une bonne question n'est pas idiot du tout.

Je répondrai à vos questions.

  1. Le but des tests unitaires n'est pas de tester le code que vous avez déjà écrit . Il n'a aucune notion du temps. Seulement dans TDD, vous êtes censé tester en premier, mais cela ne s'applique pas strictement à tout type de test unitaire. Le but est de pouvoir tester automatiquement et efficacement votre programme au niveau de la classe. Vous faites ce que vous devez faire pour y arriver, même si cela signifie changer le code. Et laissez-moi vous dire un secret - cela signifie souvent cela.
  2. Lorsque vous écrivez un test, vous avez 2 options principales pour vous assurer que votre test est correct:

    • Variez les entrées pour chaque test
    • Écrivez un cas de test qui garantit que votre programme fonctionne correctement, puis écrivez un cas de test correspondant qui garantit que votre programme ne fonctionne pas comme il ne devrait pas

    Voici un exemple:

    TEST(MyTest, TwoPlusTwoIsFour) {
        ASSERT_EQ(4, 2+2);
    }
    
    TEST(MyTest, TwoPlusThreeIsntFour) {
        ASSERT_NE(4, 2+3);
    }
    

    En plus de cela, si vous testez la logique à l' intérieur de votre code (pas les bibliothèques tierces), il est tout à fait correct que vous ne vous inquiétiez pas de l'autre code, alors que dans ce contexte. Vous testez essentiellement la façon dont votre logique se déroule et utilise les utilitaires tiers, qui est un scénario de test classique.

Une fois que vous avez terminé les tests au niveau de la classe, vous pouvez continuer à tester l'intégration entre vos classes (les médiateurs, d'après ce que je comprends) et les bibliothèques tierces. Ces tests sont appelés tests d'intégration et n'utilisent pas de maquette, mais plutôt les implémentations concrètes de toutes les classes. Ils sont un peu plus lents, mais toujours très importants!

Yam Marcovic
la source
1

Il semble que vous ayez une application monolithique qui fait tout void main(), de l'accès à la base de données à la génération de sortie. Il y a plusieurs étapes ici avant de pouvoir commencer les tests unitaires appropriés.

1) Trouvez un morceau de code qui a été écrit / copié-collé plus d'une fois. Même si c'est juste string fullName = firstName + " " + lastName. Divisez cela en une méthode, comme:

private static string GetFullName (firstName, lastName)
{
    return firstName + " " + lastName;
}

Vous avez maintenant un morceau de code testable unitaire, aussi banal soit-il. Écrivez un test unitaire pour cela. Rincez et répétez ce processus. Vous finirez par vous retrouver avec une charge de méthodes regroupées logiquement, et vous pourrez en extraire un certain nombre de classes. La plupart de ces classes seront testables.

En prime, une fois que vous avez extrait plusieurs classes, vous pouvez en extraire des interfaces et mettre à jour votre programme pour parler aux interfaces au lieu des objets eux-mêmes. À ce stade, vous pouvez utiliser un cadre de moquerie / stubbing (ou même vos propres contrefaçons roulées à la main) sans changer le programme du tout. Cela est très pratique une fois que vous avez extrait les requêtes de base de données dans une classe (ou plusieurs), car vous pouvez désormais simuler les données que la requête doit renvoyer.

Bryan Boettcher
la source
0

Un gars intelligent a dit: "Si c'est difficile à tester, c'est probablement un mauvais code". C'est pourquoi ce n'est pas une mauvaise chose de réécrire du code, pour pouvoir le rendre plus inapte. Le code avec des dépendances externes est TRÈS DIFFICILE à tester, il représente un risc pour le code, et doit être évité autant que possible, et concentré dans des zones spécifiques à l'intégration de votre code, fx. classes de type façade / passerelle. Cela rendra un changement dans le composant externe beaucoup plus facile à gérer.

Morten
la source