
Salut, habrozhiteli! Le chemin Python vous permet de perfectionner vos compétences professionnelles et d'en apprendre autant que possible sur les capacités du langage de programmation le plus populaire. Vous apprendrez à écrire du code efficace, à créer les meilleurs programmes en un minimum de temps et à éviter les erreurs courantes. Il est temps de vous familiariser avec l'informatique et la mémorisation multithread, d'obtenir des conseils d'experts dans le domaine de la conception d'API et de bases de données, ainsi que de regarder à l'intérieur de Python pour approfondir votre compréhension du langage. Vous devez démarrer un projet, travailler avec des versions, organiser des tests automatiques et choisir un style de programmation pour une tâche spécifique. Ensuite, vous continuerez à étudier les déclarations de fonctions efficaces, à sélectionner les structures de données et les bibliothèques appropriées, à créer des programmes et des packages sans problème et à optimiser les programmes au niveau du bytecode.
Extrait. Exécution de tests en parallèle
L'exécution des suites de tests peut prendre du temps. Il s'agit d'un phénomène courant dans les grands projets lorsqu'une suite de tests prend quelques minutes. Par défaut, pytest exécute les tests de manière séquentielle, dans un ordre spécifique.
Étant donné que la plupart des ordinateurs ont des processeurs multicœurs, vous pouvez accélérer si vous séparez les tests pour les exécuter sur plusieurs cœurs.
Pour cela, pytest dispose d'un plugin pytest-xdist qui peut être installé à l'aide de pip. Ce plugin étend la ligne de commande pytest avec l'argument ––numprocesses (abrégé –n), qui prend le nombre de cœurs utilisés comme argument. Le lancement de pytest –n 4 exécutera la suite de tests en quatre processus parallèles, maintenant un équilibre entre la charge des cœurs disponibles.
En raison du fait que le nombre de cœurs peut varier, le plugin accepte également le mot clé auto comme valeur. Dans ce cas, le nombre de cœurs disponibles sera retourné automatiquement.
Création d'objets utilisés dans les tests à l'aide d'appareils
Lors des tests unitaires, il est souvent nécessaire d'effectuer un ensemble d'opérations standard avant et après l'exécution du test, et ces instructions impliquent certains composants. Par exemple, vous pouvez avoir besoin d'un objet qui exprimera l'état de configuration de l'application, et il doit être initialisé avant chaque test, puis réinitialisé à ses valeurs initiales après exécution. De même, si le test dépend d'un fichier temporaire, ce fichier doit être créé avant le test et supprimé après. Ces composants sont appelés
montages . Ils sont installés avant le test et disparaissent après son exécution.
Dans pytest, les appareils sont déclarés comme des fonctions simples. La fonction fixture doit renvoyer l'objet souhaité afin qu'en testant où il est utilisé, cet objet puisse être utilisé.
Voici un exemple de montage simple:
import pytest @pytest.fixture def database(): return <some database connection> def test_insert(database): database.insert(123)
L'appareil de base de données est automatiquement utilisé par tout test qui a l'argument de base de données dans sa liste. La fonction test_insert () recevra le résultat de la fonction database () comme premier argument et utilisera ce résultat comme bon lui semble. Avec cette utilisation des appareils, vous n'avez pas besoin de répéter le code d'initialisation de la base de données plusieurs fois.
Une autre caractéristique commune des tests de code est la possibilité de supprimer le superflu après l'opération du luminaire. Par exemple, fermez la connexion à la base de données. L'implémentation du luminaire en tant que générateur ajoutera des fonctionnalités pour nettoyer les objets vérifiés (Listing 6.5).
Listing 6.5. Effacement d'un objet vérifié
import pytest @pytest.fixture def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123)
Puisque nous avons utilisé le mot-clé yield et créé un générateur à partir de la base de données, le code après l'instruction yield n'est exécuté qu'à la fin du test. Ce code fermera la connexion à la base de données à la fin du test.
La fermeture de la connexion à la base de données pour chaque test peut entraîner un gaspillage injustifié de la puissance de calcul, car d'autres tests peuvent utiliser une connexion déjà ouverte. Dans ce cas, vous pouvez passer l'argument scope au décorateur de luminaire, en spécifiant sa portée:
import pytest @pytest.fixture(scope="module") def database(): db = <some database connection> yield db db.close() def test_insert(database): database.insert(123)
En spécifiant le paramètre scope = "module", vous avez initialisé le luminaire une fois pour l'ensemble du module, et maintenant une connexion de base de données ouverte sera disponible pour toutes les fonctions de test qui le demandent.
Vous pouvez exécuter du code général avant ou après le test, en définissant les appareils comme utilisés automatiquement avec le mot-clé autouse, plutôt que de les spécifier comme argument pour chaque fonction de test. Concrétiser la fonction pytest.fixture () à l'aide de l'argument True, le mot clé autouse, garantit que les fixtures sont appelées à chaque fois avant d'exécuter le test dans le module ou la classe où il est déclaré.
import os import pytest @pytest.fixture(autouse=True) def change_user_env(): curuser = os.environ.get("USER") os.environ["USER"] = "foobar" yield os.environ["USER"] = curuser def test_user(): assert os.getenv("USER") == "foobar"</source . , : , , . <h3> </h3> , , , . Gnocchi, . Gnocchi <i>storage API</i>. Python . , API . , ( storage API), , . , <i> </i>, , . 6.6 , : mysql, — postgresql. <blockquote><h4> 6.6. </h4> <source lang="python">import pytest import myapp @pytest.fixture(params=["mysql", "postgresql"]) def database(request): d = myapp.driver(request.param) d.start() yield d d.stop() def test_insert(database): database.insert("somedata")
Le dispositif de pilote reçoit deux valeurs différentes en tant que paramètre - les noms des pilotes de base de données pris en charge par l'application. test_insert est exécuté deux fois: une fois pour la base de données MySQL et la seconde pour la base de données PostgreSQL. Cela permet de reprendre facilement le même test, mais avec des scénarios différents, sans ajouter de nouvelles lignes de code.
Tests gérés avec des objets factices
Les objets factices (ou stubs, objets fantaisie) sont des objets qui imitent le comportement des objets d'application réels, mais dans un état contrôlé spécial. Ils sont très utiles pour créer des environnements qui décrivent en détail les conditions du test. Vous pouvez remplacer tous les objets à l'exception de celui testé par des objets factices et les isoler, ainsi que créer un environnement pour les tests de code.
Un cas d'utilisation consiste à créer un client HTTP. Il est presque impossible (ou plutôt incroyablement difficile) de créer un serveur HTTP sur lequel vous pouvez exécuter toutes les situations et scénarios pour chaque valeur possible. Les clients HTTP sont particulièrement difficiles à tester pour les scénarios d'erreur.
La bibliothèque standard possède une commande simulée pour créer un objet factice. À partir de Python 3.3, mock a été intégré à la bibliothèque unittest.mock. Par conséquent, vous pouvez utiliser l'extrait de code ci-dessous pour fournir une compatibilité descendante entre Python 3.3 et versions antérieures:
try: from unittest import mock except ImportError: import mock
La bibliothèque fictive est très facile à utiliser. Tout attribut disponible pour l'objet mock.Mock est créé dynamiquement lors de l'exécution. N'importe quel attribut peut recevoir n'importe quelle valeur. Dans le Listing 6.7, mock est utilisé pour créer un objet factice pour l'attribut factice.
Listing 6.7. Accès à l'attribut mock.Mock
>>> from unittest import mock >>> m = mock.Mock() >>> m.some_attribute = "hello world" >>> m.some_attribute "hello world"
Vous pouvez également créer dynamiquement une méthode pour un objet mutable, comme dans l'extrait 6.8, où vous créez une méthode factice qui renvoie toujours 42 et prend tout ce que vous voulez comme argument.
Listing 6.8. Création d'une méthode pour l'objet factice mock.Mock
>>> from unittest import mock >>> m = mock.Mock() >>> m.some_method.return_value = 42 >>> m.some_method() 42 >>> m.some_method("with", "arguments") 42
Juste quelques lignes, et l'objet mock.Mock a maintenant une méthode some_method (), qui retourne 42. Il prend n'importe quel type d'argument, alors qu'il n'y a aucune vérification de ce qu'est l'argument.
Les méthodes générées dynamiquement peuvent également avoir des effets secondaires (intentionnels). Afin de ne pas être uniquement des méthodes passe-partout qui renvoient une valeur, elles peuvent être définies pour exécuter du code utile.
Le Listing 6.9 crée une méthode factice qui a un effet secondaire - il affiche la chaîne «hello world».
Listing 6.9. Création d'une méthode pour un objet mock.Mock avec un effet secondaire
>>> from unittest import mock >>> m = mock.Mock() >>> def print_hello(): ... print("hello world!") ... return 43 ... ❶ >>> m.some_method.side_effect = print_hello >>> m.some_method() hello world! 43 ❷ >>> m.some_method.call_count 1
Nous avons affecté une fonction entière à l'attribut some_method ❶. Techniquement, cela vous permet d'implémenter un scénario plus complexe dans le test, car vous pouvez inclure tout code nécessaire au test dans l'objet factice. Ensuite, vous devez passer cet objet à la fonction qui l'attend.
L'attribut ❷ call_count est un moyen simple de vérifier le nombre de fois qu'une méthode a été appelée.
La bibliothèque fictive utilise le modèle «action-check»: cela signifie qu'après le test, vous devez vous assurer que les actions remplacées par des mannequins ont été effectuées correctement. Le listing 6.10 applique la méthode assert () aux objets factices pour effectuer ces vérifications.
Listing 6.10. Méthodes de vérification des appels
>>> from unittest import mock >>> m = mock.Mock() ❶ >>> m.some_method('foo', 'bar') <Mock name='mock.some_method()' id='26144272'> ❷ >>> m.some_method.assert_called_once_with('foo', 'bar') >>> m.some_method.assert_called_once_with('foo', ❸mock.ANY) >>> m.some_method.assert_called_once_with('foo', 'baz') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.7/dist-packages/mock.py", line 846, in assert_cal led_once_with return self.assert_called_with(*args, **kwargs) File "/usr/lib/python2.7/dist-packages/mock.py", line 835, in assert_cal led_with raise AssertionError(msg) AssertionError: Expected call: some_method('foo', 'baz') Actual call: some_method('foo', 'bar')
Nous avons créé des méthodes avec des arguments foo et bar comme tests en appelant la méthode ❶. Un moyen facile de tester les appels aux objets factices consiste à utiliser les méthodes assert_called (), telles que assert_called_once_with () ❷. Pour ces méthodes, vous devez transmettre les valeurs que vous prévoyez d'utiliser lors de l'appel de la méthode factice. Si les valeurs transmises diffèrent de celles utilisées, mock déclenche une exception AssertionError. Si vous ne savez pas quels arguments peuvent être transmis, utilisez mock.ANY comme valeur de ❸; il remplacera tout argument passé à la méthode factice.
La bibliothèque fictive peut également être utilisée pour remplacer une fonction, une méthode ou un objet d'un module externe. Dans l'extrait 6.11, nous avons remplacé la fonction os.unlink () par notre propre fonction factice.
Listing 6.11. Utilisation de mock.patch
>>> from unittest import mock >>> import os >>> def fake_os_unlink(path): ... raise IOError("Testing!") ... >>> with mock.patch('os.unlink', fake_os_unlink): ... os.unlink('foobar') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 2, in fake_os_unlink IOError: Testing!
Lorsqu'il est utilisé comme gestionnaire de contexte, mock.patch () remplace la fonction cible par celle que nous sélectionnons. Cela est nécessaire pour que le code exécuté dans le contexte utilise la méthode corrigée. En utilisant la méthode mock.patch (), vous pouvez modifier n'importe quelle partie du code externe, le forçant à se comporter de manière à tester toutes les conditions de l'application (Listing 6.12).
Listing 6.12. Utilisation de mock.patch () pour tester de nombreux comportements
from unittest import mock import pytest import requests class WhereIsPythonError(Exception): pass ❶ def is_python_still_a_programming_language(): try: r = requests.get("http://python.org") except IOError: pass else: if r.status_code == 200: return 'Python is a programming language' in r.content raise WhereIsPythonError("Something bad happened") def get_fake_get(status_code, content): m = mock.Mock() m.status_code = status_code m.content = content def fake_get(url): return m return fake_get def raise_get(url): raise IOError("Unable to fetch url %s" % url) ❷ @mock.patch('requests.get', get_fake_get( 200, 'Python is a programming language for sure')) def test_python_is(): assert is_python_still_a_programming_language() is True @mock.patch('requests.get', get_fake_get( 200, 'Python is no more a programming language')) def test_python_is_not(): assert is_python_still_a_programming_language() is False @mock.patch('requests.get', get_fake_get(404, 'Whatever')) def test_bad_status_code(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language() @mock.patch('requests.get', raise_get) def test_ioerror(): with pytest.raises(WhereIsPythonError): is_python_still_a_programming_language()
Le listing 6.12 implémente un cas de test qui recherche toutes les instances de
Python est une chaîne de langage de programmation sur
python.org ❶. Il n'y a pas d'option où le test ne trouvera aucune ligne donnée sur la page Web sélectionnée. Pour obtenir un résultat négatif, vous devez modifier la page, mais cela ne peut pas être fait. Mais à l'aide de mock, vous pouvez passer à l'astuce et modifier le comportement de la demande afin qu'elle renvoie une réponse fictive avec une page fictive qui ne contient pas de chaîne donnée. Cela vous permettra de tester un scénario négatif dans lequel
python.org ne contient pas de chaîne donnée et de vous assurer que le programme gère correctement un tel cas.
Cet exemple utilise la version décoratrice mock.patch () . Le comportement de l'objet factice ne change pas, et il était plus facile de définir un exemple dans le contexte d'une fonction de test.
L'utilisation d'un objet factice permet de simuler tout problème: le serveur renvoie une erreur 404, une erreur d'E / S ou une erreur de retard réseau. Nous pouvons nous assurer que le code renvoie les valeurs correctes ou lève la bonne exception dans chaque cas, ce qui garantit le comportement attendu du code.
Identifier le code non testé avec la couverture
L'outil de couverture est un excellent ajout aux tests unitaires. [La couverture par code est une mesure utilisée dans les tests. Affiche le pourcentage du code source du programme qui a été exécuté pendant le processus de test -
ed. ], qui trouve des morceaux de code non testés. Il utilise des outils d'analyse et de suivi de code pour identifier les lignes qui ont été exécutées. Lors des tests unitaires, il peut révéler quelles parties du code ont été réutilisées et lesquelles n'ont pas été utilisées du tout. La création de tests est nécessaire, et la possibilité de découvrir quelle partie du code vous avez oublié de couvrir avec des tests rend ce processus plus agréable.
Installez le module de couverture via pip pour pouvoir l'utiliser à travers votre shell.
REMARQUE
La commande peut également être appelée couverture python si le module est installé via le programme d'installation de votre système d'exploitation. Un exemple de ceci est le système d'exploitation Debian.
L'utilisation de la couverture hors ligne est assez simple. Il montre les parties du programme qui ne démarrent jamais et deviennent un "poids mort" - un code tel que vous ne pouvez pas le supprimer sans modifier la fonctionnalité du programme. Tous les outils de test abordés précédemment dans le chapitre sont intégrés à la couverture.
Lorsque vous utilisez pytest, installez le plugin pytest-cov via pip install pytest-pycov et ajoutez quelques commutateurs pour générer une sortie détaillée du code non testé (Listing 6.13).
Listing 6.13. Utilisation de pytest et de la couverture
$ pytest --cov=gnocchiclient gnocchiclient/tests/unit ---------- coverage: platform darwin, python 3.6.4-final-0 ----------- Name Stmts Miss Branch BrPart Cover --------------------------- gnocchiclient/__init__.py 0 0 0 0 100% gnocchiclient/auth.py 51 23 6 0 49% gnocchiclient/benchmark.py 175 175 36 0 0% --snip-- --------------------------- TOTAL 2040 1868 424 6 8% === passed in 5.00 seconds ===
L'option --cov permet la sortie du rapport de couverture à la fin du test. Vous devez passer le nom du package comme argument pour que le plug-in filtre correctement le rapport. La sortie contiendra des lignes de code qui n'ont pas été exécutées, ce qui signifie qu'elles n'ont pas été testées. Il ne vous reste plus qu'à ouvrir l'éditeur et à écrire un test pour ce code.
Le module de couverture est encore meilleur - il vous permet de générer des rapports clairs au format HTML. Ajoutez simplement -–cov-report-html et les pages HTML apparaîtront dans le répertoire
htmlcov à partir duquel vous exécutez la commande. Chaque page montrera quelles parties du code source étaient ou n'étaient pas en cours d'exécution.
Si vous voulez aller encore plus loin, utilisez –-cover-fail-under-COVER_MIN_PERCENTAGE, ce qui entraînera l'échec de la suite de tests si elle ne couvre pas le pourcentage minimum de code. Bien qu'un grand pourcentage de couverture soit un bon objectif et que les outils de test soient utiles pour obtenir des informations sur l'état de la couverture de test, le pourcentage en soi n'est pas très informatif. La figure 6.1 montre un exemple de rapport de couverture montrant le pourcentage de couverture.
Par exemple, couvrir le code avec des tests à 100% est un objectif louable, mais cela ne signifie pas nécessairement que le code est entièrement testé. Cette valeur indique uniquement que toutes les lignes de code du programme sont remplies, mais n'indique pas que toutes les conditions ont été testées.
Il est utile d'utiliser les informations de couverture pour étendre la suite de tests et les créer pour du code qui ne s'exécute pas. Cela simplifie la prise en charge du projet et améliore la qualité globale du code.
À propos de l'auteur
Julien Danju pirate le freeware depuis une vingtaine d'années et développe des programmes Python depuis près de douze ans. Il dirige actuellement l'équipe de conception de la plateforme cloud distribuée basée sur OpenStack, qui possède la plus grande base de données open source Python existante, avec environ deux millions et demi de lignes de code. Avant de développer des services cloud, Julien a créé le gestionnaire de fenêtres et a contribué au développement de nombreux projets, tels que Debian et GNU Emacs.
À propos de Science Editor
Mike Driscoll programme en Python depuis plus d'une décennie. Pendant longtemps, il a écrit sur Python on
The Mouse vs. Le Python . Auteur de plusieurs livres sur Python: Python 101, Python Interviews et ReportLab: PDF Processingwith Python. Vous pouvez trouver Mike sur Twitter et sur GitHub: @driscollis.
»Plus d'informations sur le livre sont disponibles sur
le site Web de l'éditeur»
Contenu»
Extrait25% de réduction sur les colporteurs -
PythonLors du paiement de la version papier du livre, un livre électronique est envoyé par e-mail.