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
Part1. The Basics of TDD and Django
Chapter4. What Are We Doing with All These Tests? (And, Refactoring)
Ich habe den TDD-Fluss bis Kapitel 3 unterdrückt, aber er war etwas zu detailliert und langweilig (insbesondere home_page = None
).
Ist es, um ehrlich zu sein, notwendig, den Code zu schreiben, während die Ergebnisse des Komponententests so detailliert betrachtet werden?
Programming is Like Pulling a Bucket of Water Up from a Well
TDD ist ein bisschen langweilig und nervig, aber es ist auch ein Stopp, um die Programmiererentwicklung zu schützen. Das Entwickeln mit TDD ist eine sehr anstrengende Aufgabe, aber es ist eine Entwicklungsmethode, die auf lange Sicht geschätzt wird. Der Trick besteht darin, die Entwicklung auf der Grundlage eines möglichst kleinen Tests fortzusetzen.
Using Selenium to Test User Interactions
Beim letzten Mal haben wir die Ansicht home_page aus dem Komponententest erstellt. Lassen Sie uns diesmal den Funktionstest erweitern.
# django-tdd/functional_tests.py
from selenium import webdriver
from selenium.webdriver.common.keys import Keys #hinzufügen
import time #hinzufügen
import unittest
class NewVisitorTest(unittest.TestCase):
def setUp(self):
self.browser = webdriver.Chrome()
def tearDown(self):
self.browser.quit()
def test_can_start_a_list_and_retrieve_it_later(self):
#Nobita ist neu in-Ich habe gehört, dass es eine Do-App gibt und habe auf die Homepage zugegriffen.
self.browser.get('http://localhost:8000')
#Nobita hat den Seitentitel und den Header zu-Ich habe bestätigt, dass es sich um eine Do-App handelt.
self.assertIn('To-Do', self.browser.title)
header_text = self.browser.find_element_by_tag_name('h1').text
self.assertIn('To-Do', header_text)
#Nobita ist zu-Aufforderung zum Ausfüllen des Do-Elements,
inputbox = self.browser.find_element_by_id('id_new_item')
self.assertEqual(
inputbox.get_attribute('placeholder'),
'Enter a to-do item'
)
#Nobita schrieb in das Textfeld "Buy Dorayaki"(Sein bester Freund liebt Dorayaki)
inputbox.send_keys('Buy dorayaki')
#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.
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertTrue(
any(row.text == "1: Buy dorayaki" for row in rows)
)
#Über das Textfeld können Sie weiterhin Elemente ausfüllen
#Ausgefüllt "Geld für Dorayaki aufladen"(Er hat wenig Geld)
self.fail("Finish the test!")
#Die Seite wurde erneut aktualisiert und ich konnte sehen, dass neue Elemente hinzugefügt wurden
#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
#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.
if __name__ == '__main__':
unittest.main(warnings='ignore')
Verbesserte Funktionsprüfung. Lassen Sie es uns tatsächlich testen.
#Starten Sie einen Entwicklungsserver
$ python manage.py runserver
#Starten Sie ein weiteres cmd, um einen Funktionstest auszuführen
$ python functional_tests.py
DevTools listening on ws://127.0.0.1:51636/devtools/browser/9aa225f9-c6e8-4119-ac2a-360d76473962
E
======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "functional_tests.py", line 24, in test_can_start_a_list_and_retrieve_it_later
header_text = self.browser.find_element_by_tag_name('h1').text
File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 530, in find_element_by_tag_name
return self.find_element(by=By.TAG_NAME, value=name)
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":"h1"}
(Session info: chrome=79.0.3945.130)
----------------------------------------------------------------------
Ran 1 test in 7.004s
FAILED (errors=1)
Das Testergebnis war, dass das Element
$ git add .
$ git commit -m "Functional test now checks we can input a to-do item"
The "Don't Test Constants" Rule, and Templates to the Rescue
Lassen Sie uns die aktuellen Listen / tests.py überprüfen.
# lists/tests.py
from django.urls import resolve
from django.test import TestCase
from django.http import HttpRequest
from lists.views import home_page
class HomePageTest(TestCase):
def test_root_url_resolve_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func, home_page)
def test_home_page_returns_current_html(self):
request = HttpRequest()
response = home_page(request)
html = response.content.decode('utf8')
self.assertTrue(html.startswith('<html>'))
self.assertIn('<title>To-Do lists</title>', html)
self.assertTrue(html.endswith('</html>'))
Wenn ich mir das anschaue, überprüfe ich, ob es eine bestimmte HTML-Zeichenfolge enthält, aber dies ist keine effektive Methode. Im Allgemeinen sollten beim Testen von Einheiten das Testen von Konstanten vermieden werden. Insbesondere ist HTML wie eine Sammlung von Konstanten (Text).
HTML sollte unter Verwendung von Vorlagen erstellt werden, und Funktionstests sollten unter der Annahme fortgesetzt werden.
Refactoring to Use a Template
Refactors listet / views.py auf, um bestimmte HTML-Dateien zurückzugeben. Das Gefühl des Refactorings mit TDD besteht darin, * vorhandene Funktionen zu verbessern, ohne sie zu ändern *. Das Refactoring kann nicht ohne Tests fortgesetzt werden. Lassen Sie uns zuerst einen Unit-Test durchführen.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.009s
OK
Destroying test database for alias 'default'...
Wenn es sich um eine Fortsetzung des letzten Males handelt, sollte der Test problemlos bestanden werden. Jetzt erstellen wir eine Vorlage.
$ mkdir templates
$ cd templates
$ mkdir lists
$ type nul > lists\home.html
$ cd ../ # manage.Kehren Sie zu dem Verzeichnis zurück, in dem sich py befindet
<!-- templates/lists/home.html -->
<html>
<title>To-Do lists</title>
</html>
Ändern Sie listen / views.py, um dies zurückzugeben.
# lists/views.py
from django.shortcuts import render
def home_page(request):
return render(request, 'lists/home.html')
Machen wir einen Unit-Test.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E.
======================================================================
ERROR: test_home_page_returns_current_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\--your_path--\django-TDD\lists\tests.py", line 18, in test_home_page_returns_current_html
response = home_page(request)
File "C:\--your_path--\django-TDD\lists\views.py", line 7, in home_page
return render(request, 'home.html')
File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\django\shortcuts.py", line 19, in render
content = loader.render_to_string(template_name, context, request, using=using)
File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\django\template\loader.py", line 61, in render_to_string
template = get_template(template_name, using=using)
File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\django\template\loader.py", line 19, in get_template
raise TemplateDoesNotExist(template_name, chain=chain)
django.template.exceptions.TemplateDoesNotExist: home.html
----------------------------------------------------------------------
Ran 2 tests in 0.019s
FAILED (errors=1)
Destroying test database for alias 'default'...
Sie können die Meldung "django.template.exceptions.TemplateDoesNotExist: home.html" sehen, obwohl Sie die Vorlage hätten erstellen sollen. Sie können auch sehen, dass das "return render (request, 'home.html')" in lists / views.py nicht funktioniert.
Dies liegt daran, dass Sie sich bei der Erstellung der Anwendung nicht bei Django registriert haben.
Fügen wir es zu INSTALLED_APPS
in config / settings.py hinzu.
# config/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'lists.apps.ListsConfig', #hinzufügen
]
Lass es uns testen.
$ python manage.py test
======================================================================
FAIL: test_home_page_returns_current_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\--your_path--\django-TDD\lists\tests.py", line 22, in test_home_page_returns_current_html
self.assertTrue(html.endswith('</html>'))
AssertionError: False is not true
----------------------------------------------------------------------
Wenn Sie dies überprüfen, können Sie sehen, dass es mit self.assertTrue (html.endswith ('</ html>')) stolpert, aber listet / home.html endet sicherlich mit
</ html> . Ich werde. Sie können dies überprüfen, indem Sie einem Teil von html von lists / tests.py
print (repr (html))` hinzufügen und ausführen, aber am Ende des Satzes von lists / home.html den Zeilenvorschubcode \ n Wurde hinzugefügt.
Einige Tests müssen geändert werden, um dies zu bestehen.
# lists/tests.py
#~~Kürzung~~
self.assertTrue(html.strip().endswith('</html>')) #Veränderung
Lass es uns jetzt laufen lassen.
$ python manage.py test
----------------------------------------------------------------------
Ran 2 tests in 0.032s
OK
Der Komponententest wurde bestanden. Sie haben jetzt lists / views.py geändert, um eine Vorlage zurückzugeben. Versuchen Sie dann, list / tests.py umzugestalten, um festzustellen, ob die richtige Vorlage gerendert wird.
The Django Test Client
Mit der von Django bereitgestellten .assertTemplteUsed
können Sie effektiv testen, ob die richtige Vorlage zurückgegeben wird.
Fügen wir es als Teil des Tests hinzu.
# lists/tests.py
# ~~Kürzung~~
def test_home_page_returns_current_html(self):
response = self.client.get('/') #Veränderung
html = response.content.decode('utf8')
# print(repr(html))
self.assertTrue(html.startswith('<html>'))
self.assertIn('<title>To-Do lists</title>', html)
self.assertTrue(html.strip().endswith('</html>')) #Veränderung
self.assertTemplateUsed(response, 'lists/home.html') #hinzufügen
Geändert zu einer Anforderung mit dem Djagno-Testclient anstelle einer manuellen Anforderung mit HttpRequest () zur Verwendung von ".assertTemplateUsed".
$ python manage.py test
----------------------------------------------------------------------
Ran 2 tests in 0.040s
OK
Sie können diesen Django-Testclient und .assertTemplateUsed
zusammen verwenden, um zu überprüfen, ob die URL zugeordnet ist und ob die angegebene Vorlage zurückgegeben wird. Daher könnten Listen / tests.py übersichtlicher umgeschrieben werden.
# 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')
Nachdem wir den Komponententest "lists / view.py" überarbeitet haben, legen wir fest.
$ git add .
$ git commit -m "Refactor home page view to user a template"
A Little More of Our Front Page
Der Komponententest wurde bestanden, der Funktionstest ist jedoch weiterhin fehlgeschlagen. Der Inhalt der Vorlage wird im Komponententest nicht ausgewertet. Daher wird anhand eines Funktionstests festgestellt, ob die Vorlage korrekt ist.
<!-- lists/home.html -->
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
</body>
</html>
$ python functional_tests.py
[...]
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="id_new_item"]"}
(Session info: chrome=79.0.3945.130)
Fügen Sie einen Ort hinzu, an dem Sie ein neues Element eingeben können.
<!-- lists/home.html -->
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
<input id="id_new_item">
</body>
</html>
$ python functional_tests.py
[...]
AssertionError: '' != 'Enter a to-do item'
+ Enter a to-do item
Fügen wir einen Platzhalter hinzu.
<!-- lists/home.html -->
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
<input id="id_new_item" placeholder="Enter a to-do item">
</body>
</html>
$ python functional_tests.py
[...]
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="id_list_table"]"}
Fügen Sie ein Tabellen-Tag hinzu.
<!-- lists/home.html -->
<html>
<head>
<title>To-Do lists</title>
</head>
<body>
<h1>Your To-Do list</h1>
<input id="id_new_item" placeholder="Enter a to-do item">
<table id="id_list_table">
</table>
</body>
</html>
$ 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 45, in test_can_start_a_list_and_retrieve_it_later
any(row.text == "1: Buy dorayaki" for row in rows)
AssertionError: False is not true
Dies ist ein Fehler mit .assertTrue (any (~~))
in function_tests.py. any (Iterator) gibt True zurück, wenn sich das Argument im Iterator befindet.
Die Funktion zum Zurückgeben des eingegebenen Werts als "1: Buy dorayaki" wird später implementiert.
Fügen wir eine benutzerdefinierte Fehlermeldung hinzu, da "Neues Aufgabenelement wurde vorerst nicht in der Tabelle angezeigt".
# functional_tests.py
# ~~Kürzung~~
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertTrue(
any(row.text == "1: Buy dorayaki" for row in rows),
"New to-do item did not appear in table" #hinzufügen
)
$ 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
----------------------------------------------------------------------
Lassen Sie uns begehen.
$ git add .
$ git commit -m "Front page HTML now generated from template"
Implementierter Funktionstest, Komponententest, Komponententest und Codierungszyklus sowie Refactoring-Ablauf. Die Spitze ist lang. ..
Recommended Posts