Tests de fonctionnalités

Temps de lecture5 min

En bref

Résumé de l’article

Dans cet article, nous introduisons les tests, qui visent à vérifier que les codes développés se comportent comme attendu. L’automatisation de l’exécution des tests est aussi utile pour vérifier qu’une nouvelle fonctionnalité dans un code n’a pas cassé quelque chose dans le code déjà existant.

Nous mentionnons aussi comment concevoir de tels tests, et montrons des exemples avec des bibliothèques dédiées.

Points importants à retenir

  • Les tests et la documentation ensemble assurent la robustesse et la durabilité à long terme du logiciel.

  • L’écriture de tests assure que le logiciel fonctionne comme attendu et identifie rapidement les bugs et régressions.

  • Cela encourage les développeurs à réfléchir de manière critique sur la conception et la fonctionnalité du code, conduisant à un code plus propre, plus fiable et plus robuste.

  • Les tests automatisés facilitent la maintenance en détectant rapidement les problèmes qui nécessitent une correction.

  • Les tests automatisés sont incontournables dans les pipelines d’intégration continue (CI) et de déploiement continu (CD), s’assurant que les nouveaux changements sont constamment testés et intégrés sans introduire de nouveaux problèmes.

Contenu de l’article

1 — Tester votre code

1.1 — Le but des tests

Écrire des tests pour le code est une pratique essentielle en développement logiciel, car cela assure que chaque partie du programme fonctionne comme attendu. Les tests automatisés, qu’ils soient des tests unitaires, d’intégration, ou système, détectent rapidement les bugs et régressions, facilitant la maintenance et améliorant la qualité globale du logiciel.

En plus de la détection d’erreurs, les tests renforcent la confiance des développeurs pour faire des changements au code et rendent le processus de refactorisation plus fluide. De plus, des tests complets promeuvent une meilleure conception de code en encourageant les développeurs à considérer les cas limites et points de défaillance potentiels tôt dans le processus de développement. Cela supporte aussi les pratiques d’intégration continue et de déploiement (CI/CD), s’assurant que les nouveaux changements de code sont minutieusement vérifiés avant d’être intégrés dans la base de code principale.

En résumé, écrire des tests est une mesure proactive qui améliore la fiabilité, la maintenabilité et l’efficacité de développement du logiciel.

1.2 — Quel type de test ?

Il y a plusieurs types de tests utilisés en développement logiciel, chacun servant un but spécifique et ciblant différents aspects du logiciel. Voici quelques types communs de tests :

  • Tests unitaires – Les tests unitaires se concentrent sur le test d’unités ou composants individuels du logiciel, tels que les fonctions, méthodes, ou classes, en isolation du reste de l’application. Ils vérifient que chaque unité fonctionne correctement selon sa spécification.

  • Tests d’intégration – Les tests d’intégration vérifient que différentes unités ou composants du logiciel fonctionnent ensemble correctement. Ils testent les interactions entre ces unités et s’assurent qu’elles communiquent et collaborent comme attendu.

  • Tests de régression – Les tests de régression visent à s’assurer que les changements apportés à la base de code, tels que les corrections de bugs ou ajouts de nouvelles fonctionnalités, n’introduisent pas de nouveaux défauts ou régressions dans le logiciel. Ils aident à maintenir la stabilité et fiabilité de l’application au fil du temps.

  • Tests de sécurité – Les tests de sécurité évaluent la sécurité du logiciel et identifient les vulnérabilités ou faiblesses potentielles qui pourraient être exploitées par des attaquants. Ils aident à s’assurer que le logiciel est protégé contre les menaces et violations de sécurité.

En utilisant une combinaison de ces tests tout au long du cycle de vie de développement logiciel, les équipes peuvent s’assurer que le logiciel répond aux standards de qualité, fonctionne de manière fiable, et satisfait les besoins des utilisateurs.

2 — Tests unitaires

Dans cet article, nous nous concentrerons sur les tests unitaires.

Un test unitaire est un type de test logiciel qui se concentre sur la vérification de la validité fonctionnelle d’unités individuelles de code. Une “unité” est la plus petite partie testable d’une application, comme une fonction, une méthode, ou une classe. L’objectif principal des tests unitaires est de s’assurer que chaque unité fonctionne comme attendu en isolation du reste de l’application.

Écrire des tests peut sembler un processus lent au premier regard, mais ils sont essentiels pour livrer un programme à temps et dans le budget. Les tests aident à détecter les bugs tôt dans le cycle de développement, réduisant le coût de correction des erreurs. Ils permettent aussi au code d’être refactorisé avec confiance et aident le code à être compris. Les frameworks de test permettent d’écrire des tests rapidement.

2.1 — Bibliothèques spécialisées : unittest

Comme indiqué dans la documentation unittest, unittest s’est inspiré du module de test Java JUnit:

The unittest unit testing framework was originally inspired by JUnit and has a similar flavor as major unit testing frameworks in other languages.

Pour voir comment construire des tests, voici une fonction qui inverse la capitalisation d’un mot :

def inverse_capitalization (word: str) -> str:

    """
        Inverts the capitalization of a word.
        For instance Hello should be transformed to hELLO.
        In:
            * word: The word to process.
        Out:
            * The word with inversed capitalization.
    """

    # Fill a list char by char
    result = []
    for char in word:
        result.append(char.lower() if char.isupper() else char.upper())
    
    # Recreate the string
    return ''.join(result)

Afin de s’assurer que la méthode fait ce qui est attendu d’elle, une classe de test a été écrite.

# Needed imports
import unittest



# Define a class for all your tests of the unit you want to test
# Here, the unit is a single function, but it could be an entire module, or a class
# The name of your test class (here, TestInverseCapitalization) should represent what you will test
# For the particular case of unit tests, it should inherit from unittest.TestCase as follows
# This allows access to useful methods such as 'assertEqual'
class TestInverseCapitalization (unittest.TestCase):

    # Define a method that will test the method for uppercase inputs
    # The name of the method should be representative of the test, and start with test_
    # Also, as it is a method in a class, it should have 'self' as first argument
    def test_lower (self):

        # For tests, it is good to work with assertions
        # Here, assertEqual is provided by the unittest library
        # It is nearly equivalent to: assert 'HELLO!' == inverse_capitalization('hello!')
        # The library provides other methods for complex assertions
        self.assertEqual('HELLO!', inverse_capitalization('hello!'))

    # Another method for another test
    def test_upper (self):
        self.assertEqual('hello!', inverse_capitalization('HELLO!'))

    # Another method for another test
    def test_mix (self):
        self.assertEqual('hElLo!', inverse_capitalization('HeLlO!'))    



# In the main, you can then ask unittest to run all your defined tests
if __name__ == '__main__':
    _ = unittest.main(argv=[""], verbosity=2, exit=False)

Cette classe de tests, appelée TestInverseCapitalization, contient trois tests. Seul un concept est évalué par test et seul un test est créé par concept. Notez que chaque méthode qui teste une fonctionnalité commence par test_.

Par exemple, le premier test s’assure qu’un mot écrit entièrement en minuscules est correctement transformé en un mot écrit entièrement en majuscules. Le test lui-même consiste en un appel à la fonction assertEqual(expected, actual, msg=None). Il teste si expected et actual sont égaux, et il échouera s’ils ne le sont pas. De plus, il garantit que expected et actual sont du même type.

Exécutez cette classe de test pour confirmer que les concepts ont été correctement implémentés, indiquant tous les bugs par des tests échoués.

En exécutant unittest.main(), toutes les méthodes dans la classe TestInverseCapitalization qui commencent par test_ sont exécutées. Les résultats sont affichés dans le terminal, montrant quels tests ont réussi et lesquels ont échoué. C’est plus intéressant que d’avoir simplement une liste d’assertions, car la bibliothèque générera un rapport complet, ce qui est mieux pour le débogage.

Les frameworks de test fournissent un ensemble de méthodes prêtes à l’emploi qui simplifient l’élaboration des tests : méthodes assert unittest.

2.2 — Écrire le code ou les tests en premier ?

Une bonne pratique est d’avoir les tests écrits avant que le code soit écrit par quelqu’un d’autre que la personne qui va développer le code :

  • Les tests écrits à l’avance servent de spécification claire du comportement attendu.
  • La personne qui écrit les tests se concentre sur ce que le code devrait accomplir sans être influencée par les détails d’implémentation.
  • Cela promeut une approche de développement dirigée par la fonctionnalité et les besoins utilisateur.
  • Cela encourage la communication et s’assure que les deux parties ont une compréhension partagée des objectifs du projet.

Pour aller plus loin

Quand le code que vous voulez tester interagit avec d’autres composants de votre code, il est possible de simuler la partie du code avec laquelle vous interagissez. C’est en utilisant la bibliothèque d’objets mock. C’est particulièrement pertinent quand une fonction attend une entrée utilisateur.

Pour aller au-delà