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-Chapitre3 ⇒⇒ Cliquez ici pour Partie 4-Chapitre 4 ⇒⇒ Cliquez ici pour Partie 5 - Chapitre 5
Part1. The Basics of TDD and Django
Chapter6. Improving Functional Testss: Ensuring Isolation and Removing Voodoo Sleeps
Au chapitre 5, j'ai vérifié si les données POSTées étaient sauvegardées et si elles pouvaient être renvoyées à la réponse sans aucun problème.
Dans le test unitaire, la base de données de test de Django est créée, donc les données pour le test sont supprimées au moment de l'exécution du test, mais dans le test fonctionnel (dans le paramètre actuel), la base de données pour la production (db.sqlite3) est utilisée. Un problème est survenu lorsque les données au moment du test ont également été enregistrées.
Cette fois, nous pratiquerons les «meilleures pratiques» pour ces problèmes.
Ensuring Test Isolation in Functional Tests
Si les données de test restent, elles ne peuvent pas être séparées entre les tests, ce qui provoque des problèmes tels que «le test qui doit réussir car les données de test sont enregistrées échoue». Il est important d'être conscient de la séparation entre les tests pour éviter cela.
Dans les tests fonctionnels, vous pouvez implémenter un mécanisme qui crée automatiquement une base de données de test comme un test unitaire et la supprime une fois le test terminé en utilisant la classe LiveServerTestCase
dans Djagno.
LiveServerTestCase
est destiné à être testé en utilisant le testeur de Django.
Lorsque le test runner de Django s'exécute, il exécute les fichiers commençant par test
dans tous les dossiers.
Par conséquent, créons un dossier pour les tests fonctionnels comme une application Django.
#Créer le dossier
$ mkdir functional_tests
#Pour que Django le reconnaisse comme un package Python
$ type nul > functional_tests/__init__.py
#Renommer et déplacer les tests fonctionnels existants
$ git mv functional_tests.py functional_tests/tests.py
#Vérification
$ git status
J'exécutais maintenant un test fonctionnel avec python manage.py functional_tests.py
Vous pouvez maintenant l'exécuter avec python manage.py test functional_tests
.
Réécrivons maintenant le test fonctionnel.
# django-tdd/functional_tests/tests.py
from django.test import LiveServerTestCase #ajouter à
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
class NewVisitorTest(LiveServerTestCase): #Changement
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 est une nouveauté-J'ai entendu dire qu'il y avait une application do et j'ai accédé à la page d'accueil.
self.browser.get(self.live_server_url) #Changement
#Nobita a le titre et l'en-tête de la page-J'ai confirmé que cela suggère qu'il s'agit d'une application do.
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 est de-Invité à remplir l'élément à faire
inputbox = self.browser.find_element_by_id('id_new_item')
self.assertEqual(
inputbox.get_attribute('placeholder'),
'Enter a to-do item'
)
#Nobita a écrit dans la zone de texte "Acheter Dorayaki"(Son meilleur ami aime dorayaki)
inputbox.send_keys('Buy dorayaki')
#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(3) #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(3)
#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')
#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.
Modification du test fonctionnel pour hériter de LiveServerTestCase
du module ʻunittest. Maintenant que vous pouvez exécuter des tests fonctionnels en utilisant le lanceur de tests de Django, j'ai supprimé ce qui suit: ʻif __name == '__ main __'
Maintenant, exécutons le test fonctionnel.
$ 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'...
Le test fonctionnel s'est terminé par «self.fail» et a donné les mêmes résultats qu'avant l'application de «LiveServerTestCase». Nous avons également confirmé qu'une base de données pour les tests fonctionnels avait été créée et supprimée dès que le test était terminé.
Engageons-nous ici.
$ git status
$ git add functional_tests
$ git commit -m "make functional_tests an app, use LiveSeverTestCase"
Running Just the Unit Tests
La commande python manage.py test
permet à Django d'exécuter des tests unitaires et fonctionnels ensemble.
Si vous souhaitez tester uniquement le test unitaire, spécifiez l'application comme python manage.py test lists
.
On Implicit and Explicit Waits, and Voodoo time.sleeps
J'ai ajouté time.sleep (3)
pour vérifier le résultat de l'exécution du test fonctionnel.
Que ce "time.sleep (3)" soit réglé sur 3 secondes, 1 seconde ou 0,5 seconde dépend de la réponse, mais je ne sais pas quelle est la bonne réponse.
Réécrivons le test fonctionnel pour que seuls les tampons nécessaires soient préparés.
Remplacez check_for_row_in_list_table
par wait_for_row_in_list_table
et ajoutez une logique d'interrogation / nouvelle tentative.
# 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 #ajouter à
import time
MAX_WAIT = 10 #ajouter à
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)
[...]
Cela nous permet d'arrêter de traiter uniquement les tampons dont nous avons besoin pour la réponse (nous la refactoriserons plus tard). J'essaye d'attendre jusqu'à 10 secondes.
Changeons la partie qui exécutait check_for_row_in_list_table
en wait_for_row_in_list_table
et supprimonstime.sleep (3)
.
En conséquence, le test fonctionnel actuel ressemble à ceci:
# 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 #ajouter à
import time
MAX_WAIT = 10 #ajouter à
class NewVisitorTest(LiveServerTestCase): #Changement
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 est une nouveauté-J'ai entendu dire qu'il y avait une application do et j'ai accédé à la page d'accueil.
self.browser.get(self.live_server_url) #Changement
#Nobita a le titre et l'en-tête de la page-J'ai confirmé que cela suggère qu'il s'agit d'une application do.
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 est de-Invité à remplir l'élément à faire
inputbox = self.browser.find_element_by_id('id_new_item')
self.assertEqual(
inputbox.get_attribute('placeholder'),
'Enter a to-do item'
)
#Nobita a écrit dans la zone de texte "Acheter Dorayaki"(Son meilleur ami aime dorayaki)
inputbox.send_keys('Buy dorayaki')
#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)
self.wait_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)
#La page a été actualisée à nouveau et j'ai pu voir que de nouveaux éléments ont été ajoutés
self.wait_for_row_in_list_table('2: Demand payment for the dorayaki')
#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.
Quand j'ai exécuté le test fonctionnel, il s'est terminé par self.fail (" Terminez le test! ") AssertionError: Terminez le test!
.
Testing "Best practives" applied in this chapter
Voici un résumé des meilleures pratiques au chapitre 6.
Un test ne doit pas affecter les autres tests. Le testeur de Django crée et supprime une base de données de test, qui est également utilisée pour les tests fonctionnels.
Évitez l'abus de temps. Il est facile de mettre time.sleep () et d'avoir un tampon pour le chargement, mais en fonction du traitement et du tampon, une erreur sans signification peut se produire. éviter.
N'utilisez pas la fonction d'attente de Selenium Il semble que le sélénium a pour fonction d'avoir un tampon automatiquement, mais puisque "Explicite vaut mieux qu'impliquer" et Zen de Python l'ont. Une implémentation explicite est préférable.
Recommended Posts