Tester votre code

Tester votre code est très important.

S’habituer à écrire le code de test et le code en cours d’exécution en parallèle est maintenant considéré comme une bonne habitude. Utilisé à bon escient, cette méthode vous aide à définir plus précisément l’intention de votre code et à avoir une architecture plus découplée.

Quelques règles générales de test:

  • Un test unitaire doit se concentrer sur un tout petit morceau de fonctionnalité et prouver qu’il est correct.

  • Chaque test unitaire doit être complètement indépendant. Chacun d’eux doit être capable de s’exécuter seul, et aussi à l’intérieur de la suite de tests, indépendamment de l’ordre dans lesquels ils sont appelés. L’implication de cette règle est que chaque test doit être chargé avec un nouveau jeu de données et peut avoir à faire un peu de nettoyage après. Cela est généralement géré par les méthodes setUp() et tearDown().

  • Essayez très fort de faire des tests qui s’exécutent vite. Si un seul test a besoin de plus de quelques millisecondes pour s’exécuter, le développement sera ralenti ou les tests ne seront pas exécutés aussi souvent que ce serait souhaitable. Dans certains cas, les tests ne peuvent pas être rapides parce qu’ils ont besoin d’une structure de données complexes sur laquelle travailler, et cette structure de données doit être chargée chaque fois que le test s’exécute. Gardez ces tests plus lourds dans une suite de tests séparés qui est gérée par une tâche planifiée, et exécutez tous les autres tests aussi souvent que nécessaire.

  • Apprenez vos outils et apprenez à gérer un seul test ou une série de tests. Puis, lors du développement d’une fonction à l’intérieur d’un module, exécutez cette fonction de tests très souvent, idéalement automatiquement lorsque vous enregistrez le code.

  • Exécutez toujours la suite de tests complète avant une session de codage, et exécutez-la à nouveau après. Cela vous donnera plus de confiance en vérifiant que que vous n’avez rien cassé dans le reste du code.

  • C’est une bonne idée d’implémenter un hook qui exécute tous les tests avant de pousser le code vers un dépôt partagé.

  • Si vous êtes au milieu d’une session de développement et avez à interrompre votre travail, c’est une bonne idée d’écrire un test unitaire cassé sur ce que vous voulez développer prochainement. En reprenant votre travail, vous aurez un pointeur à l’endroit où vous étiez et pourrez revenir plus rapidement sur la bonne voie.

  • La première étape lorsque vous débugguez votre code est d’écrire un nouveau test localisant exactement le bug. Bien qu’il ne soit pas toujours possible de faire, ces tests pour attraper les bugs sont parmi les morceaux les plus précieux de code dans votre projet.

  • Utilisez des noms longs et descriptifs pour les fonctions de test. Le guide de style ici est légèrement différent de celui du code s’exécutant, où les noms courts sont souvent préférés. La raison est de tester les fonctions qui ne sont jamais appelées explicitement. square() ou même sqr() est ok dans un code en cours d’exécution, mais dans le code de test, vous auriez des noms tels que test_square_of_number_2(), test_square_negative_number(). Ces noms de fonction sont affichés quand un test échoue, et devraient être aussi descriptifs que possible.

  • Quand quelque chose va mal ou doit être changé, et si votre code a une bonne série de tests, vous ou d’autres mainteneurs allez vous reposer en grande partie sur la suite de tests pour corriger le problème ou modifier un comportement donné. Par conséquent, le code de test sera lu autant ou même plus que le code en cours d’exécution. Un test unitaire dont le la finalité est incertaine est pas très utile dans ce cas.

  • Une autre utilisation du code de test est comme une introduction aux nouveaux développeurs. Quand quelqu’un aura à travailler sur la base de code, exécuter et lire le code de test lié est souvent le mieux qu’ils peuvent faire. Ils vont ou devraient découvrir les points chauds, où la plupart des difficultés sont rencontrées, et les cas limites. Si ils doivent ajouter des fonctionnalités, la première étape devrait être d’ajouter un test et, par ce moyen, de s’assurer que la nouvelle fonctionnalité est pas déjà un chemin de travail qui n’a pas été branché sur l’interface.

Les basiques

Unittest

unittest est le module de test “tout en un” dans la bibliothèque standard Python. Son API sera familière à quiconque a déjà utilisé une des séries d’outils JUnit/nUnit/CppUnit.

Créer des cas de test est réalisé en faisant des sous-classes de unittest.TestCase.

import unittest

def fun(x):
    return x + 1

class MyTest(unittest.TestCase):
    def test(self):
        self.assertEqual(fun(3), 4)

A partir de Python 2.7, unittest comprend également ses propres mécanismes de découverte de tests.

Doctest

Le module doctest recherche des morceaux de texte qui ressemblent à des sessions de Python interactifs en docstrings, puis exécute ces sessions pour vérifier qu’ils fonctionnent exactement comme indiqué.

Les doctests ont un cas d’utilisation différent des tests unitaires appropriés: ils sont généralement moins détaillés et ne capturent pas des cas particuliers ou des bugs de régression obscurs. Ils sont utiles en tant que documentation expressive des principaux cas d’utilisation d’un module et de ses composants. Cependant, les doctests doivent exécuter automatiquement à chaque fois que la suite de tests complète s’exécute.

Un simple doctest dans une fonction:

def square(x):
    """Return the square of x.

    >>> square(2)
    4
    >>> square(-2)
    4
    """

    return x * x

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Lors de l’exécution de ce module à partir de la ligne de commande comme dans python module.py, les doctests vont s’exécuter et se plaindre si rien ne se comporte comme décrit dans les docstrings.

Outils

py.test

py.test est une alternative sans boilerplate au module unittest standard Python.

$ pip install pytest

En dépit d’être un outil de test plein de fonctionnalités et extensible, il bénéficie d’une syntaxe simple. Créer une suite de tests est aussi facile qu’écrire un module avec un couple de fonctions:

# content of test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

et ensuite en exécutant la commande py.test

$ py.test
=========================== test session starts ============================
platform darwin -- Python 2.7.1 -- pytest-2.2.1
collecting ... collected 1 items

test_sample.py F

================================= FAILURES =================================
_______________________________ test_answer ________________________________

    def test_answer():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================

est beaucoup moins de travail que ce qui serait nécessaire pour la fonctionnalité équivalente avec le module unittest!

Nose

nose étend unittest pour rendre les tests plus faciles.

$ pip install nose

nose fournit la découverte automatique de tests pour vous épargner les tracas de créer manuellement des suites de tests. Il fournit également de nombreux plugins pour des fonctionnalités telles que la sortie de test compatible xUnit, les rapports sur la couverture et la sélection de tests.

tox

tox est un outil pour automatiser la gestion de l’environnement de test et les tests ciblant des configurations d’interpréteurs multiples

$ pip install tox

tox vous permet de configurer des matrices de test multi-paramètres complexes via un simple fichier de configuration de type INI.

Unittest2

unittest2 est un portage du module unittest Python 2.7 qui a une API améliorée et de meilleures assertions par rapport à celui disponible dans les versions précédentes de Python.

Si vous utilisez Python 2.6 ou inférieur, vous pouvez l’installer avec pip

$ pip install unittest2

Vous pouvez vouloir faire l’import du module sous le nom unittest pour rendre le portage du code pour les nouvelles versions du module plus facile dans le futur

import unittest2 as unittest

class MyTest(unittest.TestCase):
    ...

De cette façon, si jamais vous basculez à une version plus récente de Python et n’avez plus besoin du module unittest2, vous pouvez simplement changer l’import dans votre module de test sans avoir besoin de changer aucun autre code.

mock

unittest.mock est une bibliothèque pour les tests en Python. A partir de Python 3.3, elle est disponible dans la bibliothèque standard.

Pour les versions anciennes de Python:

$ pip install mock

Il vous permet de remplacer les parties de votre système en test avec des objets mock et de faire des assertions sur la façon dont ils ont été utilisés.

Par exemple, vous pouvez monkey-patcher une méthode:

from mock import MagicMock
thing = ProductionClass()
thing.method = MagicMock(return_value=3)
thing.method(3, 4, 5, key='value')

thing.method.assert_called_with(3, 4, 5, key='value')

Pour mocker des classes ou des objets dans un module en test, utilisez le décorateur patch. Dans l’exemple ci-dessous, un système de recherche externe est remplacé par un mock qui retourne toujours le même résultat (mais seulement pour la durée du test).

def mock_search(self):
    class MockSearchQuerySet(SearchQuerySet):
        def __iter__(self):
            return iter(["foo", "bar", "baz"])
    return MockSearchQuerySet()

# SearchForm here refers to the imported class reference in myapp,
# not where the SearchForm class itself is imported from
@mock.patch('myapp.SearchForm.search', mock_search)
def test_new_watchlist_activities(self):
    # get_search_results runs a search and iterates over the result
    self.assertEqual(len(myapp.get_search_results(q="fish")), 3)

Mock a beaucoup d’autres façons pour que vous le configuriez et contrôliez son comportement.