Dies ist eine Lernnotiz zum Verständnis von Test Driven Development (TDD) in Django.
Referenzen sind [** Testgetriebene Entwicklung mit Python: Befolgen Sie die Testziege: Verwenden von Django, Selen und JavaScript (englische Ausgabe) 2. Ausgabe **](https://www.amazon.co.jp/dp/B074HXXXLS Wir werden mit dem Lernen basierend auf / ref = dp-kindle-redirect? _ Encoding = UTF8 & btkr = 1) fortfahren.
In diesem Buch führen wir Funktionstests mit der Django 1.1-Serie und FireFox durch. Dieses Mal werden wir jedoch Funktionstests mit der Djagno 3-Serie und Google Chrome durchführen. Ich habe auch einige persönliche Änderungen vorgenommen (z. B. das Ändern des Projektnamens in Config), aber es gibt keine wesentlichen Änderungen.
⇒⇒ Klicken Sie hier für Teil 1 - Kapitel 1 ⇒⇒ Klicken Sie hier für Teil 2 - Kapitel 2 ⇒⇒ Klicken Sie hier für Teil 3 - Kapitel 3 ⇒⇒ Klicken Sie hier für Teil 4 - Kapitel 4
Part1. The Basics of TDD and Django
Chapter5. Saving User Input: Testing the Database
Lassen Sie uns eine testgetriebene Entwicklung durchführen, vorausgesetzt, der Benutzer gibt Eingaben ein.
Wiring Up Our Form to Send a Post Request
Wenn der Benutzer Eingaben macht, müssen wir diese testen, um sicherzustellen, dass sie erfolgreich gespeichert wurden. Stellen Sie sich eine POST-Anfrage aus Standard-HTML vor. Fügen wir ein Formular-Tag in home.html ein.
<!-- 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>
Lassen Sie uns einen Funktionstest durchführen.
$ 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)
Der Funktionstest ist mit unerwartetem Inhalt fehlgeschlagen.
Als ich den Ausführungsbildschirm von Selenium überprüfte, war es ein Zugriffsfehler von csrf_token
.
Fügen wir ein CSRF-Tag mithilfe des Django-Vorlagen-Tags hinzu.
<!-- 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>
Führen Sie einen Funktionstest durch.
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)
Der Fehlerinhalt lautet "Neues Aufgabenelement wurde nicht in der Tabelle angezeigt". Dies liegt daran, dass die Serverseite noch keine Verarbeitung für POST-Anforderungen hinzugefügt hat.
Processing a POST Request on the Server
Fügen wir der Ansicht "home_page" die POST-Verarbeitung hinzu. Öffnen Sie vorher * list / tests.py * und fügen Sie HomePageTest
einen Test hinzu, um zu sehen, ob der POST gespeichert ist.
# lists/tests.py
from django.test import TestCase
class HomePageTest(TestCase):
def test_users_home_template(self):
response = self.client.get('/') #URL-Auflösung
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())
Überprüfen Sie hier die aktuellen * lists / views.py *.
# lists/views.py
from django.shortcuts import render
def home_page(request):
return render(request, 'lists/home.html')
Die aktuelle Ansicht gibt nur * home.html * als Antwort auf die Anforderung zurück. Machen wir einen 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'
----------------------------------------------------------------------
Wenn Sie das Testergebnis überprüfen, können Sie sehen, dass der Inhalt von response.content.decode ()
angezeigt wird.
Wenn Sie den Inhalt überprüfen, können Sie sehen, dass das CSRF-Token von Django in Form von "" von "{% csrf_token%}" hinzugefügt wird.
Um den Test zu bestehen, ändern Sie die Ansicht, um den in POST ausgelösten item_text
zurückzugeben.
# lists/views.py
from django.shortcuts import HttpResponse #hinzufügen
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')
Dies hat den Unit-Test bestanden, aber das ist nicht wirklich das, was ich tun möchte.
Passing Python Variables to Be Rendered in the Template
In der Djagno-Vorlage können Sie "{{}}" verwenden, um Variablen aus der Ansicht zu verwenden. Fügen wir es zu "home.html" hinzu, damit wir damit den POST-Inhalt anzeigen können.
<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>
Sie können es in "home.html" anzeigen, indem Sie "new_item_text" in View zurückgeben. Sie müssen jedoch auch testen, ob es "home.html" als Antwort auf die Anforderung zurückgibt. Fügen wir es dem Komponententest hinzu.
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') #hinzufügen
Als ich den Komponententest ausgeführt habe, wurde "AssertionError: Keine Vorlagen zum Rendern der Antwort" angezeigt. Dies liegt daran, dass in der Antwort beim POST kein HTML angegeben ist. Lassen Sie uns die Ansicht ändern.
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')
Der Unit-Test ist nun bestanden. Lassen Sie uns nun einen Funktionstest durchführen.
$ 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
----------------------------------------------------------------------
Ich habe "AssertionError: False is not true: Neues Aufgabenelement wurde nicht in der Tabelle angezeigt", obwohl ich das Element hätte hinzufügen und anzeigen sollen. Versuchen Sie, einen Teil des Funktionstests neu zu schreiben und auszuführen, um die Details des Fehlers genauer zu überprüfen.
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}"
)
Führen Sie es erneut aus.
$ 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
----------------------------------------------------------------------
Ich konnte den Text der eigentlichen Tabelle bestätigen. Wenn man dies betrachtet, scheint es, dass ein Fehler aufgetreten ist, weil der Ort, an dem es "1: Dorayaki kaufen" sein sollte, "Dorayaki kaufen" ist.
Die Ansicht, die dies löst, wird wahrscheinlich später sein. Um zu sehen, ob "item_text" zurückgegeben wird, schreiben Sie den Funktionstest auf diese Weise neu.
# functional_tests.py
[...]
self.assertIn('1: Buy dorayaki', [row.text for row in rows])
[...]
Führen Sie einen Funktionstest durch.
$ 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
Immerhin habe ich einen Fehler bekommen. Der Punkt ist, die hinzugefügten Elemente zu zählen, Der schnellste Weg, um den Funktionstest zu bestehen, ist das Hinzufügen von "1:".
<table id="id_list_table">
<tr><td>1: {{ new_item_text }}</td></tr>
</table>
Wenn ich jetzt den Funktionstest durchführe, steht "self.fail (" Test beenden! ")" Und ich konnte den Funktionstest (vorerst) ausführen.
Nachdem der Funktionstest bestanden wurde, werden wir den neuen Funktionstest aktualisieren.
# django-tdd/functional_tests.py
[...]
#Über das Textfeld können Sie weiterhin Elemente ausfüllen
#Ausgefüllt "Geld für Dorayaki aufladen"(Er hat wenig Geld)
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)
#Die Seite wurde erneut aktualisiert und ich konnte sehen, dass neue Elemente hinzugefügt wurden
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 ist das zu-Ich habe mich gefragt, ob die do-App meine Artikel richtig aufzeichnet.
#Als ich die URL überprüfte, stellte ich fest, dass die URL eine bestimmte URL für Nobita zu sein scheint
self.fail("Finish the test!")
#Als Nobita versuchte, auf eine bestimmte URL zuzugreifen, die er einmal bestätigt hatte,
#Ich war glücklich einzuschlafen, weil die Gegenstände gelagert wurden.
[...]
Lass uns das machen.
$ 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
Schließlich scheint der Funktionstest nicht bestanden zu werden, da die Elemente in "id_list_table" nicht gezählt werden.
Ich habe den Funktionstest aktualisiert und werde ihn festschreiben.
$ git add .
$ git commit -m "post request returns id_list_table"
Es ist ratsam, das Urteil "ob es Text gibt" im Funktionstest zu isolieren und zu verarbeiten. Lassen Sie uns den Funktionstest umgestalten.
# 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])
[...]
#Wenn Nobita die Eingabetaste drückt, wird die Seite aktualisiert
# "1:Dorayaki kaufen"Ist zu-Es stellt sich heraus, dass es als Element zur Aufgabenliste hinzugefügt wurde
inputbox.send_keys(Keys.ENTER)
time.sleep(1) #Warten Sie auf die Seitenaktualisierung.
self.check_for_row_in_list_table('1: Buy dorayaki')
#Über das Textfeld können Sie weiterhin Elemente ausfüllen
#Ausgefüllt "Geld für Dorayaki aufladen"(Er hat wenig Geld)
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)
#Die Seite wurde erneut aktualisiert und ich konnte sehen, dass neue Elemente hinzugefügt wurden
self.check_for_row_in_list_table('2: Demand payment for the dorayaki')
[...]
The Django ORM and Our First Model
Erstellen wir eine Tabelle zum Hinzufügen von Elementen mit Djangos ORM (Object-Relational Mapper). Dies wird in * lists / models.py * beschrieben, aber schreiben wir zuerst den Unit-Test.
# lists/tests.Zum py hinzufügen
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')
Bei der Definition eines Modells müssen zwei Punkte überprüft werden.
Sind die Daten im Modell gespeichert?
Werden die Daten aus dem Modell abgerufen?
Bestätigen Sie dies mit einem 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)
Ich habe "ImportError" erhalten, weil ich kein Modell definiert habe. Definieren wir das Modell sofort.
# lists/models.py
from django.db import models
class Item(object):
pass
Wenn ich den Komponententest ausführe, lautet das Ergebnis "AttributeError: Das Objekt" Item "hat kein Attribut" Save ".
Sie müssen Model class
erweitern, um die save
-Methode zur Item
-Klasse hinzuzufügen.
Lassen Sie uns lists / models.py
umschreiben.
# lists/models.py
from django.db import models
class Item(models.Model):
pass
Das Ergebnis des Komponententests ist django.db.utils.OperationalError: keine solche Tabelle: lists_item
.
Dies liegt daran, dass ich die Klasse "Item" zu * lists / models.py * hinzugefügt habe, aber die eigentliche Tabelle nicht erstellt wurde.
Our First Database Migration
Lassen Sie uns mit Djangos ORM migrieren.
$ python manage.py makemigrations
Migrations for 'lists':
lists\migrations\0001_initial.py
- Create model Item
Im Listenordner wurde ein Ordner mit dem Namen "/ migrations" erstellt.
Der Komponententest ergab den Attributfehler: Das Objekt "Objekt" hat kein Attribut "Text".
The Test Gets Surprisingly Far
Fügen Sie der Klasse "Item" "Text" hinzu.
# lists/models.py
from django.db import models
class Item(models.Model):
text = models.TextField()
Ein Komponententest ergibt django.db.utils.OperationalError: keine solche Spalte: lists_item.text
.
Dies liegt daran, dass der erstellten Tabelle "Element" noch kein "Text" hinzugefügt wurde.
Sie müssen migrieren, um es hinzuzufügen.
$ 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
Es scheint notwendig zu sein, den Standardwert für models.TextField ()
festzulegen, also fügen Sie ihn hinzu.
# lists/models.py
from django.db import models
class Item(models.Model):
text = models.TextField(default='')
Ich habe das Modell geändert, daher werde ich es migrieren.
$ python manage.py makemigrations
Migrations for 'lists':
lists\migrations\0002_item_text.py
- Add field text to item
Der Unit-Test ist nun bestanden. Lassen Sie uns begehen.
$ git add lists
$ git commit -m "Model for list Items and associated migration"
Saving the POST to the Database
Nachdem wir das Modell gespeichert und abgerufen haben, fügen wir einen Test hinzu, um festzustellen, ob der Inhalt der POST-Anforderung gespeichert werden kann.
# lists/tests.py
def test_can_save_a_POST_requset(self):
data = {'item_text': 'A new list item'}
response = self.client.post('/', data)
#Ob es hinzugefügt wurde
self.assertEqual(Item.objects.count(), 1)
#Ob es richtig herausgenommen werden kann
new_item = Item.objects.first()
self.assertEqual(new_item, data['item_text'])
#Wird der gespeicherte Inhalt beantwortet?
self.assertIn(data['item_text'], response.content.decode())
#Gibt an, ob Sie die angegebene Vorlage verwenden
self.assertTemplateUsed(response, 'lists/home.html')
Lassen Sie uns einen Unit-Test durchführen
$ 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
scheint aufzutreten, da im Item keine Daten gespeichert sind.
Lassen Sie uns die Ansicht neu schreiben, um dies zu lösen.
# 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) #sparen
else:
return render(request, 'lists/home.html')
return render(request, 'lists/home.html', {
'new_item_text': new_item_text,
})
Außerdem scheint es notwendig zu sein, einen Test für GET und POST hinzuzufügen.
class HomePageTest(TestCase):
def test_users_home_template(self):
response = self.client.get('/') #URL-Auflösung
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)
#Ob es hinzugefügt wurde
self.assertEqual(Item.objects.count(), 1)
#Ob es richtig herausgenommen werden kann
new_item = Item.objects.first()
self.assertEqual(new_item, data['item_text'])
#Wird der gespeicherte Inhalt beantwortet?
self.assertIn(data['item_text'], response.content.decode())
#Gibt an, ob Sie die angegebene Vorlage verwenden
self.assertTemplateUsed(response, 'lists/home.html')
def test_only_saves_items_when_necessary(self): #hinzufügen
self.client.get('/')
self.assertEqual(Item.objects.count(), 0)
Lassen Sie uns die Ansicht auf den Schreibstil ändern, der den Fall von GET und POST berücksichtigt.
# 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,
})
Wenn ich jetzt den Komponententest durchführe, besteht er.
Redirect After Post
Die Rückgabe von "new_item_text =" an eine GET-Anforderung scheint keine gute Wahl zu sein. Die Rolle von View ist unterteilt in "Verarbeitung von Benutzereingaben" und "Rückgabe der richtigen Antwort". Lassen Sie uns diesmal darüber nachdenken, "die richtige Antwort zurückzugeben".
Nach Erhalt des POST ist es eine gute Wahl, ihn umleiten zu lassen (Immer nach einem POST umleiten).
Schreiben wir den Unit-Test so um.
# lists/test.py
[...]
def test_can_save_a_POST_requset(self):
self.client.post('/', data={'item_text': 'A new list item'})
#Ob es hinzugefügt wurde
self.assertEqual(Item.objects.count(), 1)
#Ob es richtig herausgenommen werden kann
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'})
#Werden Sie nach dem POST umgeleitet?
self.assertEqual(response.status_code, 302)
#Gibt an, ob das Umleitungsziel korrekt ist
self.assertEqual(response['location'], '/')
[...]
Der Statuscode für HTTP.redirect lautet 302. Führen Sie den Komponententest aus.
Es wurde AssertionError: 200! = 302
.
Schreiben Sie die Ansicht neu, um sie nach dem POST auf "(" / ")" umzuleiten.
# lists/views.py
from django.shortcuts import render, redirect #hinzufügen
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')
Ändern Sie auch die Ansicht, um POST-Weiterleitungen zu aktivieren. Der Unit-Test ist nun bestanden.
Rendering Items in the Templates
Erstellen wir eine Funktion, die registrierte Elemente in einem Listenformat anzeigt. Sie möchten, dass alle Elemente in den HTML-Code aufgenommen werden, wenn die GET-Anforderung eingeht.
# 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())
[...]
Die aktuelle GET-Anfrage gibt einfach "home.html" zurück. Lassen Sie uns die Ansicht zum Zeitpunkt von GET ändern, damit wir die Liste der Elemente zurückgeben können.
# 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})
Lassen Sie uns einen Unit-Test durchführen.
$ 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'
----------------------------------------------------------------------
Es scheint, dass "itemey 1" nicht in "response.content" angezeigt wird. Lassen Sie uns ändern, um die Tabellenliste in templates / lists / home.html
anzuzeigen.
<!-- lists/home.html -->
[...]
<table id="id_list_table">
{% for item in items %}
<tr><td>{{ item.text }}</td></tr>
{% endfor %}
</table>
[...]
Der Unit-Test ist nun bestanden. Nachdem der Komponententest bestanden wurde, führen wir einen Funktionstest durch.
$ 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 /'
----------------------------------------------------------------------
Als ich den Funktionstest überprüfte, stellte ich fest, dass ich nicht einmal die erste Aufgabe anzeigen konnte. Darin scheint etwas grundlegend übersehen zu sein. Greifen wir auf den Entwicklungsserver "(http: // localhost: 8000)" zu und überprüfen Sie den Bildschirm.
Dann wird "Betriebsfehler an / keine solche Tabelle: lists_item" angezeigt. Anscheinend wurde die Datenbank, die ich erstellt habe, nicht erstellt.
Creating Our Production Database with migrate
Warum hat der Komponententest keinen Fehler generiert, obwohl die Datenbank nicht erstellt wurde? Im Unit-Test mit Djangos "TestCase" wurde die Testdatenbank jedes Mal erstellt und zerstört, wenn der Unit-Test ausgeführt wurde, sodass sie im Unit-Test nicht auftrat (wow).
Daher müssen Sie eine Datenbank erstellen, um Funktionstests auszuführen.
$ 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
Da ich die Änderungen in der Datenbank mit "python manage.py make migrations" aufgezeichnet habe, konnte ich die Datenbank anscheinend tatsächlich mit "python manage.py migrate" erstellen. Jetzt möchte ich einen Funktionstest durchführen.
$ 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']
Anscheinend wird die Elementanzeige nicht als "1: ~~" gezählt. Dies kann mithilfe der Vorlagen-Tags von Django behoben werden. Lassen Sie uns den Inhalt der Tabellenanzeige von * lists / home.html * ändern.
<!-- lists/home.html -->
[...]
<table id="id_list_table">
{% for item in items %}
<tr><td>{{forloop.counter}}: {{ item.text }}</td></tr>
{% endfor %}
</table>
[...]
Führen Sie einen Funktionstest durch.
$ 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']
----------------------------------------------------------------------
Wenn Sie sich den von Selenium ausgeführten Testbildschirm ansehen, werden Sie feststellen, dass es ein "Buy Dorayaki" gibt, das bereits durch den Funktionstest hinzugefügt wurde. Es scheint, dass "2: Nachfragezahlung für das Dorayaki" nicht korrekt bewertet wird.
Löschen Sie daher db.sqplite3
und erstellen Sie es neu. Führen Sie dann den Funktionstest erneut aus.
# db.Entfernung von sqlite3
$ del db.sqplite3
#Erstellen Sie die Datenbank von Grund auf neu
$ 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
Als ich den Funktionstest erneut versuchte, erhielt ich "AssertionError: Beende den Test!". Es scheint, dass der Funktionstest erfolgreich bestanden wurde.
Lassen Sie uns begehen.
$ git add .
$ git commit -m "Redirect after POST, and show all items in template"
Dieses Mal konnte ich Artikel per POST empfangen und deren Speicherung mit TDD entwickeln. Ich werde die diesmal getesteten Inhalte im Datenaustausch von POST und die Begründung zusammenfassen.
Inhalt testen | Bestätigungsmethode | |
---|---|---|
POST | Gibt an, ob die geposteten Daten gespeichert sind | Gibt an, ob die Anzahl der Daten nach dem POST gestiegen ist |
Ob die POST-Daten problemlos abgerufen werden können | Ob die neuesten Daten und der gepostete Inhalt identisch sind | |
Gibt an, ob Sie nach dem POST zur richtigen URL umleiten können | Ob der Antwortstatus nach dem POST 302 ist, Antwort['location']:Ist das richtige Umleitungsziel | |
GET | Gibt an, ob die Vorlage korrekt zurückgegeben wird | Gibt an, ob die angegebene Vorlage verwendet wird |
Ist die GET-Anforderung von der POST-Anforderung unterscheidbar? | Gibt an, ob die Anzahl der Elemente zum Zeitpunkt von GET gestiegen ist | |
Wird die Artikelliste zum Zeitpunkt der GET-Anforderung angezeigt? | Gibt an, ob der zurückgegebene Inhalt das hinzugefügte Element enthält | |
Datenspeicher | Ob die Daten korrekt gespeichert sind | Gibt an, ob sich die Anzahl der Elemente um den durch Hinzufügen von Elementen hinzugefügten Betrag erhöht hat |
Gibt an, ob das abgerufene Element mit den gespeicherten Daten übereinstimmt |
Recommended Posts