{"title":"Écriture de votre première application Django, 5ème partie","version":"3.1","locale":"fr","docname":"intro/tutorial05","url":"/fr/3.1/intro/tutorial05/","canonical":"https://djangodocs.dev/fr/3.1/intro/tutorial05/","summary":"Ce tutoriel commence là où le tutoriel 4 s’est achevé. Nous avons construit une application Web de sondage et nous allons maintenant créer quelques tests…","html":"<h1>Écriture de votre première application Django, 5ème partie<a class=\"heading-anchor\" href=\"#writing-your-first-django-app-part-5\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h1>\n<p>Ce tutoriel commence là où le <a class=\"reference internal\" href=\"/fr/3.1/intro/tutorial04/\"><span class=\"doc\">tutoriel 4</span></a> s’est achevé. Nous avons construit une application Web de sondage et nous allons maintenant créer quelques tests automatisés pour cette application.</p>\n<aside class=\"admonition-where-to-get-help admonition\">\n<p class=\"admonition-title\">Où obtenir de l’aide :</p>\n<p>Si vous rencontrez des problèmes dans le parcours de ce tutoriel, rendez-vous dans la section <a class=\"reference internal\" href=\"/fr/3.1/faq/help/\"><span class=\"doc\">Obtenir de l’aide</span></a> de la FAQ.</p>\n</aside>\n<section id=\"introducing-automated-testing\">\n<h2>Introduction aux tests automatisés<a class=\"heading-anchor\" href=\"#introducing-automated-testing\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<section id=\"what-are-automated-tests\">\n<h3>Que sont les tests automatisés ?<a class=\"heading-anchor\" href=\"#what-are-automated-tests\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Les tests sont des routines qui vérifient le fonctionnement de votre code.</p>\n<p>Les tests peuvent se faire à différents niveaux. Certains tests s’appliquent à un petit détail (<em>est-ce que tel modèle renvoie les valeurs attendues ?</em>), alors que d’autres examinent le fonctionnement global du logiciel (<em>est-ce qu’une suite d’actions d’un utilisateur sur le site produit le résultat désiré ?</em>). C’est le même genre de test qui a été pratiqué précédemment dans le <a class=\"reference internal\" href=\"/fr/3.1/intro/tutorial02/\"><span class=\"doc\">tutoriel 2</span></a>, en utilisant le <a class=\"reference internal\" href=\"/fr/3.1/ref/django-admin/#django-admin-shell\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">shell</span></code></a> pour examiner le comportement d’une méthode ou en lançant l’application et en saisissant des données pour contrôler son comportement.</p>\n<p>Ce qui est différent dans les tests <em>automatisés</em>, c’est que le travail du test est fait pour vous par le système. Vous créez une seule fois un ensemble de tests, puis au fur et à mesure des modifications de votre application, vous pouvez contrôler que votre code fonctionne toujours tel qu’il devrait, sans devoir effectuer des tests manuels fastidieux.</p>\n</section>\n<section id=\"why-you-need-to-create-tests\">\n<h3>Pourquoi faut-il créer des tests<a class=\"heading-anchor\" href=\"#why-you-need-to-create-tests\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Ainsi donc, pourquoi créer des tests, et pourquoi maintenant ?</p>\n<p>Vous pouvez penser que vous avez déjà assez de chats à fouetter en apprenant Python/Django et que rajouter encore une nouvelle chose à apprendre et à faire est superflu et inutile. Après tout, notre application de sondage fonctionne maintenant à satisfaction ; se préoccuper encore de créer des tests automatisés ne va pas l’améliorer. Si la création de l’application de sondage est la dernière œuvre de programmation Django que vous entreprenez, alors oui, vous pouvez vous passer d’apprendre à créer des tests automatisés. Mais si ce n’est pas le cas, alors c’est maintenant l’occasion d’apprendre cela.</p>\n<section id=\"tests-will-save-you-time\">\n<h4>Les tests vous feront gagner du temps<a class=\"heading-anchor\" href=\"#tests-will-save-you-time\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h4>\n<p>Jusqu’à un certain point, « contrôler que cela fonctionne apparemment » est un test satisfaisant. Dans une application plus sophistiquée, vous pouvez rencontrer des dizaines d’interactions complexes entre les différents composants.</p>\n<p>Une modification dans n’importe lequel de ces composants pourrait avoir des conséquences inattendues sur le comportement de l’application. Contrôler que « cela semble marcher » pourrait signifier la vérification de vingt combinaisons différentes de données de test pour être sûr que vous n’avez rien cassé - vous avez certainement mieux à faire.</p>\n<p>C’est particulièrement vrai alors que des tests automatisés pourraient faire cela pour vous en quelques secondes. Si quelque chose se passe mal, les tests vous aideront à identifier le code qui produit le comportement inapproprié.</p>\n<p>Parfois, le fait de laisser de côté votre productif et créatif travail de programmation pour affronter la tâche ingrate et peu motivante de l’écriture de tests, peut ressembler à une corvée, spécialement lorsque vous savez que votre code fonctionne correctement.</p>\n<p>Cependant, l’écriture de tests est bien plus rentable que de passer des heures à tester manuellement votre application ou à essayer d’identifier la cause d’un problème récemment découvert.</p>\n</section>\n<section id=\"tests-don-t-just-identify-problems-they-prevent-them\">\n<h4>Les tests ne font pas qu’identifier les problèmes, ils les préviennent<a class=\"heading-anchor\" href=\"#tests-don-t-just-identify-problems-they-prevent-them\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h4>\n<p>C’est une erreur de penser que les tests ne sont que l’aspect négatif du développement.</p>\n<p>Sans tests, le but ou le comportement attendu d’une application pourrait rester obscur. Même quand il s’agit de votre propre code, vous vous retrouverez parfois à fouiner un peu partout pour retrouver ce qu’il fait au juste.</p>\n<p>Les tests changent cela ; ils éclairent votre code de l’intérieur et lorsque quelque chose va de travers, ils mettent en relief la partie qui coince, <em>même lorsque vous n’avez même pas réalisé que quelque chose n’allait pas</em>.</p>\n</section>\n<section id=\"tests-make-your-code-more-attractive\">\n<h4>Les tests rendent votre code plus attractif<a class=\"heading-anchor\" href=\"#tests-make-your-code-more-attractive\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h4>\n<p>Vous avez peut-être créé un bout de logiciel extraordinaire, mais vous constaterez que beaucoup d’autres développeurs vont refuser de l’examiner parce qu’il ne contient pas de tests ; sans tests, ils ne lui font pas confiance. Jacob Kaplan-Moss, l’un des développeurs initiaux de Django, a dit : « Du code sans tests est par définition du code cassé ».</p>\n<p>Le fait que d’autres développeurs veulent voir des tests dans votre logiciel avant de le prendre au sérieux est une raison supplémentaire de commencer l’écriture de tests.</p>\n</section>\n<section id=\"tests-help-teams-work-together\">\n<h4>Les tests aident les équipes à travailler ensemble<a class=\"heading-anchor\" href=\"#tests-help-teams-work-together\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h4>\n<p>Les points précédents sont écrits du point de vue d’un développeur isolé maintenant une application. Les applications complexes sont maintenues par des équipes. Les tests garantissent que les collègues ne cassent votre code par mégarde (et aussi que vous ne cassiez le leur sans le vouloir). Si vous voulez gagner votre vie en tant que programmeur Django, vous devez être bon dans l’écriture de tests !</p>\n</section>\n</section>\n</section>\n<section id=\"basic-testing-strategies\">\n<h2>Stratégies élémentaires pour les tests<a class=\"heading-anchor\" href=\"#basic-testing-strategies\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>Il existe de nombreuses approches pour écrire des tests.</p>\n<p>Certains programmeurs suivent une discipline appelée « développement piloté par les tests » (test-driven development &lt;http://fr.wikipedia.org/wiki/Test_Driven_Development&gt;). Ils écrivent les tests avant de commencer à écrire le code. Cela peut paraître illogique, mais c’est un processus très semblable à ce que la plupart des gens font : ils décrivent un problème, puis ils écrivent du code pour le résoudre. Le développement piloté par les tests formalise le problème dans un cas de test Python.</p>\n<p>Plus souvent, un débutant dans les tests va créer du code, puis il décidera plus tard qu’il devrait ajouter des tests. Il aurait peut-être été préférable d’écrire des tests plus tôt, mais il n’est jamais trop tard pour commencer.</p>\n<p>Il est parfois difficile de trouver où commencer en écrivant des tests. Si vous avez écrit plusieurs milliers de lignes de code Python, choisir quoi tester n’est pas simple. Dans un tel cas, il peut être utile d’écrire le premier test au moment où vous effectuez votre prochaine modification, soit pour ajouter une nouvelle fonctionnalité ou pour corriger un bogue.</p>\n<p>Entrons dans le vif du sujet.</p>\n</section>\n<section id=\"writing-our-first-test\">\n<h2>Écriture du premier test<a class=\"heading-anchor\" href=\"#writing-our-first-test\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<section id=\"we-identify-a-bug\">\n<h3>Un bogue a été trouvé<a class=\"heading-anchor\" href=\"#we-identify-a-bug\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Heureusement, il y a un petit bogue dans l’application <code class=\"docutils literal notranslate\"><span class=\"pre\">polls</span></code>, tout prêt à être corrigé : la méthode <code class=\"docutils literal notranslate\"><span class=\"pre\">Question.was_published_recently()</span></code> renvoie <code class=\"docutils literal notranslate\"><span class=\"pre\">True</span></code> si l’objet <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> a été publié durant le jour précédent (ce qui est correct), mais également si le champ <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> de <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> est dans le futur (ce qui n’est évidemment pas juste).</p>\n<p>Confirmez l’existence du bogue en utilisant le <a class=\"reference internal\" href=\"/fr/3.1/ref/django-admin/#django-admin-shell\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">shell</span></code></a> pour contrôler la méthode sur une question dont la date se situe dans le futur :</p>\n<div class=\"console\" data-console><div class=\"console-panel\" data-platform=\"unix\"><p class=\"console-label\" id=\"console-0-unix-label\">Linux / macOS</p><div class=\"code-block\" data-language=\"console\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Shell</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Shell code\"><code><span class=\"gp\">$ </span>python<span class=\"w\"> </span>manage.py<span class=\"w\"> </span>shell\n</code></pre></div>\n</div><div class=\"console-panel\" data-platform=\"windows\"><p class=\"console-label\" id=\"console-0-windows-label\">Windows</p><div class=\"code-block\" data-language=\"doscon\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Windows</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Windows shell\"><code><span class=\"gp\">...\\&gt;</span> py manage.py shell\n</code></pre></div></div></div>\n<div class=\"code-block\" data-language=\"pycon\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python console</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python console code\"><code><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">datetime</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.utils</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">timezone</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">polls.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Question</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># create a Question instance with pub_date 30 days in the future</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">future_question</span> <span class=\"o\">=</span> <span class=\"n\">Question</span><span class=\"p\">(</span><span class=\"n\">pub_date</span><span class=\"o\">=</span><span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span> <span class=\"o\">+</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">30</span><span class=\"p\">))</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># was it published recently?</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">future_question</span><span class=\"o\">.</span><span class=\"n\">was_published_recently</span><span class=\"p\">()</span>\n<span class=\"go\">True</span>\n</code></pre></div>\n<p>Étant donné que ce qui est dans le futur n’est pas « récent », c’est clairement une erreur.</p>\n</section>\n<section id=\"create-a-test-to-expose-the-bug\">\n<h3>Création d’un test pour révéler le bogue<a class=\"heading-anchor\" href=\"#create-a-test-to-expose-the-bug\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Ce que nous venons de faire dans le <a class=\"reference internal\" href=\"/fr/3.1/ref/django-admin/#django-admin-shell\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">shell</span></code></a> pour tester le problème est exactement ce que nous pouvons faire dans un test automatisé ; transformons cette opération en un test automatisé.</p>\n<p>Un endroit conventionnel pour placer les tests d’une application est le fichier <code class=\"docutils literal notranslate\"><span class=\"pre\">tests.py</span></code> dans le répertoire de l’application. Le système de test va automatiquement trouver les tests dans tout fichier dont le nom commence par <code class=\"docutils literal notranslate\"><span class=\"pre\">test</span></code>.</p>\n<p>Placez ce qui suit dans le fichier <code class=\"docutils literal notranslate\"><span class=\"pre\">tests.py</span></code> de l’application <code class=\"docutils literal notranslate\"><span class=\"pre\">polls</span></code>:</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/tests.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">datetime</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.test</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">TestCase</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.utils</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">timezone</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Question</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionModelTests</span><span class=\"p\">(</span><span class=\"n\">TestCase</span><span class=\"p\">):</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_was_published_recently_with_future_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        was_published_recently() returns False for questions whose pub_date</span>\n<span class=\"sd\">        is in the future.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">time</span> <span class=\"o\">=</span> <span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span> <span class=\"o\">+</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">30</span><span class=\"p\">)</span>\n        <span class=\"n\">future_question</span> <span class=\"o\">=</span> <span class=\"n\">Question</span><span class=\"p\">(</span><span class=\"n\">pub_date</span><span class=\"o\">=</span><span class=\"n\">time</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertIs</span><span class=\"p\">(</span><span class=\"n\">future_question</span><span class=\"o\">.</span><span class=\"n\">was_published_recently</span><span class=\"p\">(),</span> <span class=\"kc\">False</span><span class=\"p\">)</span>\n</code></pre></figure>\n<p>Nous venons ici de créer une sous-classe de <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.TestCase\" title=\"django.test.TestCase\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">django.test.TestCase</span></code></a> contenant une méthode qui crée une instance <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> en renseignant <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> dans le futur. Nous vérifions ensuite le résultat de <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently()</span></code> qui <em>devrait</em> valoir <code class=\"docutils literal notranslate\"><span class=\"pre\">False</span></code>.</p>\n</section>\n<section id=\"running-tests\">\n<h3>Lancement des tests<a class=\"heading-anchor\" href=\"#running-tests\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Dans le terminal, nous pouvons lancer notre test :</p>\n<div class=\"console\" data-console><div class=\"console-panel\" data-platform=\"unix\"><p class=\"console-label\" id=\"console-1-unix-label\">Linux / macOS</p><div class=\"code-block\" data-language=\"console\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Shell</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Shell code\"><code><span class=\"gp\">$ </span>python<span class=\"w\"> </span>manage.py<span class=\"w\"> </span><span class=\"nb\">test</span><span class=\"w\"> </span>polls\n</code></pre></div>\n</div><div class=\"console-panel\" data-platform=\"windows\"><p class=\"console-label\" id=\"console-1-windows-label\">Windows</p><div class=\"code-block\" data-language=\"doscon\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Windows</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Windows shell\"><code><span class=\"gp\">...\\&gt;</span> py manage.py test polls\n</code></pre></div></div></div>\n<p>et vous devriez voir quelque chose comme :</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"n\">Creating</span> <span class=\"n\">test</span> <span class=\"n\">database</span> <span class=\"k\">for</span> <span class=\"n\">alias</span> <span class=\"s1\">&#39;default&#39;</span><span class=\"o\">...</span>\n<span class=\"n\">System</span> <span class=\"n\">check</span> <span class=\"n\">identified</span> <span class=\"n\">no</span> <span class=\"n\">issues</span> <span class=\"p\">(</span><span class=\"mi\">0</span> <span class=\"n\">silenced</span><span class=\"p\">)</span><span class=\"o\">.</span>\n<span class=\"n\">F</span>\n<span class=\"o\">======================================================================</span>\n<span class=\"n\">FAIL</span><span class=\"p\">:</span> <span class=\"n\">test_was_published_recently_with_future_question</span> <span class=\"p\">(</span><span class=\"n\">polls</span><span class=\"o\">.</span><span class=\"n\">tests</span><span class=\"o\">.</span><span class=\"n\">QuestionModelTests</span><span class=\"p\">)</span>\n<span class=\"o\">----------------------------------------------------------------------</span>\n<span class=\"n\">Traceback</span> <span class=\"p\">(</span><span class=\"n\">most</span> <span class=\"n\">recent</span> <span class=\"n\">call</span> <span class=\"n\">last</span><span class=\"p\">):</span>\n  <span class=\"n\">File</span> <span class=\"s2\">&quot;/path/to/mysite/polls/tests.py&quot;</span><span class=\"p\">,</span> <span class=\"n\">line</span> <span class=\"mi\">16</span><span class=\"p\">,</span> <span class=\"ow\">in</span> <span class=\"n\">test_was_published_recently_with_future_question</span>\n    <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertIs</span><span class=\"p\">(</span><span class=\"n\">future_question</span><span class=\"o\">.</span><span class=\"n\">was_published_recently</span><span class=\"p\">(),</span> <span class=\"kc\">False</span><span class=\"p\">)</span>\n<span class=\"ne\">AssertionError</span><span class=\"p\">:</span> <span class=\"kc\">True</span> <span class=\"ow\">is</span> <span class=\"ow\">not</span> <span class=\"kc\">False</span>\n\n<span class=\"o\">----------------------------------------------------------------------</span>\n<span class=\"n\">Ran</span> <span class=\"mi\">1</span> <span class=\"n\">test</span> <span class=\"ow\">in</span> <span class=\"mf\">0.001</span><span class=\"n\">s</span>\n\n<span class=\"n\">FAILED</span> <span class=\"p\">(</span><span class=\"n\">failures</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n<span class=\"n\">Destroying</span> <span class=\"n\">test</span> <span class=\"n\">database</span> <span class=\"k\">for</span> <span class=\"n\">alias</span> <span class=\"s1\">&#39;default&#39;</span><span class=\"o\">...</span>\n</code></pre></div>\n<aside class=\"admonition-different-error admonition\">\n<p class=\"admonition-title\">Erreur différente ?</p>\n<p>Si vous obtenez ici une erreur  <code class=\"docutils literal notranslate\"><span class=\"pre\">NameError</span></code>, vous avez peut-être manqué une étape à la <a class=\"reference internal\" href=\"/fr/3.1/intro/tutorial02/#tutorial02-import-timezone\"><span class=\"std std-ref\">partie 2 du tutoriel</span></a> où des importations de <code class=\"docutils literal notranslate\"><span class=\"pre\">datetime</span></code> et de <code class=\"docutils literal notranslate\"><span class=\"pre\">timezone</span></code> ont été ajoutées à <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/models.py</span></code>. Recopiez ces importations et essayez de relancer les tests.</p>\n</aside>\n<p>Voici ce qui s’est passé :</p>\n<ul class=\"simple\">\n<li><p>La commande <code class=\"docutils literal notranslate\"><span class=\"pre\">manage.py</span> <span class=\"pre\">test</span> <span class=\"pre\">polls</span></code> a cherché des tests dans l’application <code class=\"docutils literal notranslate\"><span class=\"pre\">polls</span></code> ;</p></li>\n<li><p>elle a trouvé une sous-classe de <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.TestCase\" title=\"django.test.TestCase\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">django.test.TestCase</span></code></a> ;</p></li>\n<li><p>elle a créé une base de données spéciale uniquement pour les tests ;</p></li>\n<li><p>elle a recherché des méthodes de test, celles dont le nom commence par <code class=\"docutils literal notranslate\"><span class=\"pre\">test</span></code> ;</p></li>\n<li><p>dans <code class=\"docutils literal notranslate\"><span class=\"pre\">test_was_published_recently_with_future_question</span></code>, elle a créé une instance <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code> dont le champ <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> est 30 jours dans le futur ;</p></li>\n<li><p>… et à l’aide de la méthode <code class=\"docutils literal notranslate\"><span class=\"pre\">assertIs()</span></code>, elle a découvert que sa méthode <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently()</span></code> renvoyait <code class=\"docutils literal notranslate\"><span class=\"pre\">True</span></code>, alors que nous souhaitions qu’elle renvoie <code class=\"docutils literal notranslate\"><span class=\"pre\">False</span></code>.</p></li>\n</ul>\n<p>Le test nous indique le nom du test qui a échoué ainsi que la ligne à laquelle l’échec s’est produit.</p>\n</section>\n<section id=\"fixing-the-bug\">\n<h3>Correction du bogue<a class=\"heading-anchor\" href=\"#fixing-the-bug\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Nous connaissons déjà le problème : <code class=\"docutils literal notranslate\"><span class=\"pre\">Question.was_published_recently()</span></code> devrait renvoyer <code class=\"docutils literal notranslate\"><span class=\"pre\">False</span></code> si sa <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> est dans le futur. Corrigez la méthode dans <code class=\"docutils literal notranslate\"><span class=\"pre\">models.py</span></code> afin qu’elle ne renvoie <code class=\"docutils literal notranslate\"><span class=\"pre\">True</span></code> que si la date est aussi dans le passé :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/models.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">was_published_recently</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n    <span class=\"n\">now</span> <span class=\"o\">=</span> <span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span>\n    <span class=\"k\">return</span> <span class=\"n\">now</span> <span class=\"o\">-</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span> <span class=\"o\">&lt;=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">pub_date</span> <span class=\"o\">&lt;=</span> <span class=\"n\">now</span>\n</code></pre></figure>\n<p>puis lancez à nouveau le test :</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"n\">Creating</span> <span class=\"n\">test</span> <span class=\"n\">database</span> <span class=\"k\">for</span> <span class=\"n\">alias</span> <span class=\"s1\">&#39;default&#39;</span><span class=\"o\">...</span>\n<span class=\"n\">System</span> <span class=\"n\">check</span> <span class=\"n\">identified</span> <span class=\"n\">no</span> <span class=\"n\">issues</span> <span class=\"p\">(</span><span class=\"mi\">0</span> <span class=\"n\">silenced</span><span class=\"p\">)</span><span class=\"o\">.</span>\n<span class=\"o\">.</span>\n<span class=\"o\">----------------------------------------------------------------------</span>\n<span class=\"n\">Ran</span> <span class=\"mi\">1</span> <span class=\"n\">test</span> <span class=\"ow\">in</span> <span class=\"mf\">0.001</span><span class=\"n\">s</span>\n\n<span class=\"n\">OK</span>\n<span class=\"n\">Destroying</span> <span class=\"n\">test</span> <span class=\"n\">database</span> <span class=\"k\">for</span> <span class=\"n\">alias</span> <span class=\"s1\">&#39;default&#39;</span><span class=\"o\">...</span>\n</code></pre></div>\n<p>Après avoir identifié un bogue, nous avons écrit un test qui le révèle et nous avons corrigé l’erreur dans le code pour que notre test réussisse.</p>\n<p>Bien d’autres choses pourraient aller de travers avec notre application à l’avenir, mais nous pouvons être sûrs que nous n’allons pas réintroduire cette erreur par mégarde, car en lançant le test, nous serons immédiatement avertis. Nous pouvons considérer que cette petite partie de l’application est assurée de rester fonctionnelle pour toujours.</p>\n</section>\n<section id=\"more-comprehensive-tests\">\n<h3>Des tests plus exhaustifs<a class=\"heading-anchor\" href=\"#more-comprehensive-tests\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Pendant que nous y sommes, nous pouvons assurer un peu plus le fonctionnement de la méthode <code class=\"docutils literal notranslate\"><span class=\"pre\">was_published_recently()</span></code> ; en fait, il serait réellement embarrassant si en corrigeant un bogue, nous en avions introduit un autre.</p>\n<p>Ajoutez deux méthodes de test supplémentaires dans la même classe pour tester le comportement de la méthode de manière plus complète :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/tests.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_was_published_recently_with_old_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">    was_published_recently() returns False for questions whose pub_date</span>\n<span class=\"sd\">    is older than 1 day.</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"n\">time</span> <span class=\"o\">=</span> <span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">,</span> <span class=\"n\">seconds</span><span class=\"o\">=</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n    <span class=\"n\">old_question</span> <span class=\"o\">=</span> <span class=\"n\">Question</span><span class=\"p\">(</span><span class=\"n\">pub_date</span><span class=\"o\">=</span><span class=\"n\">time</span><span class=\"p\">)</span>\n    <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertIs</span><span class=\"p\">(</span><span class=\"n\">old_question</span><span class=\"o\">.</span><span class=\"n\">was_published_recently</span><span class=\"p\">(),</span> <span class=\"kc\">False</span><span class=\"p\">)</span>\n\n<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_was_published_recently_with_recent_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">    was_published_recently() returns True for questions whose pub_date</span>\n<span class=\"sd\">    is within the last day.</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"n\">time</span> <span class=\"o\">=</span> <span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span> <span class=\"o\">-</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">hours</span><span class=\"o\">=</span><span class=\"mi\">23</span><span class=\"p\">,</span> <span class=\"n\">minutes</span><span class=\"o\">=</span><span class=\"mi\">59</span><span class=\"p\">,</span> <span class=\"n\">seconds</span><span class=\"o\">=</span><span class=\"mi\">59</span><span class=\"p\">)</span>\n    <span class=\"n\">recent_question</span> <span class=\"o\">=</span> <span class=\"n\">Question</span><span class=\"p\">(</span><span class=\"n\">pub_date</span><span class=\"o\">=</span><span class=\"n\">time</span><span class=\"p\">)</span>\n    <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertIs</span><span class=\"p\">(</span><span class=\"n\">recent_question</span><span class=\"o\">.</span><span class=\"n\">was_published_recently</span><span class=\"p\">(),</span> <span class=\"kc\">True</span><span class=\"p\">)</span>\n</code></pre></figure>\n<p>Et maintenant nous disposons de trois tests qui confirment que <code class=\"docutils literal notranslate\"><span class=\"pre\">Question.was_published_recently()</span></code> renvoie des valeurs correctes pour des questions passées, récentes et futures.</p>\n<p>Encore une fois, <code class=\"docutils literal notranslate\"><span class=\"pre\">polls</span></code> est une application minimale, mais quelle que soit la complexité de son évolution ou le code avec lequel elle devra interagir, nous avons maintenant une certaine garantie que la méthode pour laquelle nous avons écrit des tests se comportera de façon cohérente.</p>\n</section>\n</section>\n<section id=\"test-a-view\">\n<h2>Test d’une vue<a class=\"heading-anchor\" href=\"#test-a-view\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>L’application de sondage est peu regardante : elle publiera toute question, y compris celles dont le champ <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> est situé dans le futur. C’est à améliorer. Définir <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> dans le futur devrait signifier que la question sera publiée à ce moment, mais qu’elle ne doit pas être visible avant cela.</p>\n<section id=\"a-test-for-a-view\">\n<h3>Un test pour une vue<a class=\"heading-anchor\" href=\"#a-test-for-a-view\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>En corrigeant le bogue ci-dessus, nous avons d’abord écrit le test, puis le code pour le corriger. En fait, c’était un exemple de développement piloté par les tests, mais l’ordre dans lequel se font les choses n’est pas fondamental.</p>\n<p>Dans notre premier test, nous nous sommes concentrés étroitement sur le fonctionnement interne du code. Pour ce test, nous voulons contrôler son comportement tel qu’il se déroulerait avec un utilisateur depuis son navigateur.</p>\n<p>Avant d’essayer de corriger quoi que ce soit, examinons les outils à notre disposition.</p>\n</section>\n<section id=\"the-django-test-client\">\n<h3>Le client de test de Django<a class=\"heading-anchor\" href=\"#the-django-test-client\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Django fournit un <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.Client\" title=\"django.test.Client\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">Client</span></code></a> de test pour simuler l’interaction d’un utilisateur avec le code au niveau des vues. On peut l’utiliser dans <code class=\"docutils literal notranslate\"><span class=\"pre\">tests.py</span></code> ou même dans le <a class=\"reference internal\" href=\"/fr/3.1/ref/django-admin/#django-admin-shell\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">shell</span></code></a>.</p>\n<p>Nous commencerons encore une fois par le <a class=\"reference internal\" href=\"/fr/3.1/ref/django-admin/#django-admin-shell\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">shell</span></code></a>, où nous devons faire quelques opérations qui ne seront pas nécessaires dans <code class=\"docutils literal notranslate\"><span class=\"pre\">tests.py</span></code>. La première est de configurer l’environnement de test dans le <a class=\"reference internal\" href=\"/fr/3.1/ref/django-admin/#django-admin-shell\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">shell</span></code></a>:</p>\n<div class=\"console\" data-console><div class=\"console-panel\" data-platform=\"unix\"><p class=\"console-label\" id=\"console-2-unix-label\">Linux / macOS</p><div class=\"code-block\" data-language=\"console\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Shell</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Shell code\"><code><span class=\"gp\">$ </span>python<span class=\"w\"> </span>manage.py<span class=\"w\"> </span>shell\n</code></pre></div>\n</div><div class=\"console-panel\" data-platform=\"windows\"><p class=\"console-label\" id=\"console-2-windows-label\">Windows</p><div class=\"code-block\" data-language=\"doscon\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Windows</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Windows shell\"><code><span class=\"gp\">...\\&gt;</span> py manage.py shell\n</code></pre></div></div></div>\n<div class=\"code-block\" data-language=\"pycon\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python console</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python console code\"><code><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.test.utils</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">setup_test_environment</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">setup_test_environment</span><span class=\"p\">()</span>\n</code></pre></div>\n<p><a class=\"reference internal\" href=\"/fr/3.1/topics/testing/advanced/#django.test.utils.setup_test_environment\" title=\"django.test.utils.setup_test_environment\"><code class=\"xref py py-meth docutils literal notranslate\"><span class=\"pre\">setup_test_environment()</span></code></a> installe un moteur de rendu de gabarit qui va nous permettre d’examiner certains attributs supplémentaires des réponses, tels que <code class=\"docutils literal notranslate\"><span class=\"pre\">response.context</span></code> qui n’est normalement pas disponible. Notez que cette méthode <em>ne crée pas</em> de base de données de test, ce qui signifie que ce qui suit va être appliqué à la base de données existante et que par conséquent, le résultat peut légèrement différer en fonction des questions que vous avez déjà créées. Si le réglage <code class=\"docutils literal notranslate\"><span class=\"pre\">TIME_ZONE</span></code> dans <code class=\"docutils literal notranslate\"><span class=\"pre\">settings.py</span></code> n’est pas correct, il se peut que certains résultats soient faussés. Si vous n’avez pas pensé à le régler précédemment, vérifiez-le avant de continuer.</p>\n<p>Ensuite, il est nécessaire d’importer la classe Client de test (plus loin dans <code class=\"docutils literal notranslate\"><span class=\"pre\">tests.py</span></code>, nous utiliserons la classe <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.TestCase\" title=\"django.test.TestCase\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">django.test.TestCase</span></code></a> qui apporte son propre client, ce qui évitera cette étape) :</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.test</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Client</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># create an instance of the client for our use</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">client</span> <span class=\"o\">=</span> <span class=\"n\">Client</span><span class=\"p\">()</span>\n</code></pre></div>\n<p>Ceci fait, nous pouvons demander au client de faire certaines tâches pour nous :</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># get a response from &#39;/&#39;</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s1\">&#39;/&#39;</span><span class=\"p\">)</span>\n<span class=\"go\">Not Found: /</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># we should expect a 404 from that address; if you instead see an</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># &quot;Invalid HTTP_HOST header&quot; error and a 400 response, you probably</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># omitted the setup_test_environment() call described earlier.</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">status_code</span>\n<span class=\"go\">404</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># on the other hand we should expect to find something at &#39;/polls/&#39;</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"c1\"># we&#39;ll use &#39;reverse()&#39; rather than a hardcoded URL</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.urls</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">reverse</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:index&#39;</span><span class=\"p\">))</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">status_code</span>\n<span class=\"go\">200</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">content</span>\n<span class=\"go\">b&#39;\\n    &lt;ul&gt;\\n    \\n        &lt;li&gt;&lt;a href=&quot;/polls/1/&quot;&gt;What&amp;#x27;s up?&lt;/a&gt;&lt;/li&gt;\\n    \\n    &lt;/ul&gt;\\n\\n&#39;</span>\n<span class=\"gp\">&gt;&gt;&gt; </span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">context</span><span class=\"p\">[</span><span class=\"s1\">&#39;latest_question_list&#39;</span><span class=\"p\">]</span>\n<span class=\"go\">&lt;QuerySet [&lt;Question: What&#39;s up?&gt;]&gt;</span>\n</code></pre></div>\n</section>\n<section id=\"improving-our-view\">\n<h3>Amélioration de la vue<a class=\"heading-anchor\" href=\"#improving-our-view\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>La liste des sondages montre aussi les sondages pas encore publiés (c’est-à-dire ceux dont le champ <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> est dans le futur). Corrigeons cela.</p>\n<p>Dans le <a class=\"reference internal\" href=\"/fr/3.1/intro/tutorial04/\"><span class=\"doc\">tutoriel 4</span></a>, nous avons introduit une vue basée sur la classe <a class=\"reference internal\" href=\"/fr/3.1/ref/class-based-views/generic-display/#django.views.generic.list.ListView\" title=\"django.views.generic.list.ListView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">ListView</span></code></a>:</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/views.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">IndexView</span><span class=\"p\">(</span><span class=\"n\">generic</span><span class=\"o\">.</span><span class=\"n\">ListView</span><span class=\"p\">):</span>\n    <span class=\"n\">template_name</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;polls/index.html&#39;</span>\n    <span class=\"n\">context_object_name</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;latest_question_list&#39;</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_queryset</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;Return the last five published questions.&quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"n\">Question</span><span class=\"o\">.</span><span class=\"n\">objects</span><span class=\"o\">.</span><span class=\"n\">order_by</span><span class=\"p\">(</span><span class=\"s1\">&#39;-pub_date&#39;</span><span class=\"p\">)[:</span><span class=\"mi\">5</span><span class=\"p\">]</span>\n</code></pre></figure>\n<p>Nous devons corriger la méthode <code class=\"docutils literal notranslate\"><span class=\"pre\">get_queryset()</span></code> pour qu’elle vérifie aussi la date en la comparant avec <code class=\"docutils literal notranslate\"><span class=\"pre\">timezone.now()</span></code>. Nous devons d’abord ajouter une importation :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/views.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.utils</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">timezone</span>\n</code></pre></figure>\n<p>puis nous devons corriger la méthode <code class=\"docutils literal notranslate\"><span class=\"pre\">get_queryset</span></code> de cette façon :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/views.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_queryset</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">    Return the last five published questions (not including those set to be</span>\n<span class=\"sd\">    published in the future).</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"k\">return</span> <span class=\"n\">Question</span><span class=\"o\">.</span><span class=\"n\">objects</span><span class=\"o\">.</span><span class=\"n\">filter</span><span class=\"p\">(</span>\n        <span class=\"n\">pub_date__lte</span><span class=\"o\">=</span><span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span>\n    <span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">order_by</span><span class=\"p\">(</span><span class=\"s1\">&#39;-pub_date&#39;</span><span class=\"p\">)[:</span><span class=\"mi\">5</span><span class=\"p\">]</span>\n</code></pre></figure>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">Question.objects.filter(pub_date__lte=timezone.now())</span></code> renvoie un queryset contenant les questions dont le champ <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> est plus petit ou égal (c’est-à-dire plus ancien ou égal) à <code class=\"docutils literal notranslate\"><span class=\"pre\">timezone.now</span></code>.</p>\n</section>\n<section id=\"testing-our-new-view\">\n<h3>Test de la nouvelle vue<a class=\"heading-anchor\" href=\"#testing-our-new-view\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Vous pouvez maintenant vérifier vous-même que tout fonctionne comme prévu en lançant <code class=\"docutils literal notranslate\"><span class=\"pre\">runserver</span></code> et en accédant au site depuis votre navigateur. Créez des questions avec des dates dans le passé et dans le futur et vérifiez que seuls celles qui ont été publiées apparaissent dans la liste. Mais vous ne voulez pas faire ce travail de test manuel <em>chaque fois que vous effectuez une modification qui pourrait affecter ce comportement</em>, créons donc aussi un test basé sur le contenu de notre session <a class=\"reference internal\" href=\"/fr/3.1/ref/django-admin/#django-admin-shell\"><code class=\"xref std std-djadmin docutils literal notranslate\"><span class=\"pre\">shell</span></code></a> précédente.</p>\n<p>Ajoutez ce qui suit à <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/tests.py</span></code>:</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/tests.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.urls</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">reverse</span>\n</code></pre></figure>\n<p>et nous allons créer une fonction raccourci pour créer des questions, ainsi qu’une nouvelle classe de test :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/tests.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">    Create a question with the given `question_text` and published the</span>\n<span class=\"sd\">    given number of `days` offset to now (negative for questions published</span>\n<span class=\"sd\">    in the past, positive for questions that have yet to be published).</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"n\">time</span> <span class=\"o\">=</span> <span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span> <span class=\"o\">+</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span class=\"n\">timedelta</span><span class=\"p\">(</span><span class=\"n\">days</span><span class=\"o\">=</span><span class=\"n\">days</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">Question</span><span class=\"o\">.</span><span class=\"n\">objects</span><span class=\"o\">.</span><span class=\"n\">create</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"n\">question_text</span><span class=\"p\">,</span> <span class=\"n\">pub_date</span><span class=\"o\">=</span><span class=\"n\">time</span><span class=\"p\">)</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionIndexViewTests</span><span class=\"p\">(</span><span class=\"n\">TestCase</span><span class=\"p\">):</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_no_questions</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        If no questions exist, an appropriate message is displayed.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:index&#39;</span><span class=\"p\">))</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertEqual</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">status_code</span><span class=\"p\">,</span> <span class=\"mi\">200</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertContains</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">,</span> <span class=\"s2\">&quot;No polls are available.&quot;</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertQuerysetEqual</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">context</span><span class=\"p\">[</span><span class=\"s1\">&#39;latest_question_list&#39;</span><span class=\"p\">],</span> <span class=\"p\">[])</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_past_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        Questions with a pub_date in the past are displayed on the</span>\n<span class=\"sd\">        index page.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s2\">&quot;Past question.&quot;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=-</span><span class=\"mi\">30</span><span class=\"p\">)</span>\n        <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:index&#39;</span><span class=\"p\">))</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertQuerysetEqual</span><span class=\"p\">(</span>\n            <span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">context</span><span class=\"p\">[</span><span class=\"s1\">&#39;latest_question_list&#39;</span><span class=\"p\">],</span>\n            <span class=\"p\">[</span><span class=\"s1\">&#39;&lt;Question: Past question.&gt;&#39;</span><span class=\"p\">]</span>\n        <span class=\"p\">)</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_future_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        Questions with a pub_date in the future aren&#39;t displayed on</span>\n<span class=\"sd\">        the index page.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s2\">&quot;Future question.&quot;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">30</span><span class=\"p\">)</span>\n        <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:index&#39;</span><span class=\"p\">))</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertContains</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">,</span> <span class=\"s2\">&quot;No polls are available.&quot;</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertQuerysetEqual</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">context</span><span class=\"p\">[</span><span class=\"s1\">&#39;latest_question_list&#39;</span><span class=\"p\">],</span> <span class=\"p\">[])</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_future_question_and_past_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        Even if both past and future questions exist, only past questions</span>\n<span class=\"sd\">        are displayed.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s2\">&quot;Past question.&quot;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=-</span><span class=\"mi\">30</span><span class=\"p\">)</span>\n        <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s2\">&quot;Future question.&quot;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">30</span><span class=\"p\">)</span>\n        <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:index&#39;</span><span class=\"p\">))</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertQuerysetEqual</span><span class=\"p\">(</span>\n            <span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">context</span><span class=\"p\">[</span><span class=\"s1\">&#39;latest_question_list&#39;</span><span class=\"p\">],</span>\n            <span class=\"p\">[</span><span class=\"s1\">&#39;&lt;Question: Past question.&gt;&#39;</span><span class=\"p\">]</span>\n        <span class=\"p\">)</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_two_past_questions</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        The questions index page may display multiple questions.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s2\">&quot;Past question 1.&quot;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=-</span><span class=\"mi\">30</span><span class=\"p\">)</span>\n        <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s2\">&quot;Past question 2.&quot;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=-</span><span class=\"mi\">5</span><span class=\"p\">)</span>\n        <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:index&#39;</span><span class=\"p\">))</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertQuerysetEqual</span><span class=\"p\">(</span>\n            <span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">context</span><span class=\"p\">[</span><span class=\"s1\">&#39;latest_question_list&#39;</span><span class=\"p\">],</span>\n            <span class=\"p\">[</span><span class=\"s1\">&#39;&lt;Question: Past question 2.&gt;&#39;</span><span class=\"p\">,</span> <span class=\"s1\">&#39;&lt;Question: Past question 1.&gt;&#39;</span><span class=\"p\">]</span>\n        <span class=\"p\">)</span>\n</code></pre></figure>\n<p>Examinons plus en détails certaines de ces méthodes.</p>\n<p>Tout d’abord, la fonction raccourci <code class=\"docutils literal notranslate\"><span class=\"pre\">create_question</span></code> permet d’éviter de répéter plusieurs fois le processus de création de questions.</p>\n<p><code class=\"docutils literal notranslate\"><span class=\"pre\">test_no_questions</span></code> ne crée aucune question, mais vérifie le message : « No polls are available. » et que la liste <code class=\"docutils literal notranslate\"><span class=\"pre\">latest_question_list</span></code> est vide. Notez que la classe <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.TestCase\" title=\"django.test.TestCase\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">django.test.TestCase</span></code></a> fournit quelques méthodes d’assertion supplémentaires. Dans ces exemples, nous utilisons <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.SimpleTestCase.assertContains\" title=\"django.test.SimpleTestCase.assertContains\"><code class=\"xref py py-meth docutils literal notranslate\"><span class=\"pre\">assertContains()</span></code></a> et <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.TransactionTestCase.assertQuerysetEqual\" title=\"django.test.TransactionTestCase.assertQuerysetEqual\"><code class=\"xref py py-meth docutils literal notranslate\"><span class=\"pre\">assertQuerysetEqual()</span></code></a>.</p>\n<p>Dans <code class=\"docutils literal notranslate\"><span class=\"pre\">test_past_question</span></code>, nous créons une question et vérifions qu’elle apparaît dans la liste.</p>\n<p>Dans <code class=\"docutils literal notranslate\"><span class=\"pre\">test_future_question</span></code>, nous créons une question avec <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> dans le futur. La base de données est réinitialisée pour chaque méthode de test, ce qui explique que la première question n’est plus disponible et que la page d’index n’affiche plus de question.</p>\n<p>Et ainsi de suite. En pratique, nous utilisons les tests pour raconter des histoires d’interactions entre des saisies dans l’interface d’administration et d’un utilisateur parcourant le site, en contrôlant qu’à chaque état ou changement d’état du système, les résultats attendus apparaissent.</p>\n</section>\n<section id=\"testing-the-detailview\">\n<h3>Test de  <code class=\"docutils literal notranslate\"><span class=\"pre\">DetailView</span></code><a class=\"heading-anchor\" href=\"#testing-the-detailview\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Le code marche bien maintenant. Cependant, même si les questions futures n’apparaissent pas sur la page <em>index</em>, les utilisateurs peuvent toujours y accéder s’ils savent ou devinent la bonne URL. Nous avons donc besoin d’une contrainte semblable dans <code class=\"docutils literal notranslate\"><span class=\"pre\">DetailView</span></code> :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/views.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">DetailView</span><span class=\"p\">(</span><span class=\"n\">generic</span><span class=\"o\">.</span><span class=\"n\">DetailView</span><span class=\"p\">):</span>\n    <span class=\"o\">...</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_queryset</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        Excludes any questions that aren&#39;t published yet.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"n\">Question</span><span class=\"o\">.</span><span class=\"n\">objects</span><span class=\"o\">.</span><span class=\"n\">filter</span><span class=\"p\">(</span><span class=\"n\">pub_date__lte</span><span class=\"o\">=</span><span class=\"n\">timezone</span><span class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">())</span>\n</code></pre></figure>\n<p>Il est ensuite nécessaire d’ajouter quelques tests, pour contrôler qu’une question dont  <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> est dans le passé peut être affichée, mais que si <code class=\"docutils literal notranslate\"><span class=\"pre\">pub_date</span></code> est dans le futur, elle ne le sera pas :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"python\"><figcaption class=\"code-block-caption\">polls/tests.py</figcaption>\n<div class=\"code-block-toolbar\"><span class=\"code-block-language\">Python</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Python code\"><code><span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">QuestionDetailViewTests</span><span class=\"p\">(</span><span class=\"n\">TestCase</span><span class=\"p\">):</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_future_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        The detail view of a question with a pub_date in the future</span>\n<span class=\"sd\">        returns a 404 not found.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">future_question</span> <span class=\"o\">=</span> <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s1\">&#39;Future question.&#39;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=</span><span class=\"mi\">5</span><span class=\"p\">)</span>\n        <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:detail&#39;</span><span class=\"p\">,</span> <span class=\"n\">args</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"n\">future_question</span><span class=\"o\">.</span><span class=\"n\">id</span><span class=\"p\">,))</span>\n        <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertEqual</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"o\">.</span><span class=\"n\">status_code</span><span class=\"p\">,</span> <span class=\"mi\">404</span><span class=\"p\">)</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">test_past_question</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        The detail view of a question with a pub_date in the past</span>\n<span class=\"sd\">        displays the question&#39;s text.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">past_question</span> <span class=\"o\">=</span> <span class=\"n\">create_question</span><span class=\"p\">(</span><span class=\"n\">question_text</span><span class=\"o\">=</span><span class=\"s1\">&#39;Past Question.&#39;</span><span class=\"p\">,</span> <span class=\"n\">days</span><span class=\"o\">=-</span><span class=\"mi\">5</span><span class=\"p\">)</span>\n        <span class=\"n\">url</span> <span class=\"o\">=</span> <span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:detail&#39;</span><span class=\"p\">,</span> <span class=\"n\">args</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"n\">past_question</span><span class=\"o\">.</span><span class=\"n\">id</span><span class=\"p\">,))</span>\n        <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">client</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">url</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">assertContains</span><span class=\"p\">(</span><span class=\"n\">response</span><span class=\"p\">,</span> <span class=\"n\">past_question</span><span class=\"o\">.</span><span class=\"n\">question_text</span><span class=\"p\">)</span>\n</code></pre></figure>\n</section>\n<section id=\"ideas-for-more-tests\">\n<h3>Idées pour d’autres tests<a class=\"heading-anchor\" href=\"#ideas-for-more-tests\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Nous devrions ajouter une méthode <code class=\"docutils literal notranslate\"><span class=\"pre\">get_queryset</span></code> similaire à <code class=\"docutils literal notranslate\"><span class=\"pre\">ResultsView</span></code> et créer une nouvelle classe de test pour cette vue. Elle sera très semblable à celle que nous venons de créer ; en fait, il y aura beaucoup de répétition.</p>\n<p>Nous pourrions aussi améliorer notre application d’autres manières, en ajoutant des tests au fur et à mesure. Par exemple, il est stupide de pouvoir publier des questions sur le site sans choix. Nos vues pourraient donc vérifier cela et exclure de telles questions. Les tests créeraient une question sans choix et testeraient qu’elle n’est pas publiée ; de même, il s’agirait de créer une question <em>avec</em> des choix et tester qu’elle <em>est</em> bien publiée.</p>\n<p>Peut-être que les utilisateurs connectés dans l’interface d’administration pourraient être autorisés à voir les questions non publiées, mais pas les visiteurs ordinaires. C’est toujours le même principe, tout ce qui est ajouté au logiciel devrait être accompagné par un test, que ce soit en écrivant d’abord le test puis en écrivant le code pour réussir le test, ou en travaillant d’abord sur la logique du code et en écrivant ensuite le test pour prouver le bon fonctionnement.</p>\n<p>À un certain stade, vous allez regarder vos tests et vous demander si votre code ne souffre pas de surabondance de tests, ce qui nous amène à :</p>\n</section>\n</section>\n<section id=\"when-testing-more-is-better\">\n<h2>Pour les tests, abondance de biens ne nuit pas<a class=\"heading-anchor\" href=\"#when-testing-more-is-better\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>En apparence, les tests peuvent avoir l’air de croître sans limites. À ce rythme, il y aura bientôt plus de code dans nos tests que dans notre application ; et la répétition est laide, comparée à la brièveté élégante du reste du code.</p>\n<p><strong>Ça n’a aucune importance</strong>. Laissez-les grandir. Dans la plupart des cas, vous pouvez écrire un test une fois et ne plus y penser. Il continuera à jouer son précieux rôle tout au long du développement de votre programme.</p>\n<p>Les tests devront parfois être mis à jour. Supposons que nous corrigeons nos vues pour que seules les questions avec choix soient publiées. Dans ce cas, de nombreux tests existants vont échouer, <em>ce qui nous informera exactement au sujet des tests qui devront être mis à jour</em> ; dans cette optique, on peut dire que les tests prennent soin d’eux-même.</p>\n<p>Au pire, au cours du développement, vous pouvez constater que certains tests deviennent redondants. Ce n’est même pas un problème. Dans les tests, la redondance est une <em>bonne</em> chose.</p>\n<p>Tant que vos tests sont logiquement disposés, ils ne deviendront pas ingérables. Quelques bons principes à garder en tête :</p>\n<ul class=\"simple\">\n<li><p>une classe de test séparée pour chaque modèle ou vue ;</p></li>\n<li><p>une méthode de test séparée pour chaque ensemble de conditions que vous voulez tester ;</p></li>\n<li><p>des noms de méthodes de test qui indiquent ce qu’elles font.</p></li>\n</ul>\n</section>\n<section id=\"further-testing\">\n<h2>Encore plus de tests<a class=\"heading-anchor\" href=\"#further-testing\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>Ce tutoriel ne fait qu’introduire à quelques concepts de base des tests. Il y a encore beaucoup plus à faire et d’autres outils très utiles à votre disposition pour effectuer des choses plus perfectionnées.</p>\n<p>Par exemple, bien que les tests présentés ici couvrent une partie de la logique interne d’un modèle et la manière dont les vues publient de l’information, vous pouvez utiliser un système basé sur de vrais navigateurs comme <a class=\"reference external\" href=\"http://seleniumhq.org/\">Selenium</a> pour tester la façon dont le code HTML est vraiment rendu dans un navigateur. Ces outils permettent de tester plus que le comportement du seul code Django, mais aussi, par exemple, du code JavaScript. C’est assez impressionnant de voir les tests lancer un navigateur et commencer d’interagir avec votre site, comme si un être humain invisible le pilotait ! Django propose la classe <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/tools/#django.test.LiveServerTestCase\" title=\"django.test.LiveServerTestCase\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">LiveServerTestCase</span></code></a> pour faciliter l’intégration avec des outils comme Selenium.</p>\n<p>Si votre application est complexe, il peut être utile de lancer automatiquement les tests lors de chaque commit dans l’optique d’une <a class=\"reference external\" href=\"https://en.wikipedia.org/wiki/Continuous_integration\">intégration continue</a>), afin que le contrôle qualité puisse être lui-même automatisé, au moins partiellement.</p>\n<p>Une bonne façon de détecter des parties d’application non couvertes par les tests est de contrôler la couverture du code. Cela aide aussi à identifier du code fragile, ou même mort. Si vous ne pouvez pas tester un bout de code, cela signifie généralement que le code doit être réarrangé ou supprimé. Le taux de couverture aide à identifier le code inutilisé. Consultez <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/advanced/#topics-testing-code-coverage\"><span class=\"std std-ref\">Intégration avec coverage.py</span></a> pour plus de détails.</p>\n<p><a class=\"reference internal\" href=\"/fr/3.1/topics/testing/\"><span class=\"doc\">Django et les tests</span></a> contient des informations complètes au sujet des tests.</p>\n</section>\n<section id=\"what-s-next\">\n<h2>Et ensuite ?<a class=\"heading-anchor\" href=\"#what-s-next\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>Pour des détails complets sur les tests, consultez <a class=\"reference internal\" href=\"/fr/3.1/topics/testing/\"><span class=\"doc\">Django et les tests</span></a>.</p>\n<p>Lorsque vous êtes à l’aise avec les tests de vues Django, lisez la <a class=\"reference internal\" href=\"/fr/3.1/intro/tutorial06/\"><span class=\"doc\">6ème partie de ce tutoriel</span></a> pour en savoir plus sur la gestion des fichiers statiques.</p>\n</section>","rootId":"writing-your-first-django-app-part-5","toc":[{"title":"Introduction aux tests automatisés","anchor":"introducing-automated-testing","children":[{"title":"Que sont les tests automatisés ?","anchor":"what-are-automated-tests","children":[]},{"title":"Pourquoi faut-il créer des tests","anchor":"why-you-need-to-create-tests","children":[{"title":"Les tests vous feront gagner du temps","anchor":"tests-will-save-you-time","children":[]},{"title":"Les tests ne font pas qu’identifier les problèmes, ils les préviennent","anchor":"tests-don-t-just-identify-problems-they-prevent-them","children":[]},{"title":"Les tests rendent votre code plus attractif","anchor":"tests-make-your-code-more-attractive","children":[]},{"title":"Les tests aident les équipes à travailler ensemble","anchor":"tests-help-teams-work-together","children":[]}]}]},{"title":"Stratégies élémentaires pour les tests","anchor":"basic-testing-strategies","children":[]},{"title":"Écriture du premier test","anchor":"writing-our-first-test","children":[{"title":"Un bogue a été trouvé","anchor":"we-identify-a-bug","children":[]},{"title":"Création d’un test pour révéler le bogue","anchor":"create-a-test-to-expose-the-bug","children":[]},{"title":"Lancement des tests","anchor":"running-tests","children":[]},{"title":"Correction du bogue","anchor":"fixing-the-bug","children":[]},{"title":"Des tests plus exhaustifs","anchor":"more-comprehensive-tests","children":[]}]},{"title":"Test d’une vue","anchor":"test-a-view","children":[{"title":"Un test pour une vue","anchor":"a-test-for-a-view","children":[]},{"title":"Le client de test de Django","anchor":"the-django-test-client","children":[]},{"title":"Amélioration de la vue","anchor":"improving-our-view","children":[]},{"title":"Test de la nouvelle vue","anchor":"testing-our-new-view","children":[]},{"title":"Test de  DetailView","anchor":"testing-the-detailview","children":[]},{"title":"Idées pour d’autres tests","anchor":"ideas-for-more-tests","children":[]}]},{"title":"Pour les tests, abondance de biens ne nuit pas","anchor":"when-testing-more-is-better","children":[]},{"title":"Encore plus de tests","anchor":"further-testing","children":[]},{"title":"Et ensuite ?","anchor":"what-s-next","children":[]}],"breadcrumbs":[{"docname":"intro/index","title":"Premiers pas","url":"/fr/3.1/intro/"}],"prev":{"docname":"intro/tutorial04","title":"Écriture de votre première application Django, 4ème partie","url":"/fr/3.1/intro/tutorial04/"},"next":{"docname":"intro/tutorial06","title":"Écriture de votre première application Django, 6ème partie","url":"/fr/3.1/intro/tutorial06/"},"formats":{"html":"/fr/3.1/intro/tutorial05/","markdown":"/fr/3.1/intro/tutorial05.md","json":"/fr/3.1/intro/tutorial05.json"},"source":"https://github.com/django/django/blob/stable/3.1.x/docs/intro/tutorial05.txt","official":"https://docs.djangoproject.com/fr/3.1/intro/tutorial05/","inVersions":["6.1","6.0","5.2","5.1","5.0","4.2","4.1","4.0","3.2","3.1","3.0","2.2","2.1","2.0","1.11","1.10","1.9"],"inLocales":["en","zh-hans","fr","ja","id","pt-br","ko","es","el","pl"]}