Test Driven Development with Django Part 5

Test Driven Development with Django Part 5

This is a learning note to help you understand Test Driven Development (TDD) in Django.

References are [** Test-Driven Development with Python: Obey the Testing Goat: Using Django, Selenium, and JavaScript (English Edition) 2nd Edition **](https://www.amazon.co.jp/dp/B074HXXXLS We will proceed with learning based on / ref = dp-kindle-redirect? _ Encoding = UTF8 & btkr = 1).

In this book, we are conducting functional tests using Django 1.1 series and FireFox, but this time we will carry out functional tests on Djagno 3 series and Google Chrome. I've also made some personal modifications (such as changing the Project name to Config), but there are no major changes.

⇒⇒ Click here for Part 1 --Chapter 1 ⇒⇒ Click here for Part 2-Chapter 2 ⇒⇒ Click here for Part 3-Chapter 3 ⇒⇒ Click here for Part 4-Chapter 4

Part1. The Basics of TDD and Django

Chapter5. Saving User Input: Testing the Database

Let's do test-driven development assuming there is input from the user.

Wiring Up Our Form to Send a Post Request

If there is input from the user, we need to test it to make sure it is saved successfully. Imagine a POST request from standard HTML. Let's insert a form tag in home.html.

<!-- lists/home.html -->
<html>
    <head>
        <title>To-Do lists</title>
    </head>
    <body>
        <h1>Your To-Do list</h1>
            <form method="post">
                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">
            </form>
        <table id="id_list_table">
        </table>
    </body>
</html>

Let's run a functional test.

$ python functional_tests.py


DevTools listening on ws://127.0.0.1:58348/devtools/browser/2ec9655c-1dd9-4369-a97b-fb7099978b93
E
======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 42, in test_can_start_a_list_and_retrieve_it_later
    table = self.browser.find_element_by_id('id_list_table')
  File "C:--your_path--\django-TDD\venv-tdd\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 360, in find_element_by_id
    return self.find_element(by=By.ID, value=id_)
  File "C:--your_path--\django-TDD\venv-tdd\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 978, in find_element
    'value': value})['value']
  File "C:--your_path--\django-TDD\venv-tdd\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "C:--your_path--\django-TDD\venv-tdd\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="id_list_table"]"}
  (Session info: chrome=79.0.3945.130)


----------------------------------------------------------------------
Ran 1 test in 9.982s

FAILED (errors=1)

The functional test failed with unexpected content. When I checked the execution screen by Selenium, it was an access error by csrf_token. Let's add a CSRF tag using the Django template tag.

<!-- lists/home.html -->
<html>
    <head>
        <title>To-Do lists</title>
    </head>
    <body>
        <h1>Your To-Do list</h1>
            <form method="post">
                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">
                {% csrf_token %}
            </form>
        <table id="id_list_table">
        </table>
    </body>
</html>

Perform a functional test.

DevTools listening on ws://127.0.0.1:58515/devtools/browser/0bbd1ede-a958-4371-9d8a-99b5cd55b9c3
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 46, in test_can_start_a_list_and_retrieve_it_later
    "New to-do item did not appear in table"
AssertionError: False is not true : New to-do item did not appear in table

----------------------------------------------------------------------
Ran 1 test in 13.718s

FAILED (failures=1)

The error content is New to-do item did not appear in table. This is because the server side has not yet added processing for POST requests.

Processing a POST Request on the Server

Let's add POST processing to the home_page View. Before that, open * list / tests.py * and add a test to HomePageTest to see if the POST is saved.

# lists/tests.py

from django.test import TestCase


class HomePageTest(TestCase):

    def test_users_home_template(self):
        response = self.client.get('/')  #URL resolution
        self.assertTemplateUsed(response, 'lists/home.html')

    def test_can_save_a_POST_request(self):
        response = self.client.post('/', data={'item_text': "A new list item"})
        self.assertIn('A new list item', response.content.decode())

Check the current * lists / views.py * here.

# lists/views.py

from django.shortcuts import render


def home_page(request):
    return render(request, 'lists/home.html')

The current View only returns * home.html * in response to the request. Let's do a unit test.

python manage.py test

======================================================================
FAIL: test_can_save_a_POST_request (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:--your_path--\django-TDD\lists\tests.py", line 14, in test_can_save_a_POST_request
    self.assertIn('A new list item', response.content.decode())
AssertionError: 'A new list item' not found in '<!-- lists/home.html -->\n<html>\n    <head>\n        <title>To-Do lists</title>\n    </head>\n    <body>\n        <h1>Your To-Do list</h1>\n            <form method="post">\n                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">\n                <input type="hidden" name="csrfmiddlewaretoken" value="WxS3OX6BLnYLQuT4saIKUU4O18Z9mDZDIfhIrUiBjeeizFlf2ajmbj86QyzEo04R">\n            </form>\n        <table id="id_list_table">\n        </table>\n    </body>\n</html>\n'

----------------------------------------------------------------------

If you check the test result, you can see that the contents of response.content.decode () are displayed. If you look at the contents, you'll see that {% csrf_token%} added a CSRF token by Django in the form <input type =" hidden ">.

To pass the test, modify the View to return the ʻitem_text` thrown in POST.

# lists/views.py

from django.shortcuts import HttpResponse  #add to
from django.shortcuts import render


def home_page(request):
    if request.method == 'POST':
        return HttpResponse(request.POST['item_text'])
    return render(request, 'lists/home.html')

This passed the unit tests, but that's not really what I want to do.

Passing Python Variables to Be Rendered in the Template

In the Djagno template, you can use {{}} to use variables from View. Let's add it to home.html so that we can use it to display the POSTed content.

    <body>
        <h1>Your To-Do list</h1>
            <form method="post">
                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">
                {% csrf_token %}
            </form>
        <table id="id_list_table">
            <tr><td>{{ new_item_text }}</td></tr>
        </table>
    </body>

You can display it in home.html by returning new_item_text in View, but you should also test whether it is returning home.html in response to the request in the first place. Let's add it to our unit tests.

def test_can_save_a_POST_request(self):
    response = self.client.post('/', data={'item_text': "A new list item"})
    self.assertIn('A new list item', response.content.decode())
    self.assertTemplateUsed(response, 'lists/home.html')  #add to

When I ran the unit test, I got ʻAssertionError: No templates used to render the response`. This is because HTML is not specified in the response at POST. Let's change the view.

def home_page(request):
    if request.method == 'POST':
        return render(request, 'lists/home.html', {
            'new_item_text': request.POST['item_text'],
        })
    return render(request, 'lists/home.html')

The unit test has now passed. Now let's run a functional test.

$ python functional_tests.py

======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 46, in test_can_start_a_list_and_retrieve_it_later
    "New to-do item did not appear in table"
AssertionError: False is not true : New to-do item did not appear in table

----------------------------------------------------------------------

ʻAssertionError: False is not true: New to-do item did not appear in table` even though I should have been able to add and display the item. In order to check the error details in more detail, try rewriting a part of the functional test and executing it.


self.assertTrue(
    any(row.text == "1: Buy dorayaki" for row in rows),
    f"New to-do item did not appear in table. Contents were: \
    \n{table.text}"
)

Run it again.

$ python functional_tests.py

======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 47, in test_can_start_a_list_and_retrieve_it_later
    \n{table.text}"
AssertionError: False is not true : New to-do item did not appear in table. Contents were:
Buy dorayaki

----------------------------------------------------------------------

I was able to confirm the text of the actual table. Looking at this, it seems that an error has occurred because the place where it should be " 1: Buy dorayaki " is Buy dorayaki.

The View that solves this is likely to be later. This time to check if ʻitem_text` is returned, so rewrite the functional test as such.

# functional_tests.py
[...]
self.assertIn('1: Buy dorayaki', [row.text for row in rows])
[...]

Run a functional test.

$ python functional_tests.py

======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 50, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('1: Buy dorayaki', [row.text for row in rows])
AssertionError: '1: Buy dorayaki' not found in ['Buy dorayaki']

----------------------------------------------------------------------
Ran 1 test in 8.901s

After all I got an error. The point is to count the added items, The fastest way to pass the functional test is to add 1:.

<table id="id_list_table">
    <tr><td>1: {{ new_item_text }}</td></tr>
</table>

Now when I run the functional test, it says self.fail ("Finish the test!") And the functional test was (for the time being) running.

Now that the functional test has passed, we will update the new functional test.

# django-tdd/functional_tests.py

[...]

#The text box allows you to continue to fill in items, so
#Filled in "Billing Dorayaki Money"(He is tight on money)
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys("Demand payment for the dorayaki")
inputbox.send_keys(Keys.ENTER)
time.sleep(1)

#The page was refreshed again and I was able to see that new items were added
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertIn('1: Buy dorayaki', [row.text for row in rows])
self.assertIn('2: Demand payment for the dorayaki',
[row.text for row in rows])

#Nobita is this to-I was wondering if the do app was recording my items properly,
#When I checked the URL, I found that the URL seems to be a specific URL for Nobita
self.fail("Finish the test!")

#When Nobita tried to access a specific URL that he had confirmed once,

#The item was saved so I was happy to fall asleep.

[...]

Let's do this.

$ python functional_tests.py

======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 56, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('1: Buy dorayaki', [row.text for row in rows])
AssertionError: '1: Buy dorayaki' not found in ['1: Demand payment for the dorayaki']

----------------------------------------------------------------------
Ran 1 test in 13.815s

After all, it seems that the functional test cannot pass because the items are not counted in ʻid_list_table`.

I've updated the functional test, so I'll commit it.

$ git add .
$ git commit -m "post request returns id_list_table"

It is wise to isolate and process the "whether there is text" judgment in the functional test. Let's refactor the functional test.

# functional_tests.py

[...]
def tearDown(self):
    self.browser.quit()

def check_for_row_in_list_table(self, row_text):
    table = self.browser.find_element_by_id('id_list_table')
    rows = table.find_elements_by_tag_name('tr')
    self.assertIn(row_text, [row.text for row in rows])

[...]

        #When Nobita presses enter, the page is refreshed
        # "1:Buying dorayaki"Is to-Found to be added as an item to the do list
        inputbox.send_keys(Keys.ENTER)
        time.sleep(1)  #Wait for page refresh.
        self.check_for_row_in_list_table('1: Buy dorayaki')

        #The text box allows you to continue to fill in items, so
        #Filled in "Billing Dorayaki Money"(He is tight on money)
        inputbox = self.browser.find_element_by_id('id_new_item')
        inputbox.send_keys("Demand payment for the dorayaki")
        inputbox.send_keys(Keys.ENTER)
        time.sleep(1)

        #The page was refreshed again and I was able to see that new items were added
        self.check_for_row_in_list_table('2: Demand payment for the dorayaki')

[...]

The Django ORM and Our First Model

Let's create a table to add Items using Django's ORM (Object-Relational Mapper). We'll write this in * lists / models.py *, but let's write unit tests first.

# lists/tests.Add to py

from lists.models import Item


class ItemModelTest(TestCase):

    def test_saving_and_retrieving_item(self):
        first_item = Item()
        first_item.text = 'The first (ever) list item'
        first_item.save()

        second_item = Item()
        second_item.text = 'Item the second'
        second_item.save()

        saved_items = Item.objects.all()
        self.assertEqual(saved_items.count(), 2)

        first_saved_item = saved_items[0]
        second_saved_item = saved_items[1]
        self.assertEqual(first_saved_item.text, 'The first (ever) list item')
        self.assertEqual(second_saved_item.text, 'Item the second')

There are two points to check when defining a model.

--Is the data stored in the model?

--Is the data retrieved from the model?

Confirm this with a unit test.

$ python manage.py test

======================================================================
ERROR: lists.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: lists.tests
Traceback (most recent call last):
  File "C:\Users\you_name\AppData\Local\Programs\Python\Python37\lib\unittest\loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "C:\Users\you_name\AppData\Local\Programs\Python\Python37\lib\unittest\loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "C:--your_path--\django-TDD\lists\tests.py", line 4, in <module>
    from lists.models import Item
ImportError: cannot import name 'Item' from 'lists.models' (C:--your_path--\django-TDD\lists\models.py)

I got ʻImport Error` because I didn't define a model. Let's define the model immediately.

# lists/models.py

from django.db import models


class Item(object):
    pass

When I run the unit test, the result is ʻAttribute Error:'Item' object has no attribute' save'. To add the save method to the ʻItem class, you need to extend the Model class. Let's rewrite lists / models.py.

# lists/models.py

from django.db import models


class Item(models.Model):
    pass

The result of the unit test is django.db.utils.OperationalError: no such table: lists_item. This is because I added the ʻItem` class to * lists / models.py *, but the actual table was not created.

Our First Database Migration

Let's migrate using Django's ORM.

$ python manage.py makemigrations
Migrations for 'lists':
  lists\migrations\0001_initial.py
    - Create model Item

A folder called / migrations has been created in the lists folder.

Unit testing resulted in ʻAttribute Error:'Item' object has no attribute'text'`.

The Test Gets Surprisingly Far

Add text to the ʻItem` class.

# lists/models.py

from django.db import models


class Item(models.Model):
    text = models.TextField()

Unit testing gives django.db.utils.OperationalError: no such column: lists_item.text. This is because text has not yet been added to the created table called Item. You need to migrate to add it.

$ python manage.py makemigrations

You are trying to add a non-nullable field 'text' to item without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 2

It seems that it is necessary to set the default value for models.TextField (), so add it.

# lists/models.py

from django.db import models


class Item(models.Model):
    text = models.TextField(default='')

I changed the model, so I will migrate it.

$ python manage.py makemigrations
Migrations for 'lists':
  lists\migrations\0002_item_text.py
    - Add field text to item

The unit test has now passed. Let's commit.

$ git add lists
$ git commit -m "Model for list Items and associated migration"

Saving the POST to the Database

Now that we have the model saved and retrieved, we will add a test to see if the contents of the POST request can be saved.

# lists/tests.py

def test_can_save_a_POST_requset(self):
    data = {'item_text': 'A new list item'}
    response = self.client.post('/', data)

    #Whether it was added
    self.assertEqual(Item.objects.count(), 1)

    #Whether it can be taken out correctly
    new_item = Item.objects.first()
    self.assertEqual(new_item, data['item_text'])

    #Is the saved content responded?
    self.assertIn(data['item_text'], response.content.decode())

    #Whether you are using the specified template
    self.assertTemplateUsed(response, 'lists/home.html')

Let's do a unit test

$ python manage.py test

======================================================================
FAIL: test_can_save_a_POST_requset (lists.tests.ItemModelTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 43, in test_can_save_a_POST_requset
    self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1

ʻAssertionError: 0! = 1` seems to occur because no data is stored in Item. Let's rewrite the View to solve this.

# lists/views.py

from django.shortcuts import render
from lists.models import Item


def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)  #Save
    else:
        return render(request, 'lists/home.html')
    return render(request, 'lists/home.html', {
            'new_item_text': new_item_text,
        })

Also, it seems necessary to add a test for GET as well as POST.

class HomePageTest(TestCase):

    def test_users_home_template(self):
        response = self.client.get('/')  #URL resolution
        self.assertTemplateUsed(response, 'lists/home.html')

    def test_can_save_a_POST_requset(self):
        data = {'item_text': 'A new list item'}
        response = self.client.post('/', data)

        #Whether it was added
        self.assertEqual(Item.objects.count(), 1)

        #Whether it can be taken out correctly
        new_item = Item.objects.first()
        self.assertEqual(new_item, data['item_text'])

        #Is the saved content responded?
        self.assertIn(data['item_text'], response.content.decode())

        #Whether you are using the specified template
        self.assertTemplateUsed(response, 'lists/home.html')

    def test_only_saves_items_when_necessary(self):  #add to
        self.client.get('/')
        self.assertEqual(Item.objects.count(), 0)

Let's change the view to the writing style that considers the case of GET and POST.

# lists/views.py

from django.shortcuts import render
from lists.models import Item


def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)
    else:
        new_item_text = ''
    return render(request, 'lists/home.html', {
            'new_item_text': new_item_text,
        })

Now when I run the unit test, it passes.

Redirect After Post

Returning new_item_text ='' to a GET request doesn't seem like a good choice. The role of View is divided into "processing user input" and "returning the correct response". This time, let's think about "returning the correct response".

It is a good choice to have a redirect after receiving the POST (Always redirect agter a POST).

Let's rewrite our unit tests that way.

# lists/test.py

[...]

def test_can_save_a_POST_requset(self):
    self.client.post('/', data={'item_text': 'A new list item'})
    #Whether it was added
    self.assertEqual(Item.objects.count(), 1)
    #Whether it can be taken out correctly
    new_item = Item.objects.first()
    self.assertEqual(new_item.text, "A new list item")

def test_redirects_after_POST(self):
    response = self.client.post('/', data={'item_text': 'A new list item'})
    #Are you redirected after POST?
    self.assertEqual(response.status_code, 302)
    #Whether the redirect destination is correct
    self.assertEqual(response['location'], '/')

[...]

The HTTP.redirect status code is 302. Run unit tests. ʻAssertionError: 200! = 302. Rewrite the View to redirect to ('/')` after POST.

# lists/views.py

from django.shortcuts import render, redirect  #add to
from lists.models import Item


def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)
        return redirect('/')
    return render(request, 'lists/home.html')

Also change the View to enable POST redirection. The unit test has now passed.

Rendering Items in the Templates

Let's create a function that displays the registered items in a list format. You want all the items to be included in the HTML when the GET request comes in.

# lists/tests.py

class HomePageTest(TestCase):
    [...]

    def test_displays_all_lits_items(self):
        Item.objets.create(text="itemey 1")
        Item.objets.create(text="itemey 2")

        response = self.client.get('/')

        self.assertIn('itemey 1', response.cotents.decode())
        self.assertIn('itemey 2', response.cotents.decode())

    [...]

The current GET request simply returns home.html. Let's change the View at the time of GET so that we can return the list of items.

# lists/views.py

def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)
        return redirect('/')

    items = Item.objects.all()
    return render(request, 'lists/home.html', {'items': items})

Let's carry out unit tests.

$ python manage.py test

======================================================================
FAIL: test_displays_all_lits_items (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 38, in test_displays_all_lits_items
    self.assertIn('itemey 1', response.content.decode())
AssertionError: 'itemey 1' not found in '<!-- lists/home.html -->\n<html>\n    <head>\n        <title>To-Do lists</title>\n    </head>\n
 <body>\n        <h1>Your To-Do list</h1>\n            <form method="post">\n                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">\n                <input type="hidden" name="csrfmiddlewaretoken" value="DX7a2J4eXPA2wxUPvoN6dKJbDKBZAzZ3XdLGQjQyTNkh6o7GE9jbOkWNAsR8kkVn">\n            </form>\n        <table id="id_list_table">\n            <tr><td>1: </td></tr>\n        </table>\n    </body>\n</html>\n'

----------------------------------------------------------------------

It seems that ʻitemey 1 is not displayed in response.content. Let's change to display the table list in templates / lists / home.html`.

<!-- lists/home.html -->
[...]
<table id="id_list_table">
    {% for item in items %}
        <tr><td>{{ item.text }}</td></tr>
    {% endfor %}
</table>
[...]

The unit test has now passed. Now that the unit test has passed, let's perform a functional test.

$ python functional_tests.py

======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 28, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('To-Do', self.browser.title)
AssertionError: 'To-Do' not found in 'OperationalError at /'

----------------------------------------------------------------------

When I checked the functional test, I found that even the first To-Do was not displayed. There seems to be something fundamentally overlooked in this. Let's access the development server (http: // localhost: 8000) and check the screen.

Then, ʻOperational Error at / no such table: lists_item` is displayed. Apparently, the database I thought I created has not been created.

Creating Our Production Database with migrate

Why didn't the unit test give an error when the database wasn't created? In unit tests using Django's TestCase, the test database was created and destroyed every time the unit test was run, so it didn't happen in the unit test (wow).

Therefore, you need to create a database to run the functional tests.

$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, lists, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying lists.0001_initial... OK
  Applying lists.0002_item_text... OK
  Applying sessions.0001_initial... OK

Since I recorded the database changes with python manage.py make migrations, it seems that I was able to actually create the database with python manage.py migrate. Now I would like to carry out a functional test.

$ python functional_tests.py

======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 46, in test_can_start_a_list_and_retrieve_it_later
    self.check_for_row_in_list_table('1: Buy dorayaki')
  File "functional_tests.py", line 21, in check_for_row_in_list_table
    self.assertIn(row_text, [row.text for row in rows])
AssertionError: '1: Buy dorayaki' not found in ['Buy dorayaki']

Apparently the item display is not counted as 1: ~~. This can be resolved using Django's template tags. Let's change the table display contents of * lists / home.html *.

<!-- lists/home.html -->
[...]
<table id="id_list_table">
    {% for item in items %}
        <tr><td>{{forloop.counter}}: {{ item.text }}</td></tr>
    {% endfor %}
</table>
[...]

Perform a functional test.

$ python functional_tests.py


======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 56, in test_can_start_a_list_and_retrieve_it_later
    self.check_for_row_in_list_table('2: Demand payment for the dorayaki')
  File "functional_tests.py", line 21, in check_for_row_in_list_table
    self.assertIn(row_text, [row.text for row in rows])
AssertionError: '2: Demand payment for the dorayaki' not found in ['1: Buy dorayaki', '2: Buy dorayaki', '3: Demand payment for the dorayaki']

----------------------------------------------------------------------

If you take a look at the test screens run by Selenium, you'll see that there is a Buy dorayaki that has already been added by the functional test. It seems that '2: Demand payment for the dorayaki' is not being evaluated correctly.

Therefore, delete db.sqplite3 and recreate it, and then re-execute the functional test.

# db.Remove sqlite3
$ del db.sqplite3
#Rebuild database from scratch
$ python manage.py migrate --noinput

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, lists, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying lists.0001_initial... OK
  Applying lists.0002_item_text... OK
  Applying sessions.0001_initial... OK

When I tried the functional test again, I got ʻAssertion Error: Finish the test!`. It seems that the functional test passed successfully.

Let's commit.

$ git add .
$ git commit -m "Redirect after POST, and show all items in template"

Chapter 5 Summary

This time I was able to receive items by POST and develop their storage with TDD. I will summarize the contents tested this time in the data exchange by POST and the rationale.

Test content Confirmation method
POST Whether the POSTed data is saved Whether the number of data has increased after POST
Whether the POSTed data can be retrieved without any problem Whether the latest data and POSTed content are the same
Whether you can redirect to the correct URL after POST Whether the response status after POST is 302, response['location']:Is the correct redirect destination
GET Whether the template is returned correctly Whether the specified template is used
Is the GET request distinguishable from the POST request? Whether the number of items has increased at the time of GET
Is the item list displayed at the time of GET request? Whether the returned content contains the added item
Data storage Whether the data is saved correctly Whether the number of items has increased by the amount added by adding items
Whether the retrieved item matches the saved data

Recommended Posts

Test Driven Development with Django Part 3
Test Driven Development with Django Part 6
Test Driven Development with Django Part 2
Test Driven Development with Django Part 1
Test Driven Development with Django Part 5
Django test
How to authenticate with Django Part 2
How to authenticate with Django Part 3
Django begins part 1
Django begins part 4
CRUD with Django
Create test data like that with Python (Part 1)
Build Django + NGINX + PostgreSQL development environment with Docker
[Python] Build a Django development environment with Docker
Django Getting Started Part 2 with eclipse Plugin (PyDev)
Build a Django development environment with Doker Toolbox
Authenticate Google with Django
Django 1.11 started with Python3.6
Primality test with Python
Upload files with Django
Strengthen with code test ⑦
Output PDF with Django
Strengthen with code test ⑨
Strengthen with code test ③
Markdown output with Django
Use Gentelella with django
Twitter OAuth with Django
Strengthen with code test ⑤
Getting Started with Django 1
Primality test with python
Send email with Django
sandbox with neo4j part 10
Web application made with Python3.4 + Django (Part.1 Environment construction)
File upload with django
Strengthen with code test ②
Use LESS with Django
Pooling mechanize with Django
Use MySQL with Django
Strengthen with code test ①
Build a development environment with Poetry Django Docker Pycharm
Articles that enable system development with Django (Python) _Introduction
Start today with Django
Getting Started with Django 2
Strengthen with code test ⑧
Strengthen with code test ⑨
Tutorial for doing Test Driven Development (TDD) in Flask-2 Decorators
[Memo] Build a development environment for Django + Nuxt.js with Docker
[Django] Build a Django container (Docker) development environment quickly with PyCharm
Build a bulletin board app from scratch with Django. (Part 2)
Build a bulletin board app from scratch with Django. (Part 3)
Tutorial for doing Test Driven Development (TDD) in Flask ―― 1 Test Client
Image processing with Python (Part 2)
Studying Python with freeCodeCamp part1
Do Django with CodeStar (Python3.6.8, Django2.2.9)
Django starting from scratch (part: 2)
Bordering images with python Part 1
Get started with Django! ~ Tutorial ⑤ ~
Django starting from scratch (part: 1)
Test embedded software with Google Test
Scraping with Selenium + Python Part 1
Minimal website environment with django