Cours Python en ligne
- pour le proxy pip, utilisez dev au lieu de 1.3.1
- Comment choisir un thème WordPress convivial pour le référencement?
- Une introduction pratique au Web Scraping en Python – Real Python
- Épisode 47: Python dans le développement des polices et des caractères
- Enseigner Python et trouver des ressources pour les étudiants – Le véritable podcast Python
Si vous travaillez à Django, pytest
Les appareils peuvent vous aider à créer des tests pour vos modèles qui ne sont pas compliqués à entretenir. Écrire de bons tests est une étape cruciale pour maintenir une application réussie, et agencements sont un ingrédient clé pour rendre votre suite de tests efficace et efficiente. Les appareils sont de petites données qui servent de référence à vos tests.
Au fur et à mesure que vos scénarios de test changent, il peut être difficile d'ajouter, de modifier et d'entretenir vos appareils. Mais ne vous inquiétez pas. Ce tutoriel vous montrera comment utiliser le pytest-django
plugin pour faire de l'écriture de nouveaux cas de test et de nouveaux appareils un jeu d'enfant.
Dans ce didacticiel, vous apprendrez:
- Comment créer et charger montages d'essai à Django
- Comment créer et charger
pytest
fixtures pour les modèles Django - Comment utiliser des usines pour créer des montages de test pour les modèles Django
pytest
- Comment créer des dépendances entre des appareils de test à l'aide du usine comme luminaire modèle
Les concepts décrits dans ce didacticiel conviennent à tout projet Python utilisant pytest
. Pour plus de commodité, les exemples utilisent l'ORM de Django, mais les résultats peuvent être reproduits dans d'autres types d'ORM et même dans des projets qui n'utilisent pas d'ORM ou de base de données.
Calendrier à Django
Pour commencer, vous allez mettre en place un nouveau projet Django. Tout au long de ce didacticiel, vous écrirez des tests à l'aide du module d'authentification intégré.
Configuration d'un environnement virtuel Python
Lorsque vous créez un nouveau projet, il est préférable de créer également un environnement virtuel pour celui-ci. Un environnement virtuel vous permet d'isoler le projet des autres projets sur votre ordinateur. De cette façon, différents projets peuvent utiliser différentes versions de Python, Django ou tout autre package sans interférer les uns avec les autres.
Voici comment créer votre environnement virtuel dans un nouveau répertoire:
$ mkdir django_fixtures
$ CD django_fixtures
django_fixtures $ python -m venv venv
Pour obtenir des instructions détaillées sur la façon de créer un environnement virtuel, consultez Python Virtual Environments: A Primer.
L'exécution de cette commande créera un nouveau répertoire appelé venv
. Ce répertoire stockera tous les packages que vous installez dans l'environnement virtuel.
Configuration d'un projet Django
Maintenant que vous avez un nouvel environnement virtuel, il est temps de configurer un projet Django. Dans votre terminal, activez l'environnement virtuel et installez Django:
$ la source venv / bin / activate
$ installer pip django
Maintenant que Django est installé, vous pouvez créer un nouveau projet Django appelé django_fixtures
:
$ django-admin startproject django_fixtures
Après avoir exécuté cette commande, vous verrez que Django a créé de nouveaux fichiers et répertoires. Pour en savoir plus sur la façon de démarrer un nouveau projet Django, consultez Démarrage d'un projet Django.
Pour terminer la configuration de votre projet Django, appliquez le migrations pour les modules intégrés:
$ CD django_fixtures
$ migration de python manage.py
Opérations à effectuer:
Appliquer toutes les migrations: admin, auth, contenttypes, sessions
Exécution de migrations:
Application de contenttypes.0001_initial ... OK
Appliquer auth.0001_initial ... OK
Application de admin.0001_initial ... OK
Application de admin.0002_logentry_remove_auto_add ... OK
Application de admin.0003_logentry_add_action_flag_choices ... OK
Application de contenttypes.0002_remove_content_type_name ... OK
Appliquer auth.0002_alter_permission_name_max_length ... OK
Appliquer auth.0003_alter_user_email_max_length ... OK
Appliquer auth.0004_alter_user_username_opts ... OK
Appliquer auth.0005_alter_user_last_login_null ... OK
Appliquer auth.0006_require_contenttypes_0002 ... OK
Application de auth.0007_alter_validators_add_error_messages ... OK
Appliquer auth.0008_alter_user_username_max_length ... OK
Appliquer auth.0009_alter_user_last_name_max_length ... OK
Appliquer auth.0010_alter_group_name_max_length ... OK
Application d'auth.0011_update_proxy_permissions ... OK
Application de sessions.0001_initial ... OK
La sortie répertorie toutes les migrations appliquées par Django. Lors du démarrage d'un nouveau projet, Django applique des migrations pour les applications intégrées telles que auth
, séances
, et administrateur
.
Vous êtes maintenant prêt à commencer à écrire des tests et des fixtures!
Création d'appareils Django
Django fournit sa propre façon de créer et de charger des appareils pour les modèles à partir de fichiers. Les fichiers des appareils Django peuvent être écrits en JSON ou YAML. Dans ce didacticiel, vous allez travailler avec le format JSON.
La façon la plus simple de créer un appareil Django est d'utiliser un objet existant. Démarrez un shell Django:
$ shell python manage.py
Python 3.8.0 (par défaut, 23 octobre 2019, 18:51:26)
[GCC 9.2.0] sous linux
Tapez "aide", "copyright", "crédits" ou "licence" pour plus d'informations.
(InteractiveConsole)
A l'intérieur du shell Django, créez un nouveau groupe appelé appuseurs
:
>>> de django.contrib.auth.models importation Groupe
>>> groupe = Groupe.objets.créer(Nom="appusers")
>>> groupe.pk
1
le Groupe
Le modèle fait partie du système d'authentification de Django. Les groupes sont très utiles pour gérer les autorisations dans un projet Django.
Vous avez créé un nouveau groupe appelé appuseurs
. le clé primaire du groupe que vous venez de créer est 1
. Pour créer un appareil pour le groupe appuseurs
, vous allez utiliser la commande de gestion Django dumpdata
.
Quittez le shell Django avec sortie()
et exécutez la commande suivante depuis votre terminal:
$ python manage.py dumpdata auth.Group --pk 1 --indent 4 > group.json
Dans cet exemple, vous utilisez le dumpdata
pour générer des fichiers de luminaires à partir d'instances de modèle existantes. Décomposons-le:
-
groupe d'authentification
: Décrit le modèle à vider. Le format est
.. -
--pk 1
: Décrit l'objet à vider. La valeur est une liste de clés primaires séparées par des virgules, telles que1,2,3
. -
--indent 4
: Il s'agit d'un argument de formatage facultatif qui indique à Django le nombre d'espaces à ajouter avant chaque niveau d'indentation dans le fichier généré. L'utilisation d'indentations rend le fichier d'installation plus lisible. -
> group.json
: Décrit où écrire la sortie de la commande. Dans ce cas, la sortie sera écrite dans un fichier appelégroup.json
.
Ensuite, inspectez le contenu du fichier de fixture group.json
:
[[[[
"modèle": "auth.group",
"pk": 1,
"des champs":
"Nom": "appusers",
"autorisations": []
]
Le fichier fixture contient une liste d'objets. Dans ce cas, vous n'avez qu'un seul objet dans la liste. Chaque objet comprend un entête avec le nom du modèle et la clé primaire, ainsi qu'un dictionnaire avec la valeur de chaque champ du modèle. Vous pouvez voir que le luminaire contient le nom du groupe appuseurs
.
Vous pouvez créer et éditer des fichiers de fixations manuellement, mais il est généralement plus pratique de créer l’objet au préalable et d’utiliser Django dumpdata
pour créer le fichier de fixture.
Chargement des appareils Django
Maintenant que vous avez un fichier fixture, vous voulez le charger dans la base de données. Mais avant de faire cela, vous devez ouvrir un shell Django et supprimer le groupe que vous avez déjà créé:
>>> de django.contrib.auth.models importation Groupe
>>> Groupe.objets.filtre(pk=1).supprimer()
(1, 'auth.Group_permissions': 0, 'auth.User_groups': 0, 'auth.Group': 1)
Maintenant que le groupe est supprimé, chargez le projecteur à l'aide du données de charge
commander:
$ python manage.py loaddata group.json
Installé 1 objet (s) à partir de 1 luminaire (s)
Pour vous assurer que le nouveau groupe a été chargé, ouvrez un shell Django et récupérez-le:
>>> de django.contrib.auth.models importation Groupe
>>> groupe = Groupe.objets.avoir(pk=1)
>>> vars(groupe)
'_Etat': ,
'id': 1,
'name': 'appusers'
Génial! Le groupe était chargé. Vous venez de créer et de charger votre premier appareil Django.
Chargement des appareils Django dans les tests
Jusqu'à présent, vous avez créé et chargé un fichier de luminaire à partir de la ligne de commande. Maintenant, comment pouvez-vous l'utiliser pour les tests? Pour voir comment les appareils sont utilisés dans les tests Django, créez un nouveau fichier appelé test.py
et ajoutez le test suivant:
de django.test importation Cas de test
de django.contrib.auth.models importation Groupe
classe Mon test(Cas de test):
def test_should_create_group(soi):
groupe = Groupe.objets.avoir(pk=1)
soi.assertEqual(groupe.Nom, "appusers")
Le test récupère le groupe avec la clé primaire 1
et tester que son nom est appuseurs
.
Exécutez le test depuis votre terminal:
$ python manage.py tester tester
Création d'une base de données de test pour l'alias 'par défaut' ...
La vérification du système n'a identifié aucun problème (0 désactivé).
E
================================================== ====================
ERREUR: test_should_create_group (test.MyTest)
-------------------------------------------------- --------------------
Traceback (dernier appel le plus récent):
Fichier "/django_fixtures/django_fixtures/test.py", ligne 9, dans test_should_create_group
group = Group.objects.get (pk = 1)
Fichier "/django_fixtures/venv/lib/python3.8/site-packages/django/db/models/manager.py", ligne 82, dans manager_method
return getattr (self.get_queryset (), name) (* args, ** kwargs)
Fichier "/django_fixtures/venv/lib/python3.8/site-packages/django/db/models/query.py", ligne 415, dans get
augmenter self.model.DoesNotExist (
django.contrib.auth.models.Group.DoesNotExist: la requête de correspondance de groupe n'existe pas.
-------------------------------------------------- --------------------
Test Ran 1 en 0,001s
ÉCHEC (erreurs = 1)
Détruire la base de données de test pour l'alias 'par défaut' ...
Le test a échoué car un groupe avec la clé primaire 1
n'existe pas.
Pour charger le luminaire dans le test, vous pouvez utiliser un attribut spécial de la classe Cas de test
appelé agencements
:
de django.test importation Cas de test
de django.contrib.auth.models importation Groupe
classe Mon test(Cas de test):
agencements = [[[["group.json"]
def test_should_create_group(soi):
groupe = Groupe.objets.avoir(pk=1)
soi.assertEqual(groupe.Nom, "appusers")
Ajout de cet attribut à un Cas de test
indique à Django de charger les appareils avant d'exécuter chaque test. Remarquerez que agencements
accepte un tableau, vous pouvez donc fournir plusieurs fichiers de luminaire à charger avant chaque test.
L'exécution du test produit désormais la sortie suivante:
$ python manage.py tester tester
Création d'une base de données de test pour l'alias 'par défaut' ...
La vérification du système n'a identifié aucun problème (0 désactivé).
.
-------------------------------------------------- --------------------
Test Ran 1 en 0,005 s
D'accord
Détruire la base de données de test pour l'alias 'par défaut' ...
Incroyable! Le groupe a été chargé et le test a réussi. Vous pouvez maintenant utiliser le groupe appuseurs
dans vos tests.
Référencement des objets associés dans les appareils Django
Jusqu'à présent, vous avez utilisé un seul fichier avec un seul objet. Cependant, la plupart du temps, votre application comporte de nombreux modèles et vous aurez besoin de plusieurs modèles dans un test.
Pour voir à quoi ressemblent les dépendances entre les objets dans les appareils Django, créez une nouvelle instance utilisateur, puis ajoutez-la à la appuseurs
groupe que vous avez créé auparavant:
>>> de django.contrib.auth.models importation Utilisateur, Groupe
>>> appuseurs = Groupe.objets.avoir(Nom="appusers")
>>> haki = Utilisateur.objets.Créer un utilisateur("haki")
>>> haki.pk
1
>>> haki.groupes.ajouter(appuseurs)
L'utilisateur haki
est maintenant membre du appuseurs
groupe. Pour voir à quoi ressemble un appareil avec une clé étrangère, générez un appareil pour l'utilisateur 1
:
$ python manage.py dumpdata auth.User --pk 1 --indent 4
[[[[
"model": "auth.user",
"pk": 1,
"des champs":
"mot de passe": "! M4dygH3ZWfd0214U59OR9nlwsRJ94HUZtvQciG8y",
"last_login": null,
"is_superuser": false,
"nom d'utilisateur": "haki",
"Prénom": "",
"nom de famille": "",
"email": "",
"is_staff": false,
"is_active": vrai,
"date_joined": "2019-12-07T09: 32: 50.998Z",
"groupes":[[[[
1
],
"user_permissions": []
]
La structure du luminaire est similaire à celle que vous avez vue précédemment.
Un utilisateur peut être associé à plusieurs groupes, de sorte que le champ groupe
contient les ID de tous les groupes auxquels appartient l'utilisateur. Dans ce cas, l'utilisateur appartient au groupe avec la clé primaire 1
, qui est votre appuseurs
groupe.
L'utilisation de clés primaires pour référencer des objets dans des appareils n'est pas toujours une bonne idée. La clé primaire d'un groupe est un identifiant arbitraire que la base de données attribue au groupe lors de sa création. Dans un autre environnement ou sur un autre ordinateur, le appuseurs
Le groupe peut avoir un ID différent et cela ne fera aucune différence sur l'objet.
Pour éviter d'utiliser des identifiants arbitraires, Django définit le concept de clés naturelles. Une clé naturelle est un identifiant unique d'un objet qui n'est pas nécessairement la clé primaire. Dans le cas des groupes, deux groupes ne peuvent pas avoir le même nom, donc une clé naturelle pour le groupe peut être son nom.
Pour utiliser des clés naturelles au lieu de clés primaires pour référencer des objets associés dans un appareil Django, ajoutez le - naturel-étranger
drapeau au dumpdata
commander:
$ python manage.py dumpdata auth.User --pk 1 --indent 4 - naturel-étranger
[[[[
"model": "auth.user",
"pk": 1,
"des champs":
"mot de passe": "! f4dygH3ZWfd0214X59OR9ndwsRJ94HUZ6vQciG8y",
"last_login": null,
"is_superuser": false,
"nom d'utilisateur": "haki",
"Prénom": "",
"nom de famille": "",
"email": "benita",
"is_staff": false,
"is_active": vrai,
"date_joined": "2019-12-07T09: 32: 50.998Z",
"groupes":[[[[
[[[[
`appusers`
]
],
"user_permissions": []
]
Django a généré le fixture pour l'utilisateur, mais au lieu d'utiliser la clé primaire du appuseurs
groupe, il a utilisé le nom du groupe.
Vous pouvez également ajouter le - naturel-primaire
pour exclure la clé primaire d'un objet du luminaire. Quand pk
est null, la clé primaire sera définie lors de l'exécution, généralement par la base de données.
Maintenance des appareils Django
Les appareils Django sont excellents, mais ils posent également certains défis:
-
Garder les appareils mis à jour: Les appareils Django doivent contenir tous les champs obligatoires du modèle. Si vous ajoutez un nouveau champ qui ne peut pas être annulé, vous devez mettre à jour les appareils. Sinon, leur chargement échouera. Garder les appareils Django à jour peut devenir un fardeau quand vous en avez beaucoup.
-
Maintenir les dépendances entre les appareils: Les appareils Django qui dépendent d'autres appareils doivent être chargés ensemble et dans un ordre particulier. Rester à jour avec les nouveaux cas de test ajoutés et les anciens cas de test modifiés peut être difficile.
Pour ces raisons, les luminaires Django ne sont pas un choix idéal pour les modèles qui changent souvent. Par exemple, il serait très difficile de gérer les appareils Django pour les modèles qui sont utilisés pour représenter les objets principaux dans l'application tels que les ventes, les commandes, les transactions ou les réservations.
D'autre part, les appareils Django sont une excellente option pour les cas d'utilisation suivants:
-
Données constantes: Cela s'applique aux modèles qui changent rarement, tels que les codes de pays et les codes postaux.
-
Donnée initiale: Cela s'applique aux modèles qui stockent les données de recherche de votre application, telles que les catégories de produits, les groupes d'utilisateurs et les types d'utilisateurs.
pytest
Calendrier à Django
Dans la section précédente, vous avez utilisé les outils intégrés fournis par Django pour créer et charger des appareils. Les luminaires fournis par Django sont parfaits pour certains cas d'utilisation, mais pas idéaux pour d'autres.
Dans cette section, vous allez expérimenter avec un type de luminaire très différent: le pytest
fixation. pytest
fournit un système de fixation très complet que vous pouvez utiliser pour créer une suite de tests fiable et maintenable.
Configuration pytest
pour un projet Django
Pour commencer pytest
, vous devez d'abord installer pytest
et le plugin Django pour pytest
. Exécutez les commandes suivantes dans votre terminal lorsque l'environnement virtuel est activé:
$ pip installer pytest
$ pip installer pytest-django
le pytest-django
le plugin est maintenu par le pytest
équipe de développement. Il fournit des outils utiles pour écrire des tests pour les projets Django en utilisant pytest
.
Ensuite, vous devez laisser pytest
savoir où il peut trouver les paramètres de votre projet Django. Créez un nouveau fichier dans le répertoire racine du projet appelé pytest.ini
et ajoutez-y les lignes suivantes:
[pytest]
DJANGO_SETTINGS_MODULE=django_fixtures.settings
Ceci est la quantité minimale de configuration nécessaire pour faire pytest
travailler avec votre projet Django. Il existe de nombreuses autres options de configuration, mais cela suffit pour commencer.
Enfin, pour tester votre configuration, remplacez le contenu de test.py
avec ce test factice:
def test_foo():
affirmer Vrai
Pour exécuter le test factice, utilisez le pytest
commande depuis votre terminal:
$ pytest test.py
============================== la session de test démarre ================== =====
plateforme Linux - Python 3.7.4, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
Paramètres Django: django_fixtures.settings (à partir du fichier ini)
rootdir: / django_fixtures, inifile: pytest.ini
plugins: django-3.5.1
test.py.
[100%]
============================= 1 passé en 0,05 s ================= =========
Vous venez de terminer la configuration d'un nouveau projet Django avec pytest
! Vous êtes maintenant prêt à creuser plus profondément.
Pour en savoir plus sur la configuration pytest
et écrire des tests, découvrez le développement piloté par les tests avec pytest
.
Accès à la base de données à partir de tests
Dans cette section, vous allez écrire des tests à l'aide du module d'authentification intégré django.contrib.auth
. Les modèles les plus connus de ce module sont Utilisateur
et Groupe
.
Pour commencer avec Django et pytest
, écrivez un test pour vérifier si la fonction Créer un utilisateur()
fourni par Django définit correctement le nom d'utilisateur:
de django.contrib.auth.models importation Utilisateur
def test_should_create_user_with_username() -> Aucun:
utilisateur = Utilisateur.objets.Créer un utilisateur("Haki")
affirmer utilisateur.Nom d'utilisateur == "Haki"
Maintenant, essayez d'exécuter le test à partir de votre commande comme:
$ pytest test.py
================================== la session de test démarre ============= ==
plateforme Linux - Python 3.7.4, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
Paramètres Django: django_fixtures.settings (à partir du fichier ini)
rootdir: / django-django_fixtures / django_fixtures, inifile: pytest.ini
plugins: django-3.5.1
collecté 1 article
test.py F
=============================== FAILURES ================== ===========
____________________test_should_create_user_with_username ____________
def test_should_create_user_with_username () -> Aucun:
> utilisateur = User.objects.create_user("Haki")
soi = , nom = Aucun
def _cursor (self, name = None):
> self.ensure_connection()
E Échec: l'accès à la base de données n'est pas autorisé, utilisez la marque "django_db" ou la "db"
ou des appareils "transactional_db" pour l'activer.
La commande a échoué et le test ne s'est pas exécuté. Le message d'erreur vous donne quelques informations utiles: Pour accéder à la base de données dans un test, vous devez injecter un appareil spécial appelé db
. le db
luminaire fait partie de la django-pytest
plug-in que vous avez installé précédemment, et il est nécessaire d'accéder à la base de données dans les tests.
Injectez le db
montage dans le test:
de django.contrib.auth.models importation Utilisateur
def test_should_create_user_with_username(db) -> Aucun:
utilisateur = Utilisateur.objets.Créer un utilisateur("Haki")
affirmer utilisateur.Nom d'utilisateur == "Haki"
Relancez le test:
$ pytest test.py
================================== la session de test démarre ============= ==
plateforme Linux - Python 3.7.4, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
Paramètres Django: django_fixtures.settings (à partir du fichier ini)
rootdir: / django_fixtures, inifile: pytest.ini
plugins: django-3.5.1
collecté 1 article
test.py.
Génial! La commande s'est terminée avec succès et votre test a réussi. Vous savez maintenant comment accéder à la base de données dans les tests. Vous avez également injecté un appareil dans un scénario de test en cours de route.
Création d'appareils pour les modèles Django
Maintenant que vous connaissez Django et pytest
, écrivez un test pour vérifier qu'un mot de passe défini avec set_password ()
est validé comme prévu. Remplacez le contenu de test.py
avec ce test:
de django.contrib.auth.models importation Utilisateur
def test_should_check_password(db) -> Aucun:
utilisateur = Utilisateur.objets.Créer un utilisateur("UNE")
utilisateur.set_password("secret")
affirmer utilisateur.check_password("secret") est Vrai
def test_should_not_check_unusable_password(db) -> Aucun:
utilisateur = Utilisateur.objets.Créer un utilisateur("UNE")
utilisateur.set_password("secret")
utilisateur.set_unusable_password()
affirmer utilisateur.check_password("secret") est Faux
Le premier test vérifie qu'un utilisateur avec un mot de passe utilisable est en cours de validation par Django. Le deuxième test vérifie un cas limite dans lequel le mot de passe de l'utilisateur est inutilisable et ne doit pas être validé par Django.
Il y a une distinction importante à faire ici: les cas de test ci-dessus ne testent pas Créer un utilisateur()
. Ils testent set_password ()
. Cela signifie un changement Créer un utilisateur()
ne devrait pas affecter ces cas de test.
Notez également que le Utilisateur
l'instance est créée deux fois, une fois pour chaque cas de test. Un grand projet peut avoir de nombreux tests qui nécessitent un Utilisateur
exemple. Si chaque scénario de test crée son propre utilisateur, vous pourriez avoir des problèmes à l'avenir si le Utilisateur
changements de modèle.
Pour réutiliser un objet dans de nombreux cas de test, vous pouvez créer un appareil de test:
importation pytest
de django.contrib.auth.models importation Utilisateur
@pytest.fixation
def user_A(db) -> Utilisateur:
revenir Utilisateur.objets.Créer un utilisateur("UNE")
def test_should_check_password(db, user_A: Utilisateur) -> Aucun:
user_A.set_password("secret")
affirmer user_A.check_password("secret") est Vrai
def test_should_not_check_unusable_password(db, user_A: Utilisateur) -> Aucun:
user_A.set_password("secret")
user_A.set_unusable_password()
affirmer user_A.check_password("secret") est Faux
Dans le code ci-dessus, vous avez créé une fonction appelée user_A ()
qui crée et renvoie un nouveau Utilisateur
exemple. Pour marquer la fonction en tant que luminaire, vous l'avez décorée avec le pytest.fixture
décorateur. Une fois qu'une fonction est marquée comme un appareil, elle peut être injectée dans des cas de test. Dans ce cas, vous avez injecté le luminaire user_A
en deux cas de test.
Entretien des appareils lorsque les exigences changent
Supposons que vous ayez ajouté une nouvelle exigence à votre application et que chaque utilisateur doit désormais appartenir à un "app_user"
groupe. Les utilisateurs de ce groupe peuvent afficher et mettre à jour leurs propres informations personnelles. Pour tester votre application, vous devez que vos utilisateurs de test appartiennent à "app_user"
groupe aussi:
importation pytest
de django.contrib.auth.models importation Utilisateur, Groupe, Autorisation
@pytest.fixation
def user_A(db) -> Groupe:
groupe = Groupe.objets.créer(Nom="app_user")
change_user_permissions = Autorisation.objets.filtre(
nom de code__in=[[[["changer d'utilisateur", "view_user"],
)
groupe.autorisations.ajouter(*change_user_permissions)
utilisateur = Utilisateur.objets.Créer un utilisateur("UNE")
utilisateur.groupes.ajouter(groupe)
revenir utilisateur
def test_should_create_user(user_A: Utilisateur) -> Aucun:
affirmer user_A.Nom d'utilisateur == "UNE"
def test_user_is_in_app_user_group(user_A: Utilisateur) -> Aucun:
affirmer user_A.groupes.filtre(Nom="app_user").existe()
A l'intérieur du luminaire, vous avez créé le groupe "app_user"
et a ajouté le changer d'utilisateur
et view_user
autorisations. Vous avez ensuite créé l'utilisateur test et l'avez ajouté au "app_user"
groupe.
Auparavant, vous deviez parcourir chaque scénario de test qui avait créé un utilisateur et l'ajouter au groupe. En utilisant des appareils, vous avez pu effectuer le changement une seule fois. Une fois que vous avez changé le luminaire, le même changement est apparu dans chaque cas de test que vous avez injecté user_A
dans. En utilisant des appareils, vous pouvez éviter les répétitions et rendre vos tests plus faciles à maintenir.
Injection d'appareils dans d'autres appareils
Les grandes applications ont généralement plus d'un seul utilisateur, et il est souvent nécessaire de les tester avec plusieurs utilisateurs. Dans cette situation, vous pouvez ajouter un autre appareil pour créer le test user_B
:
importation pytest
de django.contrib.auth.models importation Utilisateur, Groupe, Autorisation
@pytest.fixation
def user_A(db) -> Utilisateur:
groupe = Groupe.objets.créer(Nom="app_user")
change_user_permissions = Autorisation.objets.filtre(
nom de code__in=[[[["changer d'utilisateur", "view_user"],
)
groupe.autorisations.ajouter(*change_user_permissions)
utilisateur = Utilisateur.objets.Créer un utilisateur("UNE")
utilisateur.groupes.ajouter(groupe)
revenir utilisateur
@pytest.fixation
def user_B(db) -> Utilisateur:
groupe = Groupe.objets.créer(Nom="app_user")
change_user_permissions = Autorisation.objets.filtre(
nom de code__in=[[[["changer d'utilisateur", "view_user"],
)
groupe.autorisations.ajouter(*change_user_permissions)
utilisateur = Utilisateur.objets.Créer un utilisateur("B")
utilisateur.groupes.ajouter(groupe)
revenir utilisateur
def test_should_create_two_users(user_A: Utilisateur, user_B: Utilisateur) -> Aucun:
affirmer user_A.pk ! = user_B.pk
Dans votre terminal, essayez d'exécuter le test:
$ pytest test.py
==================== la session de test démarre =========================== ======
plateforme Linux - Python 3.7.4, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
Paramètres Django: django_fixtures.settings (à partir du fichier ini)
rootdir: / django_fixtures, inifile: pytest.ini
plugins: django-3.5.1
collecté 1 article
test.py E
[100%]
============================= ERREURS ===================== ==================
_____________ ERREUR lors de la configuration de test_should_create_two_users ______________
soi = ,
sql = 'INSERT INTO "auth_group" ("name") VALUES (% s) RETURNING "auth_group". "id"'
, params = ('app_user',)
def _execute (self, sql, params, * ignored_wrapper_args):
self.db.validate_no_broken_transaction ()
avec self.db.wrap_database_errors:
si params est Aucun:
# params default peut être spécifique au backend.
return self.cursor.execute (sql)
autre:
> revenir self.cursor.execute(sql, params)
E psycopg2.IntegrityError: la valeur de la clé en double est violée
contrainte unique "auth_group_name_key"
E DÉTAIL: La clé (nom) = (utilisateur_app) existe déjà.
======================== 1 erreur dans 4.14s ====================== ===========
Le nouveau test jette un IntegrityError
. Le message d'erreur provient de la base de données, il peut donc sembler un peu différent selon la base de données que vous utilisez. Selon le message d'erreur, le test viole la contrainte unique sur le nom du groupe. Lorsque vous regardez vos appareils, cela a du sens. le "app_user"
le groupe est créé deux fois, une fois dans le luminaire user_A
et encore une fois dans le luminaire user_B
.
Une observation intéressante que nous avons ignorée jusqu'à présent est que le luminaire user_A
utilise le luminaire db
. Cela signifie que les luminaires peuvent être injectés dans d'autres luminaires. Vous pouvez utiliser cette fonction pour IntegrityError
au dessus de. Créez le "app_user"
groupe une seule fois dans un appareil, et l'injecter à la fois dans le user_A
et user_B
agencements.
Pour ce faire, refactorisez votre test et ajoutez un "utilisateur de l'application"
rencontre de groupe:
importation pytest
de django.contrib.auth.models importation Utilisateur, Groupe, Autorisation
@pytest.fixation
def app_user_group(db) -> Groupe:
groupe = Groupe.objets.créer(Nom="app_user")
change_user_permissions = Autorisation.objets.filtre(
nom de code__in=[[[["changer d'utilisateur", "view_user"],
)
groupe.autorisations.ajouter(*change_user_permissions)
revenir groupe
@pytest.fixation
def user_A(db, app_user_group: Groupe) -> Utilisateur:
utilisateur = Utilisateur.objets.Créer un utilisateur("UNE")
utilisateur.groupes.ajouter(app_user_group)
revenir utilisateur
@pytest.fixation
def user_B(db, app_user_group: Groupe) -> Utilisateur:
utilisateur = Utilisateur.objets.Créer un utilisateur("B")
utilisateur.groupes.ajouter(app_user_group)
revenir utilisateur
def test_should_create_two_users(user_A: Utilisateur, user_B: Utilisateur) -> Aucun:
affirmer user_A.pk ! = user_B.pk
Dans votre terminal, exécutez vos tests:
$ pytest test.py
================================== la session de test démarre ============= ==
plateforme Linux - Python 3.7.4, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
Paramètres Django: django_fixtures.settings (à partir du fichier ini)
rootdir: / django_fixtures, inifile: pytest.ini
plugins: django-3.5.1
collecté 1 article
test.py.
Incroyable! Vos tests réussissent. L'appareil de groupe résume la logique liée à la "utilisateur de l'application"
groupe, comme la définition des autorisations. Vous avez ensuite injecté le groupe dans deux appareils utilisateur distincts. En construisant vos appareils de cette façon, vous avez rendu vos tests moins compliqués à lire et à maintenir.
Utiliser une usine
Jusqu'à présent, vous avez créé des objets avec très peu d'arguments. Cependant, certains objets peuvent être plus compliqués, comportant de nombreux arguments avec de nombreuses valeurs possibles. Pour de tels objets, vous souhaiterez peut-être créer plusieurs montages de test.
Par exemple, si vous fournissez tous les arguments à Créer un utilisateur()
, voici à quoi ressemblerait le luminaire:
importation pytest
de django.contrib.auth.models importation Utilisateur
@pytest.fixation
def user_A(db, app_user_group: Groupe) -> Utilisateur
utilisateur = Utilisateur.objets.Créer un utilisateur(
Nom d'utilisateur="UNE",
mot de passe="secret",
Prénom="haki",
nom de famille="benita",
email="me@hakibenita.com",
is_staff=Faux,
is_superuser=Faux,
c'est actif=Vrai,
)
utilisateur.groupes.ajouter(app_user_group)
revenir utilisateur
Votre appareil est devenu beaucoup plus compliqué! Une instance d'utilisateur peut maintenant avoir de nombreuses variantes différentes, telles que superutilisateur, utilisateur personnel, utilisateur personnel inactif et utilisateur régulier inactif.
Dans les sections précédentes, vous avez appris qu'il peut être difficile de maintenir une logique de configuration compliquée dans chaque appareil de test. Donc, pour éviter d'avoir à répéter toutes les valeurs à chaque fois que vous créez un utilisateur, ajoutez une fonction qui utilise Créer un utilisateur()
pour créer un utilisateur en fonction des besoins spécifiques de votre application:
de dactylographie importation liste, Optionnel
de django.contrib.auth.models importation Utilisateur, Groupe
def create_app_user(
Nom d'utilisateur: str,
mot de passe: Optionnel[[[[str] = Aucun,
Prénom: Optionnel[[[[str] = "Prénom",
nom de famille: Optionnel[[[[str] = "nom de famille",
email: Optionnel[[[[str] = "foo@bar.com",
is_staff: str = Faux,
is_superuser: str = Faux,
c'est actif: str = Vrai,
groupes: liste[[[[Groupe] = [],
) -> Utilisateur:
utilisateur = Utilisateur.objets.Créer un utilisateur(
Nom d'utilisateur=Nom d'utilisateur,
mot de passe=mot de passe,
Prénom=Prénom,
nom de famille=nom de famille,
email=email,
is_staff=is_staff,
is_superuser=is_superuser,
c'est actif=c'est actif,
)
utilisateur.groupes.ajouter(*groupes)
revenir utilisateur
La fonction crée un utilisateur d'application. Chaque argument est défini avec une valeur par défaut sensible en fonction des besoins spécifiques de votre application. Par exemple, votre application peut exiger que chaque utilisateur ait une adresse e-mail, mais la fonction intégrée de Django n'applique pas une telle restriction. Vous pouvez à la place appliquer cette exigence dans votre fonction.
Les fonctions et classes qui créent des objets sont souvent appelées des usines. Pourquoi? C'est parce que ces fonctions agissent comme des usines qui produisent des instances d'une classe spécifique. Pour en savoir plus sur les usines en Python, consultez Le modèle de méthode d'usine et son implémentation en Python.
La fonction ci-dessus est une implémentation simple d'une usine. Il ne détient aucun état et ne met en œuvre aucune logique compliquée. Vous pouvez refactoriser vos tests afin qu'ils utilisent la fonction d'usine pour créer des instances utilisateur dans vos appareils:
@pytest.fixation
def user_A(db, app_user_group: Group) -> Utilisateur:
revenir create_user(username="A", groups=[[[[app_user_group])
@pytest.fixture
def user_B(db, app_user_group: Group) -> Utilisateur:
revenir create_user(username="B", groups=[[[[app_user_group])
def test_should_create_user(user_A: Utilisateur, app_user_group: Group) -> Aucun:
assert user_A.username == "A"
assert user_A.email == "foo@bar.com"
assert user_A.groups.filter(pk=app_user_group.pk).existe()
def test_should_create_two_users(user_A: Utilisateur, user_B: Utilisateur) -> Aucun:
assert user_A.pk != user_B.pk
Your fixtures got shorter, and your tests are now more resilient to change. For example, if you used a custom user model and you just added a new field to the model, you would only need to change create_user()
for your tests to work as expected.
Using Factories as Fixtures
Complicated setup logic makes it harder to write and maintain tests, making the entire suite fragile and less resilient to change. So far, you’ve addressed this issue by creating fixtures, creating dependencies between fixtures, and using a factory to abstract as much of the setup logic as possible.
But there is still some setup logic left in your test fixtures:
@pytest.fixture
def user_A(db, app_user_group: Group) -> Utilisateur:
revenir create_user(username="A", groups=[[[[app_user_group])
@pytest.fixture
def user_B(db, app_user_group: Group) -> Utilisateur:
revenir create_user(username="B", groups=[[[[app_user_group])
Both fixtures are injected with app_user_group
. This is currently necessary because the factory function create_user()
does not have access to the app_user_group
fixture. Having this setup logic in each test makes it harder to make changes, and it’s more likely to be overlooked in future tests. Instead, you want to encapsulate the entire process of creating a user and abstract it from the tests. This way, you can focus on the scenario at hand rather than setting up unique test data.
To provide the user factory with access to the app_user_group
fixture, you can use a pattern called factory as fixture:
de typing import liste, Optionnel
import pytest
de django.contrib.auth.models import Utilisateur, Group, Autorisation
@pytest.fixture
def app_user_group(db) -> Group:
group = Group.objets.create(name="app_user")
change_user_permissions = Autorisation.objets.filter(
codename__in=[[[["change_user", "view_user"],
)
group.autorisations.ajouter(*change_user_permissions)
revenir group
@pytest.fixture
def app_user_factory(db, app_user_group: Group):
# Closure
def create_app_user(
username: str,
mot de passe: Optionnel[[[[str] = Aucun,
first_name: Optionnel[[[[str] = "first name",
last_name: Optionnel[[[[str] = "last name",
email: Optionnel[[[[str] = "foo@bar.com",
is_staff: str = False,
is_superuser: str = False,
is_active: str = Vrai,
groups: liste[[[[Group] = [],
) -> Utilisateur:
user = Utilisateur.objets.create_user(
username=username,
mot de passe=mot de passe,
first_name=first_name,
last_name=last_name,
email=email,
is_staff=is_staff,
is_superuser=is_superuser,
is_active=is_active,
)
user.groups.ajouter(app_user_group)
# Add additional groups, if provided.
user.groups.ajouter(*groups)
revenir user
revenir create_app_user
This is not far from what you’ve already done, so let’s break it down:
-
le
app_user_group
fixture remains the same. It creates the special"app user"
group with all the necessary permissions. -
A new fixture called
app_user_factory
is added, and it is injected with theapp_user_group
fixture. -
The fixture
app_user_factory
creates a closure and returns an inner function calledcreate_app_user()
. -
create_app_user()
is similar to the function you previously implemented, but now it has access to the fixtureapp_user_group
. With access to the group, you can now add users toapp_user_group
in the factory function.
To use the app_user_factory
fixture, inject it into another fixture and use it to create a user instance:
@pytest.fixture
def user_A(db, app_user_factory) -> Utilisateur:
revenir app_user_factory("A")
@pytest.fixture
def user_B(db, app_user_factory) -> Utilisateur:
revenir app_user_factory("B")
def test_should_create_user_in_app_user_group(
user_A: Utilisateur,
app_user_group: Group,
) -> Aucun:
assert user_A.groups.filter(pk=app_user_group.pk).existe()
def test_should_create_two_users(user_A: Utilisateur, user_B: Utilisateur) -> Aucun:
assert user_A.pk != user_B.pk
Notice that, unlike before, the fixture you created is providing a une fonction rather than an object. This is the main concept behind the factory as fixture pattern: The factory fixture creates a closure, which provides the inner function with access to fixtures.
For more about closures in Python, check out Python Inner Functions — What Are They Good For?
Now that you have your factories and fixtures, this is the complete code for your test:
de typing import liste, Optionnel
import pytest
de django.contrib.auth.models import Utilisateur, Group, Autorisation
@pytest.fixture
def app_user_group(db) -> Group:
group = Group.objets.create(name="app_user")
change_user_permissions = Autorisation.objets.filter(
codename__in=[[[["change_user", "view_user"],
)
group.autorisations.ajouter(*change_user_permissions)
revenir group
@pytest.fixture
def app_user_factory(db, app_user_group: Group):
# Closure
def create_app_user(
username: str,
mot de passe: Optionnel[[[[str] = Aucun,
first_name: Optionnel[[[[str] = "first name",
last_name: Optionnel[[[[str] = "last name",
email: Optionnel[[[[str] = "foo@bar.com",
is_staff: str = False,
is_superuser: str = False,
is_active: str = Vrai,
groups: liste[[[[Group] = [],
) -> Utilisateur:
user = Utilisateur.objets.create_user(
username=username,
mot de passe=mot de passe,
first_name=first_name,
last_name=last_name,
email=email,
is_staff=is_staff,
is_superuser=is_superuser,
is_active=is_active,
)
user.groups.ajouter(app_user_group)
# Add additional groups, if provided.
user.groups.ajouter(*groups)
revenir user
revenir create_app_user
@pytest.fixture
def user_A(db, app_user_factory) -> Utilisateur:
revenir app_user_factory("A")
@pytest.fixture
def user_B(db, app_user_factory) -> Utilisateur:
revenir app_user_factory("B")
def test_should_create_user_in_app_user_group(
user_A: Utilisateur,
app_user_group: Group,
) -> Aucun:
assert user_A.groups.filter(pk=app_user_group.pk).existe()
def test_should_create_two_users(user_A: Utilisateur, user_B: Utilisateur) -> Aucun:
assert user_A.pk != user_B.pk
Open the terminal and run the test:
$ pytest test.py
======================== test session starts ========================
platform linux -- Python 3.8.1, pytest-5.3.3, py-1.8.1, pluggy-0.13.1
django: settings: django_fixtures.settings (from ini)
rootdir: /django_fixtures/django_fixtures, inifile: pytest.ini
plugins: django-3.8.0
collected 2 items
test.py .. [100%]
======================== 2 passed in 0.17s ==========================
Great job! You’ve successfully implemented the factory as fixture pattern in your tests.
Factories as Fixtures in Practice
The factory as fixture pattern is very useful. So useful, in fact, that you can find it in the fixtures provided by pytest
lui-même. For example, the tmp_path
fixture provided by pytest
is created by the fixture factory tmp_path_factory
. Likewise, the tmpdir
fixture is created by the fixture factory tmpdir_factory
.
Mastering the factory as fixture pattern can eliminate many of the headaches associated with writing and maintaining tests.
Conclusion
You’ve successfully implemented a fixture factory that provides Django model instances. You’ve also maintained and implemented dependencies between fixtures in a way that takes some of the hassle out of writing and maintaining tests.
In this tutorial, you’ve learned:
- How to create and load fixtures in Django
- Comment fournir test fixtures for Django models in
pytest
- How to use des usines to create fixtures for Django models in
pytest
- How to implement the factory as fixture pattern to create dependencies between test fixtures
You’re now able to implement and maintain a solid test suite that will help you produce better and more reliable code, faster!
[ad_2]