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"
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