Django propose un petit set d’outils bien pratiques lors de l’écriture de tests.
Le client de test est une classe Python se comportant comme un navigateur Web simpliste, permettant de tester les vues et d’interagir par programmation avec votre application Django.
Voici plusieurs choses que vous pouvez faire avec le client de test :
Simuler des requêtes GET et POST sur une URL et examiner la réponse, que ce soit les détails HTTP de bas niveau (en-têtes de la réponse et codes d’état) ou le contenu de la page renvoyée.
Voir la chaîne des redirections (le cas échéant) et contrôler l’URL et le code de statut à chaque étape.
Tester qu’une requête données est rendue par un gabarit Django donné, et que le contexte du gabarit contient certaines valeurs.
Notez que le client de test n’est pas conçu pour remplacer Selenium ou d’autres systèmes utilisant un navigateur réel. Le client de test de Django a un objectif différent. En bref :
Utiliser le client de test de Django pour confirmer que le gabarit correct a été utilisé pour le rendu et que ce gabarit à reçu les bonnes données de contexte.
Utilisez des systèmes basés sur de vrais navigateurs comme Selenium pour tester le HTML produit et le comportement des pages Web, en particulier les fonctionnalités JavaScript. Django offre aussi une prise en charge spéciale pour ces systèmes ; consultez la section sur LiveServerTestCase pour plus de détails.
Une suite de tests complète devrait utiliser une combinaison de ces deux types.
Pour utiliser le client de test, créez une instance de django.test.client.Client et récupérez des pages Web :
>>> from django.test.client import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
'<!DOCTYPE html...'
Comme cet exemple le suggère, vous pouvez créer des instances de Client à partir d’une session de l’interpréteur Python interactif.
Signalons quelques éléments importants au sujet du fonctionnement du client de test :
Le client de test n’a pas besoin que le serveur Web soit lancé. En fait, il fonctionne tout à fait correctement sans aucun serveur Web actif ! Et cela parce qu’il s’adresse directement à l’infrastructure Django sans passer par la couche HTTP. Cela permet d’accélérer le fonctionnement des tests unitaires.
Lors de la récupération des pages, n’oubliez pas de n’indiquer que le chemin de l’URL, sans mentionner tout le nom de domaine. Par exemple, ceci est correct :
>>> c.get('/login/')
Mais pas ceci :
>>> c.get('http://www.example.com/login/')
Le client de test n’est pas capable de récupérer des pages Web qui ne sont pas basées sur votre projet Django. Si vous avez besoin de récupérer d’autres pages Web, utilisez un module standard de la bibliothèque Python, comme urllib ou urllib2.
Pour résoudre des URL, le client de test utilise la configuration d’URL qui est désignée par le réglage ROOT_URLCONF.
Même si l’exemple ci-dessus fonctionne dans un interpréteur Python interactif, certaines fonctionnalités du client de test, notamment celles liées aux gabarits, ne sont disponibles que lorsque les tests sont lancés.
Ceci s’explique par le fait que le lanceur de tests de Django fait sa propre cuisine pour déterminer quel gabarit a été chargé pour une vue donnée. Cette « cuisine » (essentiellement un correctif en mémoire du système de gabarits de Django) n’est opérée que durant le fonctionnement des tests.
Par défaut, le client de test désactive tous les contrôles CSRF effectués par votre site.
Si pour une raison quelconque vous * voulez* que le client de test effectue les contrôles CSRF, vous pouvez créer une instance du client de test avec les contrôles CSRF actifs. Pour cela, passez le paramètre enforce_csrf_checks au moment de créer le client :
>>> from django.test import Client
>>> csrf_client = Client(enforce_csrf_checks=True)
La classe django.test.client.Client permet de simuler des requêtes HTTP.
Aucun paramètre n’est obligatoire au moment de la création de l’instance. Vous pouvez cependant indiquer des paramètres nommés pour définir des en-têtes par défaut. Dans l’exemple suivant, un en-tête HTTP User-Agent est envoyé avec chaque requête :
>>> c = Client(HTTP_USER_AGENT='Mozilla/5.0')
Les valeurs de paramètres nommés passés dans extra aux méthodes get(), post(), etc. ont la priorité sur les valeurs par défaut transmises au constructeur de la classe.
Le paramètre enforce_csrf_checks peut être utilisé pour tester la protection CSRF (voir au-dessus).
Dès que vous avez à disposition une instance de Client, vous pouvez appeler au choix l’une des méthodes suivantes :
Procède à une requête GET utilisant le chemin path indiqué et renvoie un objet Response, qui est documenté plus bas.
Les paires clé-valeur dans le dictionnaire data servent à créer les données utiles de GET. Par exemple :
>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7})
…aboutit à l’évaluation d’une requête GET équivalente à :
/customers/details/?name=fred&age=7
Le paramètre nommé extra peut être utilisé pour indiquer les en-têtes envoyés avec la requête. Par exemple :
>>> c = Client()
>>> c.get('/customers/details/', {'name': 'fred', 'age': 7},
... HTTP_X_REQUESTED_WITH='XMLHttpRequest')
…envoie l’en-tête HTTP HTTP_X_REQUESTED_WITH à la vue de détail, ce qui constitue une bonne manière de tester des chemins de code utilisant la méthode django.http.HttpRequest.is_ajax().
Spécification CGI
Les en-têtes envoyés par **extra doivent respecter la spécification CGI. Par exemple, pour émuler un en-tête « Host » différent de celui envoyé dans la requête HTTP du navigateur vers le serveur, il faut transmettre HTTP_HOST.
Si vous disposez déjà des paramètres GET sous une forme déjà codée pour l’URL, vous pouvez utiliser cette forme au lieu du paramètre data. Par exemple, la requête GET précédente pourrait aussi être émise par :
>>> c = Client()
>>> c.get('/customers/details/?name=fred&age=7')
Si vous fournissez à la fois une URL contenant des données GET codée et un paramètre data, ce dernier a la priorité.
Si vous définissez follow à True, le client suit d’éventuelles redirections et l’objet réponse possédera un attribut redirect_chain contenant des tuples formés des URL intermédiaires et de leur code d’état.
Si vous aviez une URL /redirect_me/ redirigeant vers /next/, redirigeant lui-même vers /final/, voici ce que vous obtiendriez :
>>> response = c.get('/redirect_me/', follow=True)
>>> response.redirect_chain
[(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)]
Procède à une requête POST utilisant le chemin path indiqué et renvoie un objet Response, qui est documenté plus bas.
Les paires clé-valeur dans le dictionnaire data servent à créer les données de soumission POST. Par exemple :
>>> c = Client()
>>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'})
…aboutit à l’évaluation d’une requête POST vers cette URL :
/login/
…contenant ces données POST :
name=fred&passwd=secret
Si vous renseignez le paramètre content_type (par ex. text/xml pour des données utiles XML), le contenu de data sera envoyé tel quel dans la requête POST, en plaçant le contenu de content_type dans l’en-tête HTTP Content-Type.
Si le paramètre content_type n’est pas renseigné, les valeurs contenues dans data sont transmises avec un type de contenu multipart/form-data. Dans ce cas, les paires clé-valeur de data sont codées sous forme de message composite (multipart) et servent à créer les données utiles de POST.
Pour envoyer plusieurs valeurs pour un même clé, par exemple pour indiquer les sélections d’un élément <select multiple>, indiquez les valeurs de la clé sous forme de liste ou de tuple. Par exemple, le contenu suivant de data envoie trois valeurs sélectionnées pour le champ nommé choices:
{'choices': ('a', 'b', 'd')}
L’envoi de fichiers est un cas particulier. Pour envoyer un fichier par POST, il suffit d’indiquer comme clé le nom du champ de fichier et comme valeur un pointeur de fichier référençant le fichier à envoyer. Par exemple :
>>> c = Client()
>>> with open('wishlist.doc') as fp:
... c.post('/customers/wishes/', {'name': 'fred', 'attachment': fp})
(Le nom attachment n’a ici aucune signification particulière ; utilisez le nom de champ attendu par votre code de traitement de fichier.)
Notez que si vous souhaitez utiliser le même pointeur de fichier pour plusieurs appels à post(), vous devrez manuellement réinitialiser ce pointeur entre les requêtes. La façon la plus simple de le faire manuellement est de fermer le fichier après qu’il a été fourni à post(), comme cela est fait dans l’exemple ci-dessus.
Vous devez aussi être certain que le fichier est ouvert d’une manière autorisant les données à être lues. Si votre fichier contient des données binaires telles qu’une image, cela signifie que vous devrez ouvrir le fichier en mode rb (read binary).
Le paramètre extra joue le même rôle que pour Client.get().
Si l’URL indiquée pour la requête POST contient des paramètres codés, ceux-ci sont placés dans les données request.GET. Par exemple, si vous effectuez la requête :
>>> c.post('/login/?visitor=true', {'name': 'fred', 'passwd': 'secret'})
…la vue traitant la requête peut consulter request.POST pour obtenir les données du nom et du mot de passe, et request.GET pour savoir si l’utilisateur est un visiteur.
Si vous définissez follow à True, le client suit d’éventuelles redirections et l’objet réponse possédera un attribut redirect_chain contenant des tuples formés des URL intermédiaires et de leur code d’état.
Procède à une requête HEAD utilisant le chemin path indiqué et renvoie un objet Response. Cette méthode fonctionne comme Client.get(), y compris les paramètres follow et extra à la seule exception qu’aucun corps de message n’est renvoyé.
Procède à une requête OPTIONS utilisant le chemin path indiqué et renvoie un objet Response. Utile pour tester les interfaces de type « REST ».
Lorsque data est renseigné, il est utilisé comme corps de requête et un en-tête Content-Type est défini avec le contenu de content_type.
Client.options() traitait les données data comme Client.get().
Les paramètres follow et extra jouent le même rôle que pour Client.get().
Procède à une requête PUT utilisant le chemin path indiqué et renvoie un objet Response. Utile pour tester les interfaces de type « REST ».
Lorsque data est renseigné, il est utilisé comme corps de requête et un en-tête Content-Type est défini avec le contenu de content_type.
Client.put() traitait les données data comme Client.post().
Les paramètres follow et extra jouent le même rôle que pour Client.get().
Procède à une requête PATCH utilisant le chemin path indiqué et renvoie un objet Response. Utile pour tester les interfaces de type « REST ».
Les paramètres follow et extra jouent le même rôle que pour Client.get().
Procède à une requête DELETE utilisant le chemin path indiqué et renvoie un objet Response. Utile pour tester les interfaces de type « REST ».
Lorsque data est renseigné, il est utilisé comme corps de requête et un en-tête Content-Type est défini avec le contenu de content_type.
Client.delete() traitait les données data comme Client.get().
Les paramètres follow et extra jouent le même rôle que pour Client.get().
Si un projet utilise le système d’authentification de Django et que les tests connectent des utilisateurs, il est possible d’utiliser la méthode login() du client de test pour simuler la connexion d’un utilisateur au site concerné.
Après l’appel à cette méthode, le client de test contiendra tous les cookies et les données de session nécessaires pour passer les tests dans lesquels des vues comptent sur des utilisateurs connectés.
Le format du paramètre credentials dépend du moteur d’authentification utilisé (configuré dans le réglage AUTHENTICATION_BACKENDS). Dans le cas du moteur d’authentification standard de Django (ModelBackend), credentials doit contenir le nom d’utilisateur et le mot de passe de l’utilisateur, fournis sous la forme de paramètres nommés :
>>> c = Client()
>>> c.login(username='fred', password='secret')
# Now you can access a view that's only available to logged-in users.
Si un autre moteur d’authentification est utilisé, cette méthode peut nécessiter un autre format de données d’authentification. Cela dépend des paramètres nécessaires à la méthode authenticate() du moteur en question.
login() renvoie True si les données d’authentification ont été acceptées et que la connexion s’est terminée avec succès.
Pour finir, il ne faut pas oublier de créer des comptes utilisateurs avant de pouvoir utiliser cette méthode. Comme expliqué ci-dessus, le lanceur de tests fonctionne avec une base de données de test, sans aucun utilisateur par défaut. Par conséquent, les comptes utilisateurs actifs sur un site de production ne sont pas pris en compte dans des conditions de test. Les utilisateurs doivent être créés dans le cadre de la suite de tests, soit manuellement (par l’API de modèle Django), soit par un instantané de test. N’oubliez pas non plus que pour qu’un utilisateur de test dispose d’un mot de passe, il ne suffit pas de définir directement l’attribut password de l’utilisateur, mais il faut passer par la fonction set_password() pour que soit stockée une empreinte correcte de mot de passe. Il est aussi possible d’employer la méthode utilitaire create_user() pour créer un nouvel utilisateur ainsi qu’une empreinte de mot de passe utilisable.
Si un projet utilise le système d’authentification de Django, il est possible d’utiliser la méthode logout() du client de test pour simuler la déconnexion d’un utilisateur du site concerné.
Après l’appel à cette méthode, le client de test verra toutes ses données de cookies et de session réinitialisées à leurs valeurs par défaut. Les requêtes suivantes apparaîtront comme si elles provenaient d’un utilisateur anonyme (AnonymousUser).
The get() and post() methods both return a Response object. This Response object is not the same as the HttpResponse object returned by Django views; the test response object has some additional data useful for test code to verify.
Plus précisément, un objet Response possède les attributs suivants :
Le client de test utilisé pour effectuer la requête qui a renvoyé cette réponse.
Le corps de la réponse sous forme de texte. Il s’agit du contenu final de la page produite par la vue, ou d’un éventuel message d’erreur.
L’instance Context du gabarit utilisé pour effectuer le rendu de gabarit qui a produit le contenu de la réponse.
Si le rendu de la page à utilisé plusieurs gabarits, context contient une liste d’objets Context dans l’ordre de leur rendu.
Quel que soit le nombre de gabarits utilisés dans le processus de rendu, vous pouvez récupérer les valeurs de contexte en utilisant l’opérateur []. Par exemple, la variable de contexte name peut être récupérée ainsi :
>>> response = client.get('/foo/')
>>> response.context['name']
'Arthur'
Les données de requête à l’origine de la réponse.
Le statut HTTP de la réponse sous forme de nombre entier. Voir RFC 2616 pour une liste exhaustive des codes de statut HTTP.
Une liste d’instances de gabarits (Template) ayant servi à rendre le contenu final, dans l’ordre de leur utilisation. Pour chaque gabarit de la liste, utilisez template.name pour obtenir le nom de fichier du gabarit si celui-ci a été chargé à partir d’un fichier (le nom est une chaîne du genre 'admin/index.html').
Il est aussi possible d’utiliser la syntaxe de dictionnaire sur l’objet réponse pour interroger n’importe quelle valeur d’en-tête HTTP. Par exemple, vous pouvez retrouver le type de contenu d’une réponse avec response['Content-Type'].
Si le client de test fait appel à une vue qui génère une exception, celle-ci est propagée dans le cas de test. Vous pouvez dès lors utiliser un bloc try ... except standard ou assertRaises() pour tester l’exception générée.
The only exceptions that are not visible to the test client are Http404, PermissionDenied, SystemExit, and SuspiciousOperation. Django catches these exceptions internally and converts them into the appropriate HTTP response codes. In these cases, you can check response.status_code in your test.
L’état du client de test est persistant. Si une réponse renvoie un cookie, celui-ci est stocké dans le client de test et il sera ensuite envoyé dans les requêtes get() et post() subséquentes.
Les politiques d’expiration de ces cookies ne sont pas respectées. Si vous souhaitez faire expirer un cookie, supprimez-le manuellement ou créez une nouvelle instance de Client (ce qui aura comme conséquence de supprimer tous les cookies).
Un client de test possède deux attributs qui stockent les informations d’état persistantes. Vous pouvez accéder à ces propriétés dans le cadre d’une condition de test.
Un objet Python SimpleCookie contenant les valeurs actuelles de tous les cookies du client. Consultez la documentation du module Cookie pour en savoir plus.
Un objet de type dictionnaire contenant les informations de session. Consultez la documentation des sessions pour plus de détails.
Pour modifier la session et l’enregistrer, elle doit être d’abord stockée dans une variable (car un nouveau SessionStore est créé chaque fois qu’on accède à cette propriété) :
def test_something(self):
session = self.client.session
session['somekey'] = 'test'
session.save()
L’exemple suivant est un test unitaire simple exploitant le client de test :
from django.utils import unittest
from django.test.client import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get('/customer/details/')
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context['customers']), 5)
Voir aussi
Les classes de test unitaire normales de Python étendent la classe de base unittest.TestCase. Django fournit plusieurs extensions de cette classe de base :
Quelle que soit la version de Python, si unittest2 est installée, django.utils.unittest se réfère à cette bibliothèque.
Une sous-classe légère de unittest.TestCase qui étend celle-ci par quelques fonctionnalités de base comme :
Sauvegarde et restauration de l’état du mécanisme Python des avertissements.
Certaines assertions utiles comme :
Vérification qu’un objet exécutable génère une exception donnée.
Le test de rendu et de traitement d'erreur des champs de formulaire.
Le test de présence ou d'absence d'un fragment donné dans des réponses HTML.
La vérification qu’un gabarit a été ou n'a pas été utilisé pour générer un contenu de réponse donné.
La vérification qu’une redirection HTTP a été effectuée par une application.
La comparaison robuste entre deux fragments HTML ou la présence ou absence d’un fragment HTML dans un autre.
La comparaison robuste entre deux fragments XML.
La comparaison robuste entre deux fragments JSON.
La capacité de lancer des tests avec des réglages modifiés.
Des configurations d'URL pour la durée des tests.
Ces deux dernières fonctionnalités ont été déplacées de TransactionTestCase vers SimpleTestCase dans Django 1.6.
Si vous avez besoin de l’une des fonctionnalités plus lourdes et complexes spécifiques à Django comme :
Test et utilisation de l’ORM.
Les instantanés (fixtures) de base de données.
Omission de tests en fonction de fonctionnalités du moteur de base de données.
Les méthodes spécialisées restantes du type assert*.
il faut alors plutôt utiliser TransactionTestCase ou TestCase.
SimpleTestCase hérite de django.utils.unittest.TestCase.
La classe TestCase de Django (décrite ci-dessous) emploie les utilitaires de transaction de base de données pour accélérer le processus de réinitialisation de la base de données à un état connu au début de chaque test. Cependant, une des conséquences de ce comportement est qu’il n’est pas possible de tester les effets des opérations de validation et d’annulation de transactions (« commit » et « rollback ») avec la classe TestCase de Django. Si votre test est censé tester de tels comportements transactionnels, il vous faut utiliser la classe TransactionTestCase.
TransactionTestCase et TestCase sont identiques à l’exception de la manière dont la base de données est réinitialisée à un état connu et de la capacité de tester les effets de « commit » et de « rollback » dans le code :
La classe TransactionTestCase réinitialise la base de données après le lancement des tests en tronquant toutes les tables. Le code de ces tests peut valider et annuler des transactions et observer les effets de ces appels sur la base de données.
D’un autre côté, un TestCase ne tronque pas les tables après un test. Il enveloppe plutôt le code de test dans une transaction de base de données qui est ensuite annulée lorsque le test est terminé. Qu’ils soient explicites comme avec transaction.commit() ou implicites comme l’emploi de transaction.atomic() peut les provoquer, les validations (« commit ») sont remplacées par des opérations blanches. Cela permet de garantir que l’annulation (« rollback ») à la fin des tests restaure la base de données à son état initial.
Lorsque la base de données sous-jacente ne prend pas en charge l’annulation (« rollback »), par exemple MySQL avec le moteur MyISAM, TestCase retourne au comportement d’initialisation de la base de données en tronquant les tables et en rechargeant les données initiales.
Avertissement
While commit and rollback operations still appear to work when used in TestCase, no actual commit or rollback will be performed by the database. This can cause your tests to pass or fail unexpectedly. Always use TransactionTestCase when testing transactional behavior or any code that can’t normally be executed in autocommit mode (select_for_update() is an example).
Avant 1.5, TransactionTestCase réinitialisait les tables de bases de données avant chaque test. Dans Django 1.5, cette opération se fait après la fin de chaque test.
Lorsque la réinitialisation se produisait avant les tests, les valeurs de clé primaire commençaient toujours à un dans les tests TransactionTestCase.
Les tests ne devraient pas dépendre de ce comportement, mais pour les tests existants qui le font, l’attribut reset_sequences peut être utilisé jusqu’à ce que le test ait été mis à jour proprement.
L’ordre dans lequel les tests sont exécutés a changé. Voir Ordre d’exécution des tests.
TransactionTestCase hérite de SimpleTestCase.
Cette classe fournit certaines fonctionnalités supplémentaires utiles aux tests de sites Web.
La conversion d’une classe basée sur unittest.TestCase vers une classe basée sur TestCase de Django est simple : il suffit de changer la classe de base de vos tests de 'unittest.TestCase' vers 'django.test.TestCase'. Toutes les fonctionnalités standard de Python pour les tests unitaires sont toujours disponibles, mais vous obtenez en plus quelques éléments utiles, dont :
Le chargement automatique d’instantanés.
La création d’une transaction enveloppant chaque test.
La création d’une instance du client de test.
Des assertions spécifiques à Django pour des tests comme la redirection et les erreurs de formulaires.
L’ordre dans lequel les tests sont exécutés a changé. Voir Ordre d’exécution des tests.
TestCase hérite de TransactionTestCase.
LiveServerTestCase est à la base identique à TransactionTestCase avec une fonction supplémentaire : elle lance un serveur Django en arrière-plan lors de la préparation (« setUp ») des tests et l’arrête lors du nettoyage (« tearDown »). Cela permet l’utilisation de clients de tests automatisés autres que le client élémentaire de Django, comme par exemple le client Selenium, afin d’exécuter une série de tests fonctionnels dans un navigateur et de simuler ainsi des actions d’un utilisateur réel.
Par défaut, l’adresse du serveur lancé est 'localhost:8081' et l’URL complète est disponible pendant les tests via self.live_server_url. Si vous souhaitez modifier cette adresse par défaut (par exemple dans l’éventualité où le port 8081 serait déjà occupé), il est possible d’en communiquer une autre à la commande test par l’option --liveserver. Par exemple :
./manage.py test --liveserver=localhost:8082
Une autre manière de changer l’adresse par défaut du serveur est de définir la variable d’environnement DJANGO_LIVE_TEST_SERVER_ADDRESS quelque part dans votre code (par exemple dans un lanceur de tests personnalisé) :
import os
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8082'
Dans le cas où les tests sont lancés en parallèle sur plusieurs processus (par exemple dans le contexte de compilations simultanées par intégration continue), les processus pourraient entrer en concurrence pour une même adresse, ce qui pourrait faire échouer certains tests de manière aléatoire avec une erreur « Address already in use ». Pour éviter ce problème, il est possible de transmettre une liste de ports séparés par des virgules ou des intervalles de ports (il en faut au moins autant que le nombre potentiel de processus parallèles). Par exemple :
./manage.py test --liveserver=localhost:8082,8090-8100,9000-9200,7041
Puis, pendant l’exécution des tests, chaque nouveau serveur de test lancé essaiera tour à tour chaque port disponible jusqu’à en trouver un de libre pour l’utiliser.
Pour démontrer comment utiliser LiveServerTestCase, écrivons un test Selenium simple. Premièrement, il s’agit d’installer le paquet selenium dans le chemin Python :
pip install selenium
Puis, ajoutez un test basé sur LiveServerTestCase dans le module de test de votre application (par exemple myapp/tests.py). Le code de ce test pourrait ressembler à ceci :
from django.test import LiveServerTestCase
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(LiveServerTestCase):
fixtures = ['user-data.json']
@classmethod
def setUpClass(cls):
cls.selenium = WebDriver()
super(MySeleniumTests, cls).setUpClass()
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super(MySeleniumTests, cls).tearDownClass()
def test_login(self):
self.selenium.get('%s%s' % (self.live_server_url, '/login/'))
username_input = self.selenium.find_element_by_name("username")
username_input.send_keys('myuser')
password_input = self.selenium.find_element_by_name("password")
password_input.send_keys('secret')
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
Finalement, lancez le test comme ceci :
./manage.py test myapp.tests.MySeleniumTests.test_login
Cet exemple ouvre Firefox automatiquement puis se rend à la page de connexion, saisit les données d’authentification et appuie sur le bouton « Log in ». Selenium propose d’autres pilotes dans le cas où Firefox n’est pas installé ou que vous voudriez tester avec un autre navigateur. L’exemple ci-dessus n’est qu’une fraction de ce que le client Selenium est capable de faire ; consultez la référence complète pour plus de détails.
Note
LiveServerTestCase exploite l’application contribuée staticfiles, il est donc nécessaire que le projet soit configuré en conséquence (en particulier par la définition de STATIC_URL).
Note
Lorsque les tests utilisent une base de données SQLite en mémoire, une même connexion de base de données sera partagée par deux fils d’exécution en parallèle : le fil dans lequel tourne le serveur « live » et le fil utilisé pour faire fonctionner le cas de test. Il est important de prévenir les requêtes de base de données simultanées au travers de cette connexion partagée, car cela pourrait provoquer l’échec aléatoire de certains tests. Vous devez donc vous assurer que les deux fils d’exécution n’accèdent pas à la base de données au même moment. En particulier, cela signifie que dans certains cas (par exemple juste après avoir cliqué sur un lien ou soumis un formulaire), il est nécessaire de contrôler qu’une réponse a été reçue par Selenium et que la page suivante a été chargée avant de continuer avec la suite de l’exécution des tests. Cela peur par exemple se faire en faisant attendre Selenium jusqu’à ce que la balise HTML <body> soit présente dans la réponse (nécessite Selenium > 2.13) :
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element_by_xpath('//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element_by_tag_name('body'))
L’élément délicat ici est que le concept de « chargement de page » n’existe pas, particulièrement dans les applications Web modernes qui produisent du code HTML dynamique après la génération du document initial par le serveur. Ceci fait que le simple contrôle de la présence de <body> dans la réponse n’est pas forcément approprié dans tous les cas de figure. Consultez la FAQ Selenium ainsi que la documentation Selenium pour obtenir davantage d’informations.
Chaque cas de test dans une instance de django.test.*TestCase peut accéder à une instance du client de test de Django. Ce client est disponible dans self.client. Il est recréé pour chaque test, il n’y a donc pas besoin de se soucier de son état (comme les cookies) qui pourrait se propager d’un test à l’autre.
Cela signifie qu’au lieu de créer une instance de Client dans chaque test :
from django.utils import unittest
from django.test.client import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
…il suffit de faire appel à self.client, comme ceci :
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get('/customer/index/')
self.assertEqual(response.status_code, 200)
Si vous souhaitez utiliser une classe Client différente (par exemple une sous-classe avec un comportement adapté), utilisez l’attribut de classe client_class:
from django.test import TestCase
from django.test.client import Client
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
Un cas de test pour un site Web adossé à une base de données n’est pas très utile s’il n’y a pas de données en base de données. Pour faciliter la mise en place de données de test en base de données, la classe TransactionTestCase de Django offre un moyen de charger des instantanés.
Un instantané est une série de données que Django sait importer dans la base de données. Par exemple, si votre site contient des comptes utilisateurs, il peut être utile de créer un instantané de comptes utilisateurs afin de remplir la base de données pendant les tests.
La façon la plus directe de créer un instantané est d’utiliser la commande manage.py dumpdata, en partant du principe que le base de données contient déjà les données nécessaires. Consultez la documentation de dumpdata pour plus de détails.
Note
À chaque fois que vous avez exécuté manage.py syncdb, vous avez déjà utilisé un instantané sans vous en rendre compte ! Lorsque syncdb synchronise la base de données pour la première fois, Django installe un instantané nommé initial_data. Cela vous donne l’opportunité de remplir une nouvelle base de données avec des données de base, comme par exemple un ensemble de catégories par défaut.
Des instantanés nommés différemment peuvent toujours être installés manuellement en appelant la commande manage.py loaddata.
Données SQL initiales et tests
Django offre une seconde manière d’insérer des données initiales dans des modèles, le code SQL personnalisé. Cependant, cette technique ne peut pas être utilisée pour fournir des données initiales dans le contexte des tests. L’infrastructure de test de Django réinitialise le contenu de la base de données de test après chaque test ; par conséquent, toute donnée insérée à l’aide du mécanisme de code SQL sera effacée.
Après avoir créé un instantané et l’avoir placé dans un répertoire fixtures dans l’une des applications de INSTALLED_APPS, vous pouvez l’utiliser dans vos tests unitaires en définissant un attribut de classe fixtures dans votre sous-classe de django.test.TestCase:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ['mammals.json', 'birds']
def setUp(self):
# Test definitions as before.
call_setup_methods()
def testFluffyAnimals(self):
# A test that uses the fixtures.
call_some_test_code()
Voici ce qui se passe en pratique :
Au début de chaque test, avant l’exécution de setUp(), Django réinitialise la base de données telle qu’elle se trouvait juste après avoir exécuté syncdb.
Puis, tous les instantanés nommés sont installés. Dans cet exemple, Django installe tout instantané JSON nommé mammals, suivi par tout instantané nommé birds. Consultez la documentation de loaddata pour plus de détails sur la définition et l’installation d’instantanés.
Cette procédure de réinitialisation/chargement est répétée pour chaque test du cas de test ; vous pouvez ainsi être sûr que le résultat d’un test ne sera pas affecté par un autre test ou par l’ordre d’exécution des tests.
Par défaut, les instantanés ne sont chargés que dans la base de données default. Si vous utilisez plusieurs bases de données et que vous définissez multi_db=True, les instantanés seront chargés dans toutes les bases de données.
Si une application contient des vues, il peut être souhaitable d’inclure des tests où le client de test éprouve ces vues. Cependant, un utilisateur de l’application est libre de déployer les vues d’une application à l’URL de son choix. Cela signifie que les tests ne peuvent pas compter sur des URL figées pour accéder aux vues.
Afin de mettre à disposition un espace d’URL fiable pour les tests, les classes django.test.*TestCase donnent la possibilité de personnaliser la configuration d’URL pour la durée d’exécution d’une suite de tests. Si votre instance de *TestCase définit un attribut urls, la classe *TestCase utilise la valeur de cet attribut comme ROOT_URLCONF pour la durée des tests concernés.
Par exemple :
from django.test import TestCase
class TestMyViews(TestCase):
urls = 'myapp.test_urls'
def testIndexPageView(self):
# Here you'd test your view using ``Client``.
call_some_test_code()
Ce cas de test utilise le contenu de myapp.test_urls comme configuration d’URL pour toute la durée du cas de test.
Django crée une base de données de test pour chaque base de données définie dans DATABASES du fichier de réglages. Cependant, une bonne partie du temps nécessaire à exécuter un cas de test Django est passé dans l’appel à flush (réinitialisation des données) permettant de retrouver une base de données propre au début de chaque test. Pour un projet avec plusieurs bases de données, plusieurs commandes flush sont nécessaires (une par base de données), ce qui peut représenter un temps non négligeable, particulièrement si les tests n’ont pas pour but de tester l’activité entre plusieurs bases de données.
Dans une optique d’optimisation, Django ne réinitialise que la base de données default au début de chaque test. Si votre configuration contient plusieurs bases de données et que certains tests nécessitent que toutes les bases de données soient propres, vous pouvez définir l’attribut multi_db de la suite de tests pour provoquer une réinitialisation complète.
Par exemple :
class TestMyViews(TestCase):
multi_db = True
def testIndexPageView(self):
call_some_test_code()
Ce cas de test réinitialise toutes les bases de données de test avant d’exécuter testIndexPageView.
L’option multi_db affecte également les bases données dans lesquelles sont chargés les instantanés (attr:TransactionTestCase.fixtures). Par défaut lorsque multi_db=False, les instantanés ne sont chargés que dans la base de données default. Si multi_db=True, ils sont chargés dans toutes les bases de données.
Avertissement
Use the functions below to temporarily alter the value of settings in tests. Don’t manipulate django.conf.settings directly as Django won’t restore the original values after such manipulations.
À des fins de tests, il est souvent utile de modifier temporairement un réglage puis de retrouver la valeur d’origine après l’exécution du code des tests. Pour cette situation, Django offre un gestionnaire de contexte de style Python (voir PEP 343) nommé settings() qui peut être utilisé comme ceci :
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL='/other/login/'):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
Cet exemple surcharge le réglage LOGIN_URL pour le code contenu dans le bloc with et réapplique la valeur originale à la fin du bloc.
Dans le cas où vous souhaitez surcharger un réglage pour une seule méthode de test ou même pour toute une classe TestCase, Django met à disposition le décorateur override_settings() (voir PEP 318). Voici comment l’utiliser :
from django.test import TestCase
from django.test.utils import override_settings
class LoginTestCase(TestCase):
@override_settings(LOGIN_URL='/other/login/')
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
Le décorateur peut aussi être appliqué à des classes de cas de test :
from django.test import TestCase
from django.test.utils import override_settings
@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
Note
Lorsqu’il reçoit une classe, le décorateur modifie directement la classe et la renvoie ; la classe renvoyée n’est pas une copie. Ainsi si vous essayez de manipuler l’exemple ci-dessus pour que la valeur renvoyée soit nommée différemment que LoginTestCase, vous pourriez être surpris de constater que la classe LoginTestCase originale est tout de même affectée par le décorateur.
Pour terminer, évitez de créer des alias de réglages comme constantes de niveau module, car override_settings() ne fonctionnera pas avec de telles valeurs qui ne sont évaluées que lors de la première importation du module.
Il est aussi possible de simuler l’absence d’un réglage en supprimant celui-ci après que les réglages aient été surchargés, comme ceci :
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
Lors de la surcharge de réglages, prenez soin de gérer les cas où le code de votre application utilise du cache ou un mécanisme similaire conservant l’état même quand le réglage a changé. Django fournit le signal django.test.signals.setting_changed permettant d’inscrire des fonctions de rappel qui serviront à réinitialiser l’état lorsque les réglages ont changé.
Django utilise lui-même ce signal pour réinitialiser différentes données :
Réglages surchargés |
Données réinitialisées |
---|---|
USE_TZ, TIME_ZONE | Fuseau horaire de la base de données |
TEMPLATE_CONTEXT_PROCESSORS | Cache des processeurs de contexte |
TEMPLATE_LOADERS | Cache des chargeurs de gabarits |
SERIALIZATION_MODULES | Cache des sérialiseurs |
LOCALE_PATHS, LANGUAGE_CODE | Traduction par défaut et traductions chargées |
MEDIA_ROOT, DEFAULT_FILE_STORAGE | Stockage de fichiers par défaut |
Si vous utilisez l’une des classes TestCase personnalisées de Django, l’exécuteur de tests efface le contenu de la boîte de messagerie de test au début de chaque cas de test.
Pour plus de détails sur les services de messagerie durant les tests, consultez Services de messagerie plus bas dans ce document.
De la même manière que la classe unittest.TestCase normale de Python implément des méthodes d’assertion telles que assertTrue() et assertEqual(), la classe TestCase propre à Django fournit un certain nombre de méthodes d’assertion utiles pour tester les applications Web :
Les messages d’échec produits par la plupart de ces méthodes d’assertion peuvent être personnalisés par le paramètre msg_prefix. Cette chaîne préfixe tout message d’échec généré par l’assertion. Ceci permet d’ajouter des détails pouvant aider à identifier l’emplacement et la cause d’un échec dans une suite de tests.
Confirme que l’appel à l’exécutable callable_obj génère l’exception expected_exception et que la représentation de celle-ci correspond à expected_message. Tout autre résultat est signalé comme un échec. Semblable à assertRaisesRegexp() de la bibliothèque unittest, à la différence près que expected_message n’est ici pas une expression régulière.
Confirme qu’un champ de formulaire se comporte correctement avec différentes valeurs soumises.
Paramètres: |
|
---|
Par exemple, le code suivant teste qu’un champ EmailField accepte « a@a.com » comme adresse électronique valide, mais rejette « aaa » avec un message d’erreur adéquat :
self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': [u'Enter a valid email address.']})
Confirme qu’un champ de formulaire génère la liste d’erreurs fournie lorsqu’il est affiché dans son formulaire.
form est le nom de l’instance Form dans le contexte de gabarit.
field est le nom du champ dans le formulaire à contrôler. Si field possède la valeur None, ce sont les erreurs non liées aux formulaires (accessibles via formset.non_field_errors()) qui seront vérifiées.
errors est un texte d’erreur ou une liste de textes d’erreur qui sont censés être produits en réponse à la validation du formulaire.
Confirme que formset génère la liste d’erreurs fournie lorsqu’il est affiché.
formset est le nom de l’instance Formset dans le contexte de gabarit.
form_index est le numéro du formulaire dans Formset. Si form_index possède la valeur None, ce sont les erreurs non liées aux formulaires (accessibles via formset.non_form_errors()) qui seront vérifiées.
field est le nom du champ dans le formulaire à contrôler. Si field possède la valeur None, ce sont les erreurs non liées aux formulaires (accessibles via formset.non_field_errors()) qui seront vérifiées.
errors est un texte d’erreur ou une liste de textes d’erreur qui sont censés être produits en réponse à la validation du formulaire.
Confirme qu’une instance de Response produit le code status_code indiqué et que le contenu text apparaît dans le contenu de la réponse. Si count est renseigné, text doit apparaître exactement count fois dans la réponse.
Définissez html à True pour que text soit géré comme du code HTML. La comparaison avec le contenu de la réponse prend alors en compte la sémantique HTML au lieu d’une comparaison caractère par caractère. Les espaces blancs sont majoritairement ignorés et l’ordre des attributs ne joue pas de rôle. Voir assertHTMLEqual() pour plus de détails.
Confirme qu’une instance de Response produit le code status_code indiqué et que le contenu text n’apparaît pas dans le contenu de la réponse.
Définissez html à True pour que text soit géré comme du code HTML. La comparaison avec le contenu de la réponse prend alors en compte la sémantique HTML au lieu d’une comparaison caractère par caractère. Les espaces blancs sont majoritairement ignorés et l’ordre des attributs ne joue pas de rôle. Voir assertHTMLEqual() pour plus de détails.
Confirme que le gabarit du nom indiqué a été utilisé pour produire la réponse.
Le nom est une chaîne du genre 'admin/index.html'.
Vous pouvez l’utiliser comme un gestionnaire de contexte, comme ceci :
with self.assertTemplateUsed('index.html'):
render_to_string('index.html')
with self.assertTemplateUsed(template_name='index.html'):
render_to_string('index.html')
Confirme que le gabarit du nom indiqué n’a pas été utilisé pour produire la réponse.
Vous pouvez l’utiliser comme un gestionnaire de contexte de la même façon qu’avec assertTemplateUsed().
Confirme que la réponse renvoie un statut de redirection status_code, qu’elle redirige vers expected_url (y compris avec d’éventuelles données GET) et que la page finale a renvoyé le code target_status_code.
Si la requête a utilisé le paramètre follow, les valeurs de expected_url et de target_status_code doivent être celles de la page finale de la chaîne de redirection.
Le paramètre host définit un hôte par défaut si expected_url n’en contient pas déjà un (par ex. "/bar/"). Si expected_url est une URL absolue contenant une hôte (par ex. "http://testhost/bar/"), le paramètre host sera ignoré. Notez que le client de test ne va pas récupérer des URL externes, mais le paramètre peut être utile si vous testez avec un hôte HTTP personnalisé (par exemple en initialisant le client de test avec Client(HTTP_HOST="testhost")).
Confirme que les chaînes html1 et html2 sont équivalentes. La comparaison prend en compte la sémantique HTML. La comparaison tient compte des éléments suivants :
Les blancs avant et après les balises HTML sont ignorés.
Tous les types de blancs (espaces, tabulateurs, etc.) sont équivalents.
Toutes les balises ouvertes sont fermées implicitement, par exemple quand une balise de niveau supérieur est fermée ou quand le document HTML est fini.
Les balises vides sont équivalentes à leur version auto-fermante.
L’ordre des attributs d’un élément HTML n’est pas signifiant.
Les attributs sans paramètre sont équivalents aux attributs dont le nom et la valeur sont identiques (voir les exemples).
Les exemples suivants sont des tests valides et ne génèrent pas d’exception AssertionError:
self.assertHTMLEqual('<p>Hello <b>world!</p>',
'''<p>
Hello <b>world! <b/>
</p>''')
self.assertHTMLEqual(
'<input type="checkbox" checked="checked" id="id_accept_terms" />',
'<input id="id_accept_terms" type='checkbox' checked>')
html1 et html2 doivent contenir du code HTML valide. Une exception AssertionError est générée si l’une de ces deux valeurs ne peut pas être analysée comme du code HTML.
L’affichage en cas d’erreur peut être personnalisé avec le paramètre msg.
Confirme que les chaînes html1 et html2 ne sont pas équivalentes. La comparaison prend en compte la sémantique HTML. Voir assertHTMLEqual() pour plus de détails.
html1 et html2 doivent contenir du code HTML valide. Une exception AssertionError est générée si l’une de ces deux valeurs ne peut pas être analysée comme du code HTML.
L’affichage en cas d’erreur peut être personnalisé avec le paramètre msg.
Confirme que les chaînes xml1 et xml2 sont équivalentes. La comparaison prend en compte la sémantique XML. Sur le même principe que assertHTMLEqual(), la comparaison se fait sur le contenu analysé, ce qui fait que seules les différences sémantiques sont prises en compte, pas la syntaxe elle-même. Si le contenu XML de l’un des paramètres n’est pas valide, une exception AssertionError est toujours générée, même si les deux chaînes sont identiques.
L’affichage en cas d’erreur peut être personnalisé avec le paramètre msg.
Confirme que les chaînes xml1 et xml2 ne sont pas équivalentes. La comparaison prend en compte la sémantique xML. Voir assertXMLEqual() pour plus de détails.
L’affichage en cas d’erreur peut être personnalisé avec le paramètre msg.
Confirme que le fragment HTML needle est contenu dans le contenu haystack.
Si le paramètre nombre entier count est indiqué, un contrôle supplémentaire est effectué que le nombre d’occurrences de needle correspond à count.
Dans la plupart des cas, les blancs sont ignorés et l’ordre des attributs n’est pas pris en compte. Les paramètres transmis doivent être du code HTML valide.
Confirme que les fragments JSON raw et expected_data sont égaux. Les règles habituelles de JSON concernant les blancs non significatifs s’appliquent, car le gros du travail est confié à la bibliothèque json.
L’affichage en cas d’erreur peut être personnalisé avec le paramètre msg.
Confirme que le jeu de requête qs renvoie une liste particulière de valeurs values.
La comparaison des contenus de qs et de values se fait par la fonction transform. Cela signifie que par défaut c’est la représentation repr() de chaque valeur qui est utilisée pour la comparaison. Toute autre exécutable peut être utilisé si repr() ne constitue pas un point de comparaison valable.
Par défaut, la comparaison dépend aussi de l’ordre de tri. Si qs ne comporte pas d’ordre de tri implicite, vous pouvez définir le paramètre ordered à False, ce qui provoquera une comparaison sur un ensemble (set) Python.
La méthode vérifie maintenant si l’ordre est indéfini et génère une exception ValueError le cas échéant. L’ordre de tri est considéré comme indéfini si le paramètre qs n’est pas trié et que la comparaison se fait avec plus d’une valeur triée.
Confirme que lorsque func est appelée avec *args et **kwargs, num requêtes de base de données sont effectuées.
Si une clé "using" est présente dans kwargs, cette valeur est utilisée comme alias de base de données pour laquelle le nombre de requêtes sera contrôlé. Si vous souhaitez appeler une fonction avec un « vrai » paramètre using, vous pouvez le faire en enveloppant l’appel dans une fonction lambda pour y ajouter un paramètre supplémentaire :
self.assertNumQueries(7, lambda: my_function(using=7))
Vous pouvez également l’utiliser comme un gestionnaire de contexte :
with self.assertNumQueries(2):
Person.objects.create(name="Aaron")
Person.objects.create(name="Daniel")
Si l’une de vos vues Django envoie des courriels via la fonctionnalité d’envoi de courriels de Django, vous ne souhaiterez probablement pas réellement envoyer un courriel lors de chaque test de cette vue. C’est pour cette raison que le lanceur de tests de Django redirige automatiquement tous les courriels qu’il envoie dans une boîte artificielle. Cela vous permet aussi de tester chaque aspect de l’envoi de courriels, du nombre de messages envoyés jusqu’au contenu de chaque message, sans jamais envoyer réellement les messages.
Le lanceur de tests fait cela en remplaçant de manière transparente le moteur de messagerie normal par un moteur de test (n’ayez crainte, cela n’a aucun effet sur l’expédition de courriels en dehors de Django, comme un éventuel serveur de messagerie tournant sur votre machine).
Durant le fonctionnement des tests, chaque courriel sortant est enregistré dans django.core.mail.outbox. Il s’agit d’une simple liste de toutes les instances de EmailMessage qui ont été envoyées. L’attribut outbox est un attribut spécial qui est uniquement créé lorsque le moteur de messagerie locmem est actif. Il ne fait normalement pas partie du module django.core.mail et il ne peut être importé directement. Le code ci-dessous montre comment accéder correctement à cet attribut.
Voici un exemple de test qui examine la longueur et le contenu de django.core.mail.outbox:
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
# Send message.
mail.send_mail('Subject here', 'Here is the message.',
'from@example.com', ['to@example.com'],
fail_silently=False)
# Test that one message has been sent.
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
self.assertEqual(mail.outbox[0].subject, 'Subject here')
Comme noté précédemment, la boîte de messagerie de test est vidée au début de chaque test des classes Django *TestCase. Pour vider la boîte manuellement, attribuez une liste vide à mail.outbox:
from django.core import mail
# Empty the test outbox
mail.outbox = []
Management commands can be tested with the call_command() function. The output can be redirected into a StringIO instance:
from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
call_command('closepoll', stdout=out)
self.assertIn('Expected output', out.getvalue())
La bibliothèque unittest fournit les décorateurs @skipIf rz @skipUnless pour permettre d’exclure des tests si vous savez à l’avance que ces tests vont échouer dans certaines conditions.
Par exemple, si la réussite d’un test exige la présence d’une certaine bibliothèque, il est possible de décorer le cas de test avec @skipIf. Puis, le lanceur de tests signalera que le test n’a pas été exécuté ainsi que la raison, au lieu de laisser le test échouer ou d’ignorer totalement le test.
En plus de ces comportements d’exclusion de test, Django ajoute deux décorateurs de test supplémentaires. Au lieu de se baser sur une valeur booléenne générique, ces décorateurs contrôlent les capacités d’une base de données et excluent le test concerné si la base de données ne gère pas la capacité nommément indiquée.
Ces décorateurs utilisent un identifiant textuel pour désigner les capacités de base de données. Cet identifiant correspond à un attribut de la classe des capacités de connexion de base de données. Voir la classe django.db.backends.BaseDatabaseFeatures pour obtenir une liste complète des capacités de base de données pouvant être utilisées comme critères d’exclusion de tests.
Exclut le test décoré si la capacité de base de données indiquée est prise en charge.
Par exemple, le test suivant ne sera pas exécuté si la base de données gère les transactions (par ex. il ne serait pas exécuté avec PostgreSQL, mais il le serait avec MySQL utilisant des tables MyISAM) :
class MyTests(TestCase):
@skipIfDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
Exclut le test décoré si la capacité de base de données indiquée n’est pas prise en charge.
Par exemple, le test suivant ne sera exécuté que si la base de données gère les transactions (par ex. il serait exécuté avec PostgreSQL, mais il ne le serait pas avec MySQL utilisant des tables MyISAM)
class MyTests(TestCase):
@skipUnlessDBFeature('supports_transactions')
def test_transaction_behavior(self):
# ... conditional test code
Jan 13, 2016