Ceci est une note d'apprentissage pour comprendre le développement piloté par les tests (TDD) dans Django.
Les références sont [** 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 Nous allons procéder à l'apprentissage basé sur / ref = dp-kindle-redirect? _ Encoding = UTF8 & btkr = 1).
Dans ce livre, nous menons des tests fonctionnels en utilisant la série Django 1.1 et FireFox, mais cette fois nous effectuerons des tests fonctionnels sur la série Djagno 3 et Google Chrome. J'ai également apporté quelques modifications personnelles (comme changer le nom du projet en Config), mais il n'y a pas de changements majeurs.
⇒⇒ Cliquez ici pour Partie 1 - Chapitre 1 ⇒⇒ Cliquez ici pour Partie 2-Chapitre 2 ⇒⇒ Cliquez ici pour Partie 3 - Chapitre 3 ⇒⇒ Cliquez ici pour Partie 4-Chapitre 4
Part1. The Basics of TDD and Django
Chapter5. Saving User Input: Testing the Database
Effectuons un développement piloté par les tests en supposant qu'il y a une entrée de l'utilisateur.
Wiring Up Our Form to Send a Post Request
S'il y a une entrée de l'utilisateur, nous devons la tester pour nous assurer qu'elle est enregistrée avec succès. Imaginez une requête POST à partir du HTML standard. Insérons une balise de formulaire dans 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>
Faisons un test fonctionnel.
$ 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)
Le test fonctionnel a échoué avec un contenu inattendu.
Quand j'ai vérifié l'écran d'exécution par Selenium, c'était une erreur d'accès par csrf_token
.
Ajoutons une balise CSRF en utilisant la balise de modèle Django.
<!-- 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>
Effectuez un test fonctionnel.
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)
Le contenu de l'erreur est "La nouvelle tâche à faire n'apparaît pas dans le tableau". Cela est dû au fait que le côté serveur n'a pas encore ajouté le traitement des demandes POST.
Processing a POST Request on the Server
Ajoutons le traitement POST à la vue home_page
. Avant cela, ouvrez * list / tests.py * et ajoutez un test à HomePageTest
pour voir si le POST est enregistré.
# lists/tests.py
from django.test import TestCase
class HomePageTest(TestCase):
def test_users_home_template(self):
response = self.client.get('/') #Résolution d'URL
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())
Consultez les * lists / views.py * actuelles ici.
# lists/views.py
from django.shortcuts import render
def home_page(request):
return render(request, 'lists/home.html')
La vue actuelle ne renvoie que * home.html * en réponse à la demande. Faisons un test unitaire.
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'
----------------------------------------------------------------------
Si vous vérifiez le résultat du test, vous pouvez voir que le contenu de response.content.decode ()
est affiché.
Si vous vérifiez le contenu, vous pouvez voir que le jeton CSRF est ajouté par Django sous la forme <input type =" hidden ">
by {% csrf_token%}
.
Modifiez la vue pour renvoyer le ʻitem_text` lancé dans POST pour réussir le test.
# lists/views.py
from django.shortcuts import HttpResponse #ajouter à
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')
Cela a réussi le test unitaire, mais ce n'est pas vraiment ce que je veux faire.
Passing Python Variables to Be Rendered in the Template
Dans le modèle Djagno, vous pouvez utiliser {{}}
pour utiliser des variables de View.
Ajoutons-le à home.html
afin que nous puissions l'utiliser pour afficher le contenu POSTÉ.
<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>
Vous pouvez l'afficher dans home.html
en renvoyant new_item_text
dans View, mais vous devez également tester s'il renvoie home.html
en réponse à la demande en premier lieu. Ajoutons-le au test unitaire.
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') #ajouter à
Quand j'ai exécuté le test unitaire, j'ai eu ʻAssertionError: No templates used to render the response`. C'est parce que HTML n'est pas spécifié dans la réponse au POST. Changeons la vue.
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')
Le test unitaire est maintenant réussi. Lançons maintenant un test fonctionnel.
$ 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 n'est pas vrai: la nouvelle tâche à faire n'apparaît pas dans le tableau` même si j'aurais dû être en mesure d'ajouter et d'afficher l'élément. Afin de vérifier plus en détail les détails de l'erreur, essayez de réécrire une partie du test fonctionnel et de l'exécuter.
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}"
)
Exécutez à nouveau.
$ 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
----------------------------------------------------------------------
J'ai pu confirmer le texte du tableau actuel. En regardant cela, il semble qu'une erreur se soit produite parce que l'endroit où elle devrait être «« 1: Buy dorayaki »« est «Buy dorayaki».
La vue qui résout ce problème sera probablement plus tardive. Cette fois pour voir si ʻitem_text` est renvoyé, réécrivez le test fonctionnel de cette façon.
# functional_tests.py
[...]
self.assertIn('1: Buy dorayaki', [row.text for row in rows])
[...]
Exécutez un test fonctionnel.
$ 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
Après tout, j'ai eu une erreur. Le but est de compter les éléments ajoutés, Le moyen le plus rapide de réussir le test fonctionnel est d'ajouter «1:».
<table id="id_list_table">
<tr><td>1: {{ new_item_text }}</td></tr>
</table>
Maintenant, quand j'exécute le test fonctionnel, il dit `self.fail (" Termine le test! ") ʻEt j'ai pu exécuter le test fonctionnel (pour le moment).
Maintenant que le test fonctionnel a réussi, nous mettrons à jour le nouveau test fonctionnel.
# django-tdd/functional_tests.py
[...]
#La zone de texte vous permet de continuer à remplir les éléments, donc
#Rempli "Facturation de l'argent pour Dorayaki"(Il est à court d'argent)
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)
#La page a été actualisée à nouveau et j'ai pu voir que de nouveaux éléments ont été ajoutés
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 est-ce pour-Je me demandais si l'application do enregistrait correctement mes éléments,
#Lorsque j'ai vérifié l'URL, j'ai trouvé que l'URL semble être une URL spécifique pour Nobita
self.fail("Finish the test!")
#Lorsque Nobita a tenté d'accéder à une URL spécifique qu'il avait confirmée une fois,
#J'étais heureux de m'endormir car les articles étaient rangés.
[...]
Faisons cela.
$ 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
Après tout, il semble que le test fonctionnel ne puisse pas réussir car les éléments ne sont pas comptés dans ʻid_list_table`.
J'ai mis à jour le test fonctionnel, donc je vais le valider.
$ git add .
$ git commit -m "post request returns id_list_table"
Il est sage d'isoler et de traiter le jugement "s'il y a du texte" dans le test fonctionnel. Refactorisons le test fonctionnel.
# 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])
[...]
#Lorsque Nobita appuie sur Entrée, la page est actualisée
# "1:Acheter Dorayaki"Est de-Il s'avère qu'il a été ajouté en tant qu'élément à la liste de tâches
inputbox.send_keys(Keys.ENTER)
time.sleep(1) #Attendez l'actualisation de la page.
self.check_for_row_in_list_table('1: Buy dorayaki')
#La zone de texte vous permet de continuer à remplir les éléments, donc
#Rempli "Facturation de l'argent pour Dorayaki"(Il est à court d'argent)
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)
#La page a été actualisée à nouveau et j'ai pu voir que de nouveaux éléments ont été ajoutés
self.check_for_row_in_list_table('2: Demand payment for the dorayaki')
[...]
The Django ORM and Our First Model
Créons une table pour ajouter des éléments à l'aide de l'ORM (Object-Relational Mapper) de Django. Cela sera décrit dans * lists / models.py *, mais écrivons d'abord le test unitaire.
# lists/tests.Ajouter à 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')
Il y a deux points à vérifier lors de la définition d'un modèle.
Les données sont-elles stockées dans le modèle?
Les données sont-elles extraites du modèle?
Confirmez-le avec un test unitaire.
$ 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)
J'ai eu «Erreur d'importation» parce que je n'ai pas défini de modèle. Définissons le modèle immédiatement.
# lists/models.py
from django.db import models
class Item(object):
pass
Lorsque j'exécute le test unitaire, le résultat est ʻAttribute Error: l'objet 'Item' n'a pas d'attribut'save ''.
Vous devez étendre la Model class
pour ajouter la méthode save
à la classe ʻItem. Réécrivons
lists / models.py`.
# lists/models.py
from django.db import models
class Item(models.Model):
pass
Le résultat du test unitaire est django.db.utils.OperationalError: no such table: lists_item
.
C'est parce que j'ai ajouté la classe ʻItem` à * lists / models.py *, mais la table réelle n'a pas été créée.
Our First Database Migration
Migrons en utilisant l'ORM de Django.
$ python manage.py makemigrations
Migrations for 'lists':
lists\migrations\0001_initial.py
- Create model Item
Un dossier appelé / migrations
a été créé dans le dossier lists.
Le test unitaire a abouti à une "Erreur d'attribut: l'objet" Item "n'a pas d'attribut" Texte ".
The Test Gets Surprisingly Far
Ajoutez text
à la classe ʻItem`.
# lists/models.py
from django.db import models
class Item(models.Model):
text = models.TextField()
Un test unitaire donne django.db.utils.OperationalError: no such column: lists_item.text
.
Ceci est dû au fait que text
n'a pas encore été ajouté à la table créée appelée Item.
Vous devez migrer pour l'ajouter.
$ 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
Il semble qu'il soit nécessaire de définir la valeur par défaut pour models.TextField ()
, alors ajoutez-la.
# lists/models.py
from django.db import models
class Item(models.Model):
text = models.TextField(default='')
J'ai changé le modèle, donc je vais le migrer.
$ python manage.py makemigrations
Migrations for 'lists':
lists\migrations\0002_item_text.py
- Add field text to item
Le test unitaire est maintenant réussi. Engageons-nous.
$ git add lists
$ git commit -m "Model for list Items and associated migration"
Saving the POST to the Database
Maintenant que le modèle est enregistré et récupéré, nous allons ajouter un test pour voir si le contenu de la requête POST peut être enregistré.
# lists/tests.py
def test_can_save_a_POST_requset(self):
data = {'item_text': 'A new list item'}
response = self.client.post('/', data)
#S'il a été ajouté
self.assertEqual(Item.objects.count(), 1)
#S'il peut être retiré correctement
new_item = Item.objects.first()
self.assertEqual(new_item, data['item_text'])
#Le contenu enregistré a-t-il répondu?
self.assertIn(data['item_text'], response.content.decode())
#Si vous utilisez le modèle spécifié
self.assertTemplateUsed(response, 'lists/home.html')
Faisons un test unitaire
$ 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` semble se produire car aucune donnée n'est stockée dans l'élément. Réécrivons la vue pour résoudre ce problème.
# 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) #sauvegarder
else:
return render(request, 'lists/home.html')
return render(request, 'lists/home.html', {
'new_item_text': new_item_text,
})
De plus, il semble nécessaire d'ajouter un test pour GET ainsi que pour POST.
class HomePageTest(TestCase):
def test_users_home_template(self):
response = self.client.get('/') #Résolution d'URL
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)
#S'il a été ajouté
self.assertEqual(Item.objects.count(), 1)
#S'il peut être retiré correctement
new_item = Item.objects.first()
self.assertEqual(new_item, data['item_text'])
#Le contenu enregistré a-t-il répondu?
self.assertIn(data['item_text'], response.content.decode())
#Si vous utilisez le modèle spécifié
self.assertTemplateUsed(response, 'lists/home.html')
def test_only_saves_items_when_necessary(self): #ajouter à
self.client.get('/')
self.assertEqual(Item.objects.count(), 0)
Changeons la vue du style d'écriture qui considère le cas de GET et 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,
})
Maintenant, lorsque j'exécute le test unitaire, il réussit.
Redirect After Post
Renvoyer `` new_item_text = '' 'à une requête GET ne semble pas être un bon choix. Le rôle de View est divisé en «traitement de l'entrée utilisateur» et «retour de la bonne réponse». Cette fois, pensons à "renvoyer la bonne réponse".
Après avoir reçu le POST, c'est un bon choix de le rediriger (Toujours rediriger un POST).
Réécrivons le test unitaire de cette façon.
# lists/test.py
[...]
def test_can_save_a_POST_requset(self):
self.client.post('/', data={'item_text': 'A new list item'})
#S'il a été ajouté
self.assertEqual(Item.objects.count(), 1)
#S'il peut être retiré correctement
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'})
#Êtes-vous redirigé après le POST?
self.assertEqual(response.status_code, 302)
#Si la destination de la redirection est correcte
self.assertEqual(response['location'], '/')
[...]
Le code d'état pour HTTP.redirect est 302. Exécutez le test unitaire.
ʻAssertionError: 200! = 302. Réécrivez la vue pour rediriger vers
('/')` après le POST.
# lists/views.py
from django.shortcuts import render, redirect #ajouter à
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')
Modifiez également la vue pour activer les redirections POST. Le test unitaire est maintenant réussi.
Rendering Items in the Templates
Créons une fonction qui affiche les éléments enregistrés dans un format de liste. Vous voulez que tous les éléments soient inclus dans le HTML lorsque la demande GET arrive.
# 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())
[...]
La requête GET actuelle renvoie simplement «home.html». Modifions la vue au moment de GET afin de pouvoir renvoyer la liste des éléments.
# 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})
Faisons un test unitaire.
$ 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'
----------------------------------------------------------------------
Il semble que ʻitemey 1 ne soit pas affiché dans
response.content. Changeons pour afficher la liste des tables dans
templates / lists / home.html`.
<!-- lists/home.html -->
[...]
<table id="id_list_table">
{% for item in items %}
<tr><td>{{ item.text }}</td></tr>
{% endfor %}
</table>
[...]
Le test unitaire est maintenant réussi. Maintenant que le test unitaire a réussi, effectuons un test fonctionnel.
$ 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 /'
----------------------------------------------------------------------
Quand j'ai vérifié le test fonctionnel, j'ai constaté que je ne pouvais même pas afficher le premier "To-Do".
Il semble y avoir quelque chose de fondamentalement négligé. Accédons au serveur de développement (http: // localhost: 8000)
et vérifions l'écran.
Ensuite, ʻOperational Error at / no such table: lists_item` s'affiche. Apparemment, la base de données que je pensais avoir créée n'a pas été créée.
Creating Our Production Database with migrate
Pourquoi le test unitaire n'a-t-il pas généré d'erreur même si la base de données n'a pas été créée?
Dans le test unitaire utilisant TestCase
de Django, la base de données de test était créée et détruite à chaque fois que le test unitaire était exécuté, donc cela ne se produisait pas dans le test unitaire (wow).
Par conséquent, vous devez créer une base de données pour exécuter des tests fonctionnels.
$ 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
Depuis que j'ai enregistré les modifications de la base de données avec python manage.py make migrations
, il semble que j'ai pu créer la base de données avec python manage.py migrate
.
Je souhaite maintenant effectuer un test fonctionnel.
$ 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']
Apparemment, l'affichage de l'élément n'est pas compté comme «1: ~~». Cela peut être résolu en utilisant les balises de gabarit de Django. Modifions le contenu d'affichage de la table de * 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>
[...]
Effectuez un test fonctionnel.
$ 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']
----------------------------------------------------------------------
Si vous regardez l'écran de test exécuté par Selenium, vous verrez qu'il existe un Buy dorayaki
qui a déjà été ajouté par le test fonctionnel. Il semble que `` 2: exiger le paiement du dorayaki '' ne soit pas correctement évalué.
Par conséquent, supprimez db.sqplite3
et recréez-le, puis réexécutez le test fonctionnel.
# db.Suppression de sqlite3
$ del db.sqplite3
#Reconstruire la base de données à partir de zéro
$ 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
Quand j'ai réessayé le test fonctionnel, j'ai eu ʻErreur d'assertion: Terminez le test! `. Il semble que le test fonctionnel a réussi.
Engageons-nous.
$ git add .
$ git commit -m "Redirect after POST, and show all items in template"
Cette fois, j'ai pu recevoir des articles par POST et développer leur stockage avec TDD. Je vais résumer le contenu testé cette fois dans l'échange de données par POST et la justification.
Tester le contenu | Méthode de confirmation | |
---|---|---|
POST | Si les données POSTÉES sont enregistrées | Si le nombre de données a augmenté après le POST |
Si les données POSTées peuvent être récupérées sans aucun problème | Si les dernières données et le contenu POSTÉ sont les mêmes | |
Si vous pouvez rediriger vers la bonne URL après le POST | Si l'état de la réponse après POST est 302, réponse['location']:Est la bonne destination de redirection | |
GET | Si le modèle est renvoyé correctement | Si le modèle spécifié est utilisé |
La requête GET se distingue-t-elle de la requête POST? | Si le nombre d'articles a augmenté au moment de GET | |
La liste d'articles est-elle affichée au moment de la demande GET? | Si le contenu renvoyé contient l'élément ajouté | |
Stockage de données | Si les données sont correctement enregistrées | Si le nombre d'articles a augmenté du montant ajouté en ajoutant des articles |
Si l'élément récupéré correspond aux données enregistrées |
Recommended Posts