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 effectuons des tests fonctionnels en utilisant la série Django 1.1 et FireFox, mais cette fois nous allons effectuer 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
Part1. The Basics of TDD and Django
Chapter4. What Are We Doing with All These Tests? (And, Refactoring)
J'ai supprimé le flux de TDD jusqu'au chapitre 3, mais c'était un peu trop détaillé et ennuyeux (surtout home_page = None
)
Pour être honnête, est-il nécessaire d'écrire le code tout en regardant les résultats du test unitaire avec autant de détails?
Programming is Like Pulling a Bucket of Water Up from a Well
TDD est un peu ennuyeux et ennuyeux, mais c'est aussi un arrêt pour protéger le développement des programmeurs. Développer avec TDD est une tâche très fatigante, mais c'est une méthode de développement appréciée sur le long terme. L'astuce consiste à procéder au développement sur la base d'un test aussi petit que possible.
Using Selenium to Test User Interactions
La dernière fois, nous avons créé la vue home_page à partir du test unitaire, donc étendons le test fonctionnel cette fois.
# django-tdd/functional_tests.py
from selenium import webdriver
from selenium.webdriver.common.keys import Keys #ajouter à
import time #ajouter à
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 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('http://localhost:8000')
#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(1) #Attendez l'actualisation de la page.
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)
)
#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)
self.fail("Finish the test!")
#La page a été actualisée à nouveau et j'ai pu voir que de nouveaux éléments ont été ajoutés
#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
#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.
if __name__ == '__main__':
unittest.main(warnings='ignore')
Tests fonctionnels améliorés. Testons-le réellement.
#Lancer un serveur de développement
$ python manage.py runserver
#Lancer une autre cmd pour exécuter un test fonctionnel
$ 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)
Le résultat du test était que l'élément «
$ 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
Vérifions les listes actuelles / tests.py.
# 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>'))
En regardant cela, je vérifie si elle contient une chaîne HTML spécifique, mais ce n'est pas une méthode efficace. En général, les tests unitaires doivent éviter de tester les constantes. En particulier, HTML est comme une collection de constantes (texte).
Le HTML doit être créé à l'aide de modèles et les tests fonctionnels doivent continuer en supposant que.
Refactoring to Use a Template
Refactorise lists / views.py pour renvoyer des fichiers HTML spécifiques. Le sentiment de refactoring avec TDD est de * améliorer la fonctionnalité existante sans la changer *. Le refactoring ne peut pas continuer sans test. Faisons d'abord un test unitaire.
$ 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'...
S'il s'agit d'une continuation de la dernière fois, le test devrait réussir sans aucun problème. Créons maintenant un modèle.
$ mkdir templates
$ cd templates
$ mkdir lists
$ type nul > lists\home.html
$ cd ../ # manage.Revenir au répertoire où se trouve py
<!-- templates/lists/home.html -->
<html>
<title>To-Do lists</title>
</html>
Modifiez lists / views.py pour renvoyer ceci.
# lists/views.py
from django.shortcuts import render
def home_page(request):
return render(request, 'lists/home.html')
Faisons un test unitaire.
$ 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'...
Vous pouvez voir le message django.template.exceptions.TemplateDoesNotExist: home.html
même si vous auriez dû créer le modèle.
Vous pouvez également voir que le return render (request, 'home.html')
dans lists / views.py ne fonctionne pas.
En effet, vous ne vous êtes pas inscrit auprès de Django lorsque vous avez créé l'application. Ajoutons-le à ʻINSTALLED_APPS` dans config / settings.py.
# 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', #ajouter à
]
Testons-le.
$ 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
----------------------------------------------------------------------
Si vous cochez ceci, vous pouvez voir qu'il trébuche avec self.assertTrue (html.endswith ('</ html>'))
, mais lists / home.html se termine certainement par </ html>
. Je vais.
Vous pouvez vérifier en ajoutant print (repr (html))
à une partie du html de lists / tests.py et en l'exécutant, mais à la fin de la phrase de lists / home.html, le code de saut de ligne \ n
A été ajouté.
Certains tests doivent être modifiés pour réussir.
# lists/tests.py
#~~réduction~~
self.assertTrue(html.strip().endswith('</html>')) #Changement
Lançons-le maintenant.
$ python manage.py test
----------------------------------------------------------------------
Ran 2 tests in 0.032s
OK
Le test unitaire a réussi. Vous avez maintenant modifié lists / views.py pour renvoyer un modèle. Essayez ensuite de refactoriser lists / tests.py pour déterminer si le modèle correct est rendu.
The Django Test Client
Le .assertTemplteUsed
fourni par Django est un moyen efficace de tester si le modèle correct est renvoyé.
Ajoutons-le dans le cadre du test.
# lists/tests.py
# ~~réduction~~
def test_home_page_returns_current_html(self):
response = self.client.get('/') #Changement
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>')) #Changement
self.assertTemplateUsed(response, 'lists/home.html') #ajouter à
Changé en une requête utilisant Djagno test Client au lieu d'une requête manuelle utilisant HttpRequest () pour utiliser .assertTemplateUsed
.
$ python manage.py test
----------------------------------------------------------------------
Ran 2 tests in 0.040s
OK
Vous pouvez utiliser ce client de test Django et .assertTemplateUsed
ensemble pour vérifier si l'URL est mappée et si le modèle spécifié est retourné. Par conséquent, lists / tests.py pourrait être réécrit plus proprement.
# 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')
Maintenant que nous avons remanié le test unitaire, lists / view.py, nous allons nous engager.
$ git add .
$ git commit -m "Refactor home page view to user a template"
A Little More of Our Front Page
Le test unitaire a réussi, mais le test fonctionnel a toujours échoué. Le contenu du modèle n'est pas évalué dans le test unitaire, un test fonctionnel est donc utilisé pour déterminer si le modèle est correct.
<!-- 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)
Ajoutez un lieu pour saisir un nouvel élément.
<!-- 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
Ajoutons un espace réservé.
<!-- 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"]"}
Ajoutez une balise de table.
<!-- 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
Il s'agit d'une erreur avec .assertTrue (any (~~))
dans functional_tests.py. any (iterator) renvoie True si l'argument est à l'intérieur de l'itérateur.
La fonction pour renvoyer la valeur entrée comme "1: Acheter dorayaki" sera implémentée plus tard.
Ajoutons un message d'erreur personnalisé comme `` La nouvelle tâche à faire n'apparaît pas dans le tableau '' pour le moment.
# functional_tests.py
# ~~réduction~~
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" #ajouter à
)
$ 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
----------------------------------------------------------------------
Engageons-nous.
$ git add .
$ git commit -m "Front page HTML now generated from template"
Mise en œuvre des tests fonctionnels, des tests unitaires, des tests unitaires et du cycle de codage, et du flux de refactorisation. La pointe est longue. ..
Recommended Posts