<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Špela Giacomelli</title>
    <description>The latest articles on DEV Community by Špela Giacomelli (@girlthatlovestocode).</description>
    <link>https://dev.to/girlthatlovestocode</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1231887%2F8cf69eaf-6734-4029-bcf4-a838c220e711.jpg</url>
      <title>DEV Community: Špela Giacomelli</title>
      <link>https://dev.to/girlthatlovestocode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/girlthatlovestocode"/>
    <language>en</language>
    <item>
      <title>An Introduction to Testing with Django for Python</title>
      <dc:creator>Špela Giacomelli</dc:creator>
      <pubDate>Wed, 14 Feb 2024 14:00:00 +0000</pubDate>
      <link>https://dev.to/appsignal/an-introduction-to-testing-with-django-for-python-26k8</link>
      <guid>https://dev.to/appsignal/an-introduction-to-testing-with-django-for-python-26k8</guid>
      <description>&lt;p&gt;In a world of ever-changing technology, testing is an integral part of writing robust and reliable software. Tests verify that your code behaves as expected, make it easier to maintain and refactor code, and serve as&lt;br&gt;
documentation for your code.&lt;/p&gt;

&lt;p&gt;There are two widely used testing frameworks for testing Django applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django's built-in test framework, built on Python's unittest&lt;/li&gt;
&lt;li&gt;Pytest, combined with pytest-django&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we will see how both work.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;
&lt;h2&gt;
  
  
  What Will We Test?
&lt;/h2&gt;

&lt;p&gt;All the tests we'll observe will use the same code, a &lt;em&gt;BookList&lt;/em&gt; endpoint.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Book&lt;/code&gt; model has &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;, and &lt;code&gt;date_published&lt;/code&gt; fields. Note that the default ordering is set by &lt;code&gt;date_published&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# models.py
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;date_published&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ordering&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date_published&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The view is just a simple &lt;code&gt;ListView&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.views.generic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ListView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookListView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ListView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the test examples will test the endpoint, we also need the URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# urls.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;books/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BookListView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_view&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now for the tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unittest for Python
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.python.org/3/library/unittest.html"&gt;Unittest&lt;/a&gt; is Python's built-in testing framework. Django extends it with &lt;a href="https://docs.djangoproject.com/en/4.2/topics/testing/"&gt;some of its own functionality&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Initially, you might be confused about what methods belong to unittest and what are Django's extensions.&lt;/p&gt;

&lt;p&gt;Unittest provides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.python.org/3/library/unittest.html#test-cases"&gt;TestCase&lt;/a&gt;, serving as a base class.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setUp()&lt;/code&gt; and &lt;code&gt;tearDown()&lt;/code&gt; methods for the code you want to execute before or after each test method.
&lt;code&gt;setUpClass()&lt;/code&gt; and &lt;code&gt;tearDownClass()&lt;/code&gt; also run once per whole &lt;code&gt;TestClass&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Multiple types of &lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEqual"&gt;assertions&lt;/a&gt;, such
as &lt;code&gt;assertEqual&lt;/code&gt;, &lt;code&gt;assertAlmostEqual&lt;/code&gt;, and &lt;code&gt;assertIsInstance&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The core part of the Django testing framework is &lt;a href="https://docs.djangoproject.com/en/4.2/topics/testing/tools/#testcase"&gt;TestCase&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Since it subclasses &lt;code&gt;unittest.TestCase&lt;/code&gt;, &lt;code&gt;TestCase&lt;/code&gt; has all the same functionality but also builds on top of it, with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/4.2/topics/testing/tools/#django.test.TestCase.setUpTestData"&gt;setUpTestData class method&lt;/a&gt;: This creates data once per &lt;code&gt;TestCase&lt;/code&gt; (unlike &lt;code&gt;setUp&lt;/code&gt;, which creates test data once per test).
Using &lt;code&gt;setUpTestData&lt;/code&gt; can significantly speed up your tests.&lt;/li&gt;
&lt;li&gt;Test data loaded via &lt;a href="https://docs.djangoproject.com/en/4.2/topics/testing/tools/#fixture-loading"&gt;fixtures&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Django-specific assertions, such as &lt;code&gt;assertQuerySetEqual&lt;/code&gt;, &lt;code&gt;assertFormError&lt;/code&gt;, and &lt;code&gt;assertContains&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Temporary &lt;a href="https://docs.djangoproject.com/en/4.2/topics/testing/tools/#overriding-settings"&gt;override settings&lt;/a&gt; you can set up during a test run.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Django's testing framework also provides a &lt;a href="https://docs.djangoproject.com/en/4.2/topics/testing/tools/#the-test-client"&gt;Client&lt;/a&gt; that doesn't rely on &lt;code&gt;TestCase&lt;/code&gt; and so can be used with pytest. &lt;code&gt;Client&lt;/code&gt; serves as a dummy browser, allowing users to create &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Tests Are Built with Unittest
&lt;/h3&gt;

&lt;p&gt;Unittest supports test discovery, but the following rules must be followed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The test should be in a file named with a &lt;code&gt;test&lt;/code&gt; prefix.&lt;/li&gt;
&lt;li&gt;The test must be within a class that subclasses &lt;code&gt;unittest.TestCase&lt;/code&gt; (that includes using &lt;code&gt;django.TestCase&lt;/code&gt;). Including 'Test' in the class name is not mandatory.&lt;/li&gt;
&lt;li&gt;The name of the test method must start with &lt;code&gt;test_&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see what tests written with unittest look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# tests.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BookListTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Jane&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Austen&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;second_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Emma&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1815&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pride and Prejudice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1813&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_book_list_returns_all_books&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;response_book_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_book_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;second_book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_book_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_book_list&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_book_list_ordered_by_date_published&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertQuerySetEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;second_book&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You must always put your test functions inside a class that extends a &lt;code&gt;TestCase&lt;/code&gt; class. Although it's not required, we've included 'Test' in our class name so it's easily recognizable as a test class at first glance. Notice that the &lt;code&gt;TestCase&lt;/code&gt; is imported from Django, not unittest — you want access to additional Django functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Happening Here?
&lt;/h3&gt;

&lt;p&gt;This &lt;code&gt;TestCase&lt;/code&gt; aims to check whether the &lt;code&gt;book_list&lt;/code&gt; endpoint works correctly. There are two tests — one checks that all &lt;code&gt;Book&lt;/code&gt; objects in the database are included in the response as &lt;code&gt;book_list&lt;/code&gt;, and the second checks that the books are listed by ascending publishing date.&lt;/p&gt;

&lt;p&gt;Although the tests appear similar, they evaluate two distinct functionalities, which should not be combined into a single test.&lt;/p&gt;

&lt;p&gt;Since both tests need the same data in the database, we use the class method &lt;code&gt;setUpTestData&lt;/code&gt; for preparing data — both tests will have access to it without repeating the process twice.&lt;/p&gt;

&lt;p&gt;Unlike the two &lt;code&gt;Book&lt;/code&gt; objects, the &lt;code&gt;Author&lt;/code&gt; object is not needed outside the setup method, so we don't set it as an instance attribute. I switched the order of the two books to ensure that the correct order isn't accidental.&lt;/p&gt;

&lt;p&gt;The two tests look very similar — they both make a &lt;code&gt;GET&lt;/code&gt; request to the same URL and extract the &lt;code&gt;book_list&lt;/code&gt; from &lt;code&gt;context&lt;/code&gt;. Methods inside the &lt;code&gt;TestCase&lt;/code&gt; class automatically have access to &lt;code&gt;client&lt;/code&gt;, which is used to make a request.&lt;/p&gt;

&lt;p&gt;Up to this point, both tests did the same thing — but the assertions differ.&lt;/p&gt;

&lt;p&gt;The first test asserts that both books are in the response context, using &lt;code&gt;assertIn&lt;/code&gt;. It also ensures the length of the &lt;code&gt;response.context['book_list']&lt;/code&gt; object. The second test uses Django's &lt;code&gt;assertQuerySetEqual&lt;/code&gt;. This assertion checks the ordering by default, so it does exactly what we need.&lt;/p&gt;

&lt;p&gt;Both tests also check the &lt;code&gt;status_code&lt;/code&gt;, so you know immediately if the URL doesn't work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Our Test with Unittest
&lt;/h3&gt;

&lt;p&gt;We can run a test with Django's &lt;a href="https://docs.djangoproject.com/en/4.2/ref/django-admin/#test"&gt;test command&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;venv&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python manage.py &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Found 2 &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Creating &lt;span class="nb"&gt;test &lt;/span&gt;database &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;...
System check identified no issues &lt;span class="o"&gt;(&lt;/span&gt;0 silenced&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
..
&lt;span class="nt"&gt;----------------------------------------------------------------------&lt;/span&gt;
Ran 2 tests &lt;span class="k"&gt;in &lt;/span&gt;0.089s

OK
Destroying &lt;span class="nb"&gt;test &lt;/span&gt;database &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What if we run a failing test? For example, if the order of returned objects doesn't match the desired result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;venv&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python manage.py &lt;span class="nb"&gt;test
&lt;/span&gt;Found 2 &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Creating &lt;span class="nb"&gt;test &lt;/span&gt;database &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;...
System check identified no issues &lt;span class="o"&gt;(&lt;/span&gt;0 silenced&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
F
&lt;span class="o"&gt;======================================================================&lt;/span&gt;
FAIL: test_book_order &lt;span class="o"&gt;(&lt;/span&gt;bookstore.test.BookListTest&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;----------------------------------------------------------------------&lt;/span&gt;
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"/bookstore/test.py"&lt;/span&gt;, line 17, &lt;span class="k"&gt;in &lt;/span&gt;test_book_order
    self.assertEqual&lt;span class="o"&gt;(&lt;/span&gt;books, &lt;span class="o"&gt;[&lt;/span&gt;self.second_book, self.first_book]&lt;span class="o"&gt;)&lt;/span&gt;
AssertionError: Lists differ: &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Pride and Prejudice&amp;gt;, &amp;lt;Book: Emma&amp;gt;] &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Emma&amp;gt;, &amp;lt;Book: Pride and Prejudice&amp;gt;]

First differing element 0:
&amp;lt;Book: Pride and Prejudice&amp;gt;
&amp;lt;Book: Emma&amp;gt;

- &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Pride and Prejudice&amp;gt;, &amp;lt;Book: Emma&amp;gt;]
+ &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Emma&amp;gt;, &amp;lt;Book: Pride and Prejudice&amp;gt;]
&lt;span class="nt"&gt;----------------------------------------------------------------------&lt;/span&gt;
Ran 2 tests &lt;span class="k"&gt;in &lt;/span&gt;0.085s

FAILED &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
Destroying &lt;span class="nb"&gt;test &lt;/span&gt;database &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the output is quite informative — you learn which test failed, in which line, and why. The test failed because the lists differ; you can even see both lists, so you can quickly determine where the problem lies.&lt;/p&gt;

&lt;p&gt;Another essential thing to notice here is that only the second test failed — figuring out what's wrong can be more challenging if there's a single test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pytest for Python
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.pytest.org/en/7.4.x/"&gt;Pytest&lt;/a&gt; is an excellent alternative to unittest. Even though it doesn't come built-in to &lt;code&gt;Python&lt;/code&gt; itself, it is considered more &lt;em&gt;pythonic&lt;/em&gt; than unittest. It doesn't require a &lt;code&gt;TestClass&lt;/code&gt;, has less boilerplate code, and has a plain &lt;code&gt;assert&lt;/code&gt; statement. Pytest has a rich plugin ecosystem, including a specific Django plugin, &lt;a href="https://pytest-django.readthedocs.io/"&gt;pytest-django&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The pytest approach is quite different from unittest.&lt;/p&gt;

&lt;p&gt;Among other things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You can write function-based tests without the need for classes.&lt;/li&gt;
&lt;li&gt;It allows you to create fixtures: reusable components for setup and teardown. Fixtures can have different scopes (e.g., &lt;code&gt;module&lt;/code&gt;), allowing you to optimize performance while keeping a test isolated.&lt;/li&gt;
&lt;li&gt;Parametrization of the test function means it can be efficiently run with different arguments.&lt;/li&gt;
&lt;li&gt;It has a single, plain &lt;code&gt;assert&lt;/code&gt; statement.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Pytest and Django
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;pytest-django&lt;/em&gt; serves as an adapter between Pytest and Django. Testing Django with pytest without pytest-django is technically possible, but not practical nor recommended.&lt;br&gt;
Along with handling Django's settings, static files, and templates, it brings some &lt;em&gt;Django test&lt;/em&gt; tools to pytest, but also adds its own:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;@pytest.mark.django_db&lt;/code&gt; decorator that enables database access for a specific test.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;client&lt;/code&gt; that passes Django's &lt;code&gt;Client&lt;/code&gt; in the form of a fixture.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;admin_client&lt;/code&gt; fixture returns an authenticated &lt;code&gt;Client&lt;/code&gt; with admin access.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;settings&lt;/code&gt; fixture that allows you to temporarily override Django settings during a test.&lt;/li&gt;
&lt;li&gt;The same special Django assertions as &lt;code&gt;Django TestCase&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  How Tests Are Built with Pytest
&lt;/h3&gt;

&lt;p&gt;Like unittest, pytest also supports test discovery.&lt;br&gt;
As you'll see below, the out-of-the-box rules are (these rules can be easily changed):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The files must be named &lt;em&gt;test_*.py&lt;/em&gt; or &lt;em&gt;*_test.py&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;There's no need for test classes, but if you want to use them, the name should have a &lt;em&gt;Test&lt;/em&gt; prefix.&lt;/li&gt;
&lt;li&gt;The function names need to be prefixed with &lt;em&gt;test&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's see what tests written with pytest look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bookstore.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.mark.django_db&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_book_list_returns_all_books&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Jane&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Austen&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;second_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Emma&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1815&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;first_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pride and Prejudice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1813&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;first_book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;second_book&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;books&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.mark.django_db&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_book_list_ordered_by_date_published&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Jane&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Austen&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;second_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Emma&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1815&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;first_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pride and Prejudice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1813&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first_book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_book&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Happening Here?
&lt;/h3&gt;

&lt;p&gt;These two tests assess the same functionality as the previous two unittest tests — the first test ensures all the books in the database are returned, and the second ensures the books are ordered by publication date. How do they differ?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They're entirely standalone — no class or joint data preparation is needed. You could put them in two different files, and nothing would change.&lt;/li&gt;
&lt;li&gt;Since they need database access, they require a &lt;code&gt;@pytest.mark.django_db&lt;/code&gt; decorator that comes from pytest-django. If there's no DB access required, skip the decorator.&lt;/li&gt;
&lt;li&gt;Django's &lt;code&gt;client&lt;/code&gt; needs to be passed as a fixture since you don't automatically have access to it. Again, this comes from pytest-django.&lt;/li&gt;
&lt;li&gt;Instead of different assert statements, a plain &lt;code&gt;assert&lt;/code&gt; is used.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The lines that execute the request and that turn the &lt;code&gt;book_list&lt;/code&gt; in the response are precisely the same as in unittest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixtures in Pytest
&lt;/h3&gt;

&lt;p&gt;In pytest, there's no &lt;code&gt;setUpTestData&lt;/code&gt;. But if you find yourself repeating the same data preparation code over and over again, you can use a &lt;a href="https://docs.pytest.org/en/6.2.x/fixture.html"&gt;fixture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The fixture in our example would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# fixture creation
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;two_book_objects_same_author&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Jane&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Austen&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;second_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Emma&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1815&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;first_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Book&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Pride and Prejudice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_published&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1813&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;first_book&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_book&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# fixture usage
&lt;/span&gt;&lt;span class="nd"&gt;@pytest.mark.django_db&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_book_list_ordered_by_date_published_with_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two_book_objects_same_author&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;book_list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;two_book_objects_same_author&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You mark a fixture with the &lt;code&gt;@pytest.fixture&lt;/code&gt; decorator. You then need to pass it as an argument for a function to use.&lt;br&gt;
You can use fixtures to avoid repetitive code in the preparation step or to improve readability, but don't overdo it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Running a Test with Pytest for Django
&lt;/h3&gt;

&lt;p&gt;For pytest to work correctly, you need to tell django-pytest where Django's settings can be found. While this can be done in the terminal (&lt;code&gt;pytest --ds=bookstore.settings&lt;/code&gt;), a better option is to use &lt;a href="https://pytest-django.readthedocs.io/en/latest/configuring_django.html#pytest-ini-settings"&gt;pytest.ini&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the same time, you can use &lt;em&gt;pytest.ini&lt;/em&gt; to provide other &lt;a href="https://docs.pytest.org/en/stable/reference/reference.html#configuration-options"&gt;configuration options&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;pytest.ini&lt;/em&gt; looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[pytest]&lt;/span&gt;

&lt;span class="c"&gt;;where the django settings are
&lt;/span&gt;&lt;span class="py"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;bookstore.settings&lt;/span&gt;

&lt;span class="c"&gt;;changing test discovery
&lt;/span&gt;&lt;span class="py"&gt;python_files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;tests.py test_*.py&lt;/span&gt;

&lt;span class="c"&gt;;output logging records into the console
&lt;/span&gt;&lt;span class="py"&gt;log_cli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; is set, you can simply run the tests with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;venv&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;=================================================================================================&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;session starts &lt;span class="o"&gt;==================================================================================================&lt;/span&gt;
platform darwin &lt;span class="nt"&gt;--&lt;/span&gt; Python 3.10.5, pytest-7.4.3, pluggy-1.3.0
django: settings: bookstore.settings &lt;span class="o"&gt;(&lt;/span&gt;from option&lt;span class="o"&gt;)&lt;/span&gt;
rootdir: /bookstore
plugins: django-4.5.2
collected 2 items

bookstore/test_with_pytest.py::test_book_list_returns_all_books_with_pytest PASSED                                                                                                                                   &lt;span class="o"&gt;[&lt;/span&gt; 50%]
bookstore/test_with_pytest.py::test_book_list_ordered_by_date_published_with_pytest PASSED                                                                                                                           &lt;span class="o"&gt;[&lt;/span&gt;100%]

&lt;span class="o"&gt;==================================================================================================&lt;/span&gt; 2 passed &lt;span class="k"&gt;in &lt;/span&gt;0.58s &lt;span class="o"&gt;===================================================================================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This output is shown if &lt;code&gt;log_cli&lt;/code&gt; is set to &lt;code&gt;True&lt;/code&gt;. If it's not explicitly set, it defaults to &lt;code&gt;False&lt;/code&gt;, and the output of each test is replaced by a simple dot.&lt;/p&gt;

&lt;p&gt;And what does the output look like if the test fails?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;=================================================================================================&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;session starts &lt;span class="o"&gt;==================================================================================================&lt;/span&gt;
platform darwin &lt;span class="nt"&gt;--&lt;/span&gt; Python 3.10.5, pytest-7.4.3, pluggy-1.3.0
django: settings: bookstore.settings &lt;span class="o"&gt;(&lt;/span&gt;from ini&lt;span class="o"&gt;)&lt;/span&gt;
rootdir: /bookstore
configfile: pytest.ini
plugins: django-4.5.2
collected 2 items

bookstore/test_with_pytest.py::test_book_list_returns_all_books_with_pytest PASSED                                                                                                                                   &lt;span class="o"&gt;[&lt;/span&gt; 50%]
bookstore/test_with_pytest.py::test_book_list_ordered_by_date_published_with_pytest FAILED                                                                                                                           &lt;span class="o"&gt;[&lt;/span&gt;100%]

&lt;span class="o"&gt;=======================================================================================================&lt;/span&gt; FAILURES &lt;span class="o"&gt;=======================================================================================================&lt;/span&gt;
___________________________________________________________________________________________________ test_book_order ____________________________________________________________________________________________________

client &lt;span class="o"&gt;=&lt;/span&gt; &amp;lt;django.test.client.Client object at 0x1064dbfa0&amp;gt;, books_in_correct_order &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Emma&amp;gt;, &amp;lt;Book: Pride and Prejudice&amp;gt;]

    @pytest.mark.django_db
    def test_book_order&lt;span class="o"&gt;(&lt;/span&gt;client, books_in_correct_order&lt;span class="o"&gt;)&lt;/span&gt;:
        response &lt;span class="o"&gt;=&lt;/span&gt; client.get&lt;span class="o"&gt;(&lt;/span&gt;reverse&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'book_list'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
        books &lt;span class="o"&gt;=&lt;/span&gt; list&lt;span class="o"&gt;(&lt;/span&gt;response.context[&lt;span class="s1"&gt;'book_list'&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;       assert books &lt;span class="o"&gt;==&lt;/span&gt; books_in_correct_order
E       assert &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Pride... &amp;lt;Book: Emma&amp;gt;] &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Emma&amp;gt;...nd Prejudice&amp;gt;]
E         At index 0 diff: &amp;lt;Book: Pride and Prejudice&amp;gt; &lt;span class="o"&gt;!=&lt;/span&gt; &amp;lt;Book: Emma&amp;gt;
E         Full diff:
E         - &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Emma&amp;gt;, &amp;lt;Book: Pride and Prejudice&amp;gt;]
E         + &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Pride and Prejudice&amp;gt;, &amp;lt;Book: Emma&amp;gt;]

&lt;span class="o"&gt;===============================================================================================&lt;/span&gt; short &lt;span class="nb"&gt;test &lt;/span&gt;summary info &lt;span class="o"&gt;================================================================================================&lt;/span&gt;
FAILED bookstore/test_with_pytest.py::test_book_list_ordered_by_date_published_with_pytest - assert &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Pride... &amp;lt;Book: Emma&amp;gt;] &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;Book: Emma&amp;gt;...nd Prejudice&amp;gt;]
&lt;span class="o"&gt;=============================================================================================&lt;/span&gt; 1 failed, 1 passed &lt;span class="k"&gt;in &lt;/span&gt;0.78s &lt;span class="o"&gt;==============================================================================================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;log_cli&lt;/code&gt; is set to &lt;code&gt;True&lt;/code&gt;, you can easily see which test passed and which failed. Although there's a long output on the error, you can often find enough information on why the test failed in the &lt;em&gt;short test summary info&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unittest vs. Pytest for Python
&lt;/h2&gt;

&lt;p&gt;There's no consensus as to whether unittest or pytest is better. The opinions of developers differ, as you can see&lt;br&gt;
on &lt;a href="https://stackoverflow.com/questions/44558018/django-test-vs-pytest"&gt;StackOverflow&lt;/a&gt; or &lt;a href="https://www.reddit.com/r/django/comments/zcarme/testing_in_django_with_unittest_or_another_package/"&gt;Reddit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A lot depends on your own preferences or those of your team.&lt;br&gt;
If most of your team is used to pytest, there is no need to switch, and vice-versa.&lt;/p&gt;

&lt;p&gt;However, if you don't have your preferred way of testing yet, here's a quick comparison between the two:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;unittest (with Django test)&lt;/th&gt;
&lt;th&gt;pytest (with pytest-django)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No additional installation needed&lt;/td&gt;
&lt;td&gt;Installation of pytest and pytest-django required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Class-based approach&lt;/td&gt;
&lt;td&gt;Functional approach&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple types of assertions&lt;/td&gt;
&lt;td&gt;A plain &lt;code&gt;assert&lt;/code&gt; statement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup/TearDown methods within &lt;code&gt;TestCase&lt;/code&gt; class&lt;/td&gt;
&lt;td&gt;Fixture system with different scopes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parametrization isn't supported (but it can be done on your own)&lt;/td&gt;
&lt;td&gt;Native support for parametrization&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  How to Approach Tests in Django
&lt;/h2&gt;

&lt;p&gt;Django encourages rapid development, so you might feel that you don't actually need tests as they would just slow you down. However, tests will, in the long run, make your development process faster and less error-prone. Since Django provides a lot of building blocks that allow you to accomplish much with only a few lines of code, you may end up writing way more tests than actual code.&lt;/p&gt;

&lt;p&gt;One thing that can help you cover most of your code with tests is &lt;em&gt;code coverage&lt;/em&gt;. "&lt;em&gt;Code coverage&lt;/em&gt;" is a measure that tells you what percentage of your program's code is executed during a test suite run. The higher the percentage, the bigger the portion of the code being tested, which usually means fewer unnoticed problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://coverage.readthedocs.io"&gt;Coverage.py&lt;/a&gt; is the go-to tool for measuring code coverage of Python programs. Once installed, you can use it with either &lt;em&gt;unittest&lt;/em&gt; or &lt;em&gt;pytest&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If using pytest, you can install &lt;a href="https://pypi.org/project/pytest-cov/"&gt;pytest-cov&lt;/a&gt; — a library that integrates Coverage.py with pytest.&lt;br&gt;
It is widely used, but the &lt;a href="https://coverage.readthedocs.io/en/7.3.2/#quick-start"&gt;coverage.py official documentation states that it's mostly unnecessary&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The commands for running coverage are a little different for unittest and pytest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For unittest: &lt;code&gt;coverage run manage.py test&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For pytest: &lt;code&gt;coverage run -m pytest&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those commands will run tests and check the coverage, but you must run a separate command to get the output.&lt;/p&gt;

&lt;p&gt;To see the report in your terminal, you need to run the &lt;code&gt;coverage report&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="si"&gt;$(&lt;/span&gt;venv&lt;span class="si"&gt;)&lt;/span&gt; coverage report
Name                                                                             Stmts   Miss  Cover
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------&lt;/span&gt;
core/__init__.py                                                         0      0    100%
core/settings.py                                                         34     0    100%
core/urls.py                                                             3      0    100%
bookstore/__init__.py                                                             0      0    100%
bookstore/admin.py                                                                19     1    95%
bookstore/apps.py                                                                 6      0    100%
bookstore/models.py                                                               64     7    89%
bookstore/urls.py                                                                 3      0    100%
bookstore/views.py                                                                34     7    79%
&lt;span class="nt"&gt;------------------------------------------------------------------------------------------------------&lt;/span&gt;
TOTAL                                                                             221     15    93%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Percentages ranging from 75% to 100% are seen as giving "&lt;em&gt;good coverage&lt;/em&gt;". Aim for a score higher than 75%, but keep in mind that just because your code is well-covered by tests, doesn't mean it is any good.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Note on Tests
&lt;/h3&gt;

&lt;p&gt;Test coverage is one aspect of a good test suite. However, as long as your coverage is not below 75%, the exact percentage is not that significant.&lt;/p&gt;

&lt;p&gt;Don't write tests just for the sake of writing them or to improve your coverage percentage. Not everything in Django needs testing — writing useless tests will slow down your test suite and make refactoring a slow and painful process.&lt;/p&gt;

&lt;p&gt;You should not test Django's own code — it's already been tested. For example, you don't need to write a test that checks if an object is retrieved with &lt;code&gt;get_object_or_404&lt;/code&gt; — &lt;a href="https://github.com/django/django/blob/main/tests/get_object_or_404/tests.py"&gt;Django's testing suite already has that covered&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Also, not every implementation detail needs to be tested.&lt;br&gt;
For example, I rarely check if the correct template is used for a response — if I change the name, the test will fail without providing any real value.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Can AppSignal Help with Testing in Django?
&lt;/h3&gt;

&lt;p&gt;The trouble with tests is that they're written in isolation — when your app is live, it might not behave the same, and the user will probably not behave just as you expected.&lt;/p&gt;

&lt;p&gt;That's why it's a good idea to use application monitoring — it can help you track errors and monitor performance.&lt;br&gt;
Most importantly, it can inform you when an error is fatal, breaking your app.&lt;/p&gt;

&lt;p&gt;AppSignal integrates with &lt;a href="https://www.appsignal.com/python/django-monitoring"&gt;Django&lt;/a&gt; and can help you find the &lt;a href="https://www.appsignal.com/tour/errors"&gt;errors you miss&lt;/a&gt; when writing tests.&lt;/p&gt;

&lt;p&gt;With AppSignal, you can see how often an error occurs and when. Knowing that can help you decide how urgently you need to fix the error.&lt;/p&gt;

&lt;p&gt;Since you can integrate AppSignal with your Git repository, you can also pinpoint the line in which an error occurs. This simplifies the process of identifying an error's cause, making it easier to fix. When an error is well understood, it's also easier to add tests that prevent it and similar errors in the future.&lt;/p&gt;

&lt;p&gt;Here's an example of an error from the &lt;a href="https://www.appsignal.com/tour/errors"&gt;Issues list under the Errors dashboard for a Django application&lt;/a&gt; in AppSignal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r4POahTK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-01/django-issue-list.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r4POahTK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://blog.appsignal.com/images/blog/2024-01/django-issue-list.png" alt="Error on a Django dashboard" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this post, we've seen that you have two excellent options for testing in Django: &lt;em&gt;unittest&lt;/em&gt; and &lt;em&gt;pytest&lt;/em&gt;. We introduced both options, highlighting their unique strengths and capabilities. Which one you choose largely hinges on your personal or project-specific preferences.&lt;/p&gt;

&lt;p&gt;I hope you've seen how writing tests is essential to having a high-quality product that provides a seamless user experience.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you'd like to read Python posts as soon as they get off the press, &lt;a href="https://dev.to/python-wizardry"&gt;subscribe to our Python Wizardry newsletter and never miss a single post&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
    </item>
  </channel>
</rss>
