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 ⇒⇒ Klicken Sie hier für Teil 5 - Kapitel 5
Part1. The Basics of TDD and Django
Chapter6. Improving Functional Testss: Ensuring Isolation and Removing Voodoo Sleeps
In Kapitel 5 habe ich überprüft, ob die POST-Daten gespeichert wurden und ob sie problemlos an die Antwort zurückgegeben werden können.
Im Komponententest wird die Test-DB von Django erstellt, sodass die Daten für den Test zum Zeitpunkt der Testausführung gelöscht werden. Im Funktionstest (in der aktuellen Einstellung) wird jedoch die DB für die Produktion (db.sqlite3) verwendet. Es gab ein Problem, dass die Daten zum Zeitpunkt des Tests ebenfalls gespeichert wurden.
Dieses Mal werden wir "Best Practice" für diese Probleme üben.
Ensuring Test Isolation in Functional Tests
Wenn die Testdaten erhalten bleiben, können sie nicht zwischen den Tests getrennt werden, was zu Problemen wie "Der Test, der erfolgreich sein sollte, weil die Testdaten gespeichert werden, schlägt fehl" führt. Es ist wichtig, sich der Trennung zwischen den Tests bewusst zu sein, um dies zu vermeiden.
In Funktionstests können Sie einen Mechanismus implementieren, der automatisch eine Testdatenbank wie einen Komponententest erstellt und diese nach Abschluss des Tests mithilfe der LiveServerTestCase-Klasse in Djagno löscht.
"LiveServerTestCase" soll mit dem Testläufer von Django getestet werden. Wenn der Testläufer von Django ausgeführt wird, werden die Dateien, die mit "test" beginnen, in allen Ordnern ausgeführt.
Erstellen wir daher einen Ordner für Funktionstests wie eine Django-Anwendung.
#Ordner erstellen
$ mkdir functional_tests
#Damit Django es als Python-Paket erkennt
$ type nul > functional_tests/__init__.py
#Bestehende Funktionstests umbenennen und verschieben
$ git mv functional_tests.py functional_tests/tests.py
#Bestätigung
$ git status
Jetzt habe ich einen Funktionstest mit python manage.py function_tests.py
durchgeführt
Sie können es jetzt mit python manage.py test function_tests
ausführen.
Schreiben wir nun den Funktionstest neu.
# django-tdd/functional_tests/tests.py
from django.test import LiveServerTestCase #hinzufügen
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
class NewVisitorTest(LiveServerTestCase): #Veränderung
def setUp(self):
self.browser = webdriver.Chrome()
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])
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(self.live_server_url) #Veränderung
#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(3) #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(3)
#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')
#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.
Der Funktionstest wurde geändert, um "LiveServerTestCase" vom "unittest" -Modul zu erben. Jetzt, da Sie Funktionstests mit dem Testläufer von Django ausführen können, habe ich das folgende "if __name ==" __ main __ "entfernt.
Lassen Sie uns nun den Funktionstest durchführen.
$ python manage.py test functional_tests
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (functional_tests.tests.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:--your_path--\django-TDD\functional_tests\tests.py", line 59, in test_can_start_a_list_and_retrieve_it_later
self.fail("Finish the test!")
AssertionError: Finish the test!
----------------------------------------------------------------------
Ran 1 test in 28.702s
FAILED (failures=1)
Destroying test database for alias 'default'...
Der Funktionstest endete mit "self.fail" und ergab die gleichen Ergebnisse wie vor dem Anwenden des "LiveServerTestCase". Wir haben außerdem bestätigt, dass eine Datenbank für Funktionstests erstellt und gelöscht wurde, sobald der Test abgeschlossen war.
Lassen Sie uns hier festlegen.
$ git status
$ git add functional_tests
$ git commit -m "make functional_tests an app, use LiveSeverTestCase"
Running Just the Unit Tests
Mit dem Befehl "python manage.py test" kann Django Unit- und Funktionstests zusammen ausführen. Wenn Sie nur den Komponententest testen möchten, geben Sie die Anwendung wie "python manage.py-Testlisten" an.
On Implicit and Explicit Waits, and Voodoo time.sleeps
Ich habe time.sleep (3)
hinzugefügt, um das Ergebnis der Ausführung des Funktionstests zu überprüfen.
Ob diese "time.sleep (3)" auf 3 Sekunden, 1 Sekunde oder 0,5 Sekunden eingestellt ist, hängt von der Antwort ab, aber ich weiß nicht, was die richtige Antwort ist.
Schreiben wir den Funktionstest neu, sodass nur die erforderlichen Puffer vorbereitet werden.
Ändern Sie check_for_row_in_list_table
in wait_for_row_in_list_table
und fügen Sie eine Polling / Retry-Logik hinzu.
# django-tdd/functional_tests/tests.py
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException #hinzufügen
import time
MAX_WAIT = 10 #hinzufügen
class NewVisitorTest(LiveServerTestCase):
def setUp(self):
self.browser = webdriver.Chrome()
def tearDown(self):
self.browser.quit()
def wait_for_row_in_list_table(self, row_text):
start_time = time.time()
while True:
try:
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])
return
except (AssertionError, WebDriverException) as e:
if time.time() - start_time > MAX_WAIT:
raise e
time.sleep(0.5)
[...]
Dadurch können wir die Verarbeitung nur der Puffer beenden, die wir für die Antwort benötigen (wir werden sie später umgestalten). Ich versuche bis zu 10 Sekunden zu warten.
Lassen Sie uns den Teil, der check_for_row_in_list_table
ausführte, in wait_for_row_in_list_table
ändern undtime.sleep (3)
löschen.
Infolgedessen sieht der aktuelle Funktionstest folgendermaßen aus:
# django-tdd/functional_tests/tests.py
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException #hinzufügen
import time
MAX_WAIT = 10 #hinzufügen
class NewVisitorTest(LiveServerTestCase): #Veränderung
def setUp(self):
self.browser = webdriver.Chrome()
def tearDown(self):
self.browser.quit()
def wait_for_row_in_list_table(self, row_text):
start_time = time.time()
while True:
try:
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])
return
except (AssertionError, WebDriverException) as e:
if time.time() - start_time > MAX_WAIT:
raise e
time.sleep(0.5)
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(self.live_server_url) #Veränderung
#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)
self.wait_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)
#Die Seite wurde erneut aktualisiert und ich konnte sehen, dass neue Elemente hinzugefügt wurden
self.wait_for_row_in_list_table('2: Demand payment for the dorayaki')
#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.
Als ich den Funktionstest ausführte, endete er mit "self.fail (" Test beenden! "). AssertionError: Beenden Sie den Test!".
Testing "Best practives" applied in this chapter
Hier finden Sie eine Zusammenfassung der Best Practices in Kapitel 6.
Ein Test darf keine Auswirkungen auf andere Tests haben. Der Testläufer von Django erstellt und löscht eine Testdatenbank, die auch für Funktionstests verwendet wird.
Vermeiden Sie Missbrauch von time.sleep () Es ist einfach, time.sleep () zu setzen und einen Puffer zum Laden zu haben, aber abhängig von der Verarbeitung und dem Puffer kann ein bedeutungsloser Fehler auftreten. vermeiden.
Verwenden Sie nicht die Wartefunktion von Selen Es scheint, dass Selen die Funktion hat, automatisch einen Puffer zu haben, aber da "Explizit ist besser als implizit" und Zen of Python es haben. Eine explizite Implementierung wird bevorzugt.
Recommended Posts