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
Part1. The Basics of TDD and Django
Capter3. Testing as Simple Home Page with Unit Tests
Dans le chapitre 2, nous avons écrit un test fonctionnel en utilisant ʻunittest.TestCase` et avons testé s'il y avait un "To-Do" dans le titre de la page. Cette fois, nous allons réellement démarrer l'application et exécuter TDD.
Our First Django App, and Our First Unit Test
Django prend la forme de la construction de plusieurs applications sous un même projet. Créons tout de suite une application Django. Ici, nous allons créer une application nommée ** lists **.
$ python manage.py startapp lists
Unit Tests, and How They Differ from Functional Tests
Les tests fonctionnels testent l'application de l'extérieur (du point de vue de l'utilisateur) pour voir si elle fonctionne correctement. Les tests unitaires testent si l'application fonctionne de l'intérieur (du point de vue du développeur). Le TDD est nécessaire pour couvrir les tests fonctionnels et les tests unitaires, et la procédure de développement est la suivante.
** step1. ** Rédiger un test fonctionnel (expliquant les nouvelles fonctionnalités du point de vue de l'utilisateur).
** step2. ** Si le test fonctionnel échoue, réfléchissez à la façon d'écrire le code pour réussir le test (ne l'écrivez pas soudainement). Ajoutez un test unitaire pour définir le comportement de votre code.
** step3. ** Si le test unitaire échoue, écrivez le code d'application minimum que le test unitaire passera.
** step4. ** Répétez les étapes 2 et 3 pour enfin vérifier si le test fonctionnel réussit.
Unit Testing in Django
J'écrirai le test de la vue de la page d'accueil dans lists / tests.py. Vérifions ici d'abord.
# lists/tests.py
from django.test import TestCase
# Create your tests here.
En regardant cela, j'ai trouvé que je pouvais écrire un test unitaire en utilisant la classe TestCase fournie par Django. djagno.test.TestCase
est une extension du module standard ʻunittest.TestCase` utilisé dans le test fonctionnel.
J'écrirai un test unitaire à titre d'essai.
# lists/tests.py
from django.test import TestCase
class SmokeTest(TestCase):
def test_bad_maths(self):
self.assertEqual(1 + 1, 3)
Django a une fonctionnalité de test runner qui recherche et exécute des tests pour chaque application. Lançons le testeur de Django.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_bad_maths (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\--your_pass--\django-TDD\lists\tests.py", line 9, in test_bad_maths
self.assertEqual(1 + 1, 3)
AssertionError: 2 != 3
----------------------------------------------------------------------
Ran 1 test in 0.005s
FAILED (failures=1)
Destroying test database for alias 'default'...
J'ai pu confirmer que lists / tests.py a été exécuté et a échoué. Je vais m'engager ici.
$ git status
$ git add lists
$ git commit -m "Add app for lists, with deliberately failing unit test"
Django's MVC, URLs, and View Functions
Django doit définir ce qu'il faut faire avec une URL particulière. Le flux de travail Django ressemble à ceci:
HTTP / requête arrive à une URL spécifique
Puisque la règle a été déterminée quelle vue doit être exécutée pour HTTP / requête, exécutez la vue selon la règle.
View traite la demande et renvoie HTTP / réponse.
Par conséquent, nous avons deux choses à faire:
Est-il possible de résoudre l'URL entre l'URL et la vue?
View peut-il modifier le code HTML qui peut passer le test fonctionnel?
Maintenant, ouvrons lists / tests.py et écrivons un petit test.
# lists/tests.py
from django.urls import resolve #ajouter à
from django.test import TestCase
from lists.views import home_page #ajouter à
class HomePageTest(TestCase):
def test_root_url_resolve_to_home_page_view(self):
found = resolve('/')
self.assertEqual(found.func, home_page)
django.urls.resolve
est un module de résolution des URL utilisées en interne par django.
from lists.views import home_page
est la vue à décrire ensuite. Vous pouvez voir que cela est décrit ci-dessous.
Testons-le.
$ python manage.py test
System check identified no issues (0 silenced).
E
======================================================================
ERROR: lists.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: lists.tests
Traceback (most recent call last):
File "C:\--your_user_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:\--your_user_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 5, in <module>
from lists.views import home_page
ImportError: cannot import name 'home_page' from 'lists.views' (C:\Users\--your_path--\django-TDD\lists\views.py)
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (errors=1)
Une erreur d'importation est apparue. En regardant le contenu, cela me dit que home_page
ne peut pas être importé de lists.views
.
Maintenant, écrivons home_page
dans lists.views
.
# lists/views.py
from django.shortcuts import render
home_page = None
Cela ressemble à une blague, mais cela devrait résoudre l '«Erreur d'importation». Rappelez-vous que TDD écrit le plus petit code pour résoudre une erreur.
Testons-le à nouveau.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_root_url_resolve_to_home_page_view (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\--your_path--\django-TDD\lists\tests.py", line 10, in test_root_url_resolve_to_home_page_view
found = resolve('/')
File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\django\urls\base.py", line 25, in resolve
return get_resolver(urlconf).resolve(path)
File "C:\--your_path--\django-TDD\venv-tdd\lib\site-packages\django\urls\resolvers.py", line 575, in resolve
raise Resolver404({'tried': tried, 'path': new_path})
django.urls.exceptions.Resolver404: {'tried': [[<URLResolver <URLPattern list> (admin:admin) 'admin/'>]], 'path': ''}
----------------------------------------------------------------------
Ran 1 test in 0.005s
FAILED (errors=1)
Destroying test database for alias 'default'...
Bien sûr, l '«erreur d'importation» a été résolue, mais le test a de nouveau échoué. Si vous cochez Traceback, vous pouvez voir que Django renvoie une erreur 404 même si résoudre
résout '/'
. Cela signifie que Django n'a pas été en mesure de résoudre `` '/' '.
urls.py
Django a urls.py qui mappe les URL aux vues. config / urls.py sera l'urls.py principale. Vérifions ici.
# config/urls.py
"""config URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
Si vous vérifiez le contenu, vous trouverez comment écrire un mappage d'URL appelé «" config URL Configuration "», alors reportez-vous-y. Cette fois, ajoutons le mappage entre «/» «et« home_page »aux« motifs d'url »en écrivant des vues Funciton. De plus, ʻadmin / ʻn'est pas encore utilisé, alors commentez-le.
# config/urls.py
from django.contrib import admin
from django.urls import path
from lists import views
urlpatterns = [
# path('admin/', admin.site.urls), #Commenter
path('', views.home_page, name='home')
]
La cartographie est terminée. Faites un test.
$ python manage.py test
[...]
TypeError: view must be a callable or a list/tuple in the case of include().
J'ai ajouté le mappage d'URL pour que l'erreur 404 soit résolue, mais j'ai eu une erreur de type.
Il semble que cela soit dû au fait que lorsque vous appelez home_page
depuis lists.view
, rien n'est renvoyé avec home_page = None
.
Corrigeons cela en éditant lists / views.py.
# lists/views.py
from django.shortcuts import render
def home_page():
pass
Je vais le tester.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.003s
OK
Destroying test database for alias 'default'...
J'ai réussi le test unitaire. Je vais m'engager ici.
$ git add .
$ git status
$ git commit -m "First unit test and url mapping, dummy view"
Nous réécrirons lists / tests.py afin de pouvoir tester si les listes / vues.py actuelles renvoient réellement du HTML.
# 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): #ajouter à
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.endwith('</html>'))
test_root_url_resolve_to_home_page_view
vérifie si le mappage d'URL est correct,
Je vérifie si le bon HTML peut être retourné avec test_home_page_returns_current_html
.
Nous avons ajouté un nouveau test unitaire, donc testons-le immédiatement.
$ 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.SmokeTest)
----------------------------------------------------------------------
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)
TypeError: home_page() takes 0 positional arguments but 1 was given
----------------------------------------------------------------------
Ran 2 tests in 0.005s
FAILED (errors=1)
Destroying test database for alias 'default'...
J'ai un TypeError
. Quand je vérifie le contenu, il dit home_page () prend 0 argument positionnel mais 1 a été donné
.
Vous pouvez voir que la définition de home_page ()
ne spécifie aucun argument (0 arguments positionnels
), mais c'est étrange avec les arguments donnés ( 1 a été donné
).
Je voudrais donc réécrire lists / views.py.
# lists/views.py
from django.shortcuts import render
def home_page(request): #Changement
pass
Ajout de l'argument request
à la fonction home_page ()
. Testons avec ça.
$ 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.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\--your_path--\django-TDD\lists\tests.py", line 19, in test_home_page_returns_current_html
html = response.content.decode('utf8')
AttributeError: 'NoneType' object has no attribute 'content'
----------------------------------------------------------------------
Ran 2 tests in 0.005s
FAILED (errors=1)
Destroying test database for alias 'default'...
Le TypeError
a été résolu, mais la prochaine fois que j'obtiens un ʻAttributeError. Puisqu'il dit que l'objet 'NoneType' n'a pas d'attribut'content'
, il semble que la cause en soit que la valeur de retour de home_page (request)
soit None
.
Modifiez lists / views.py.
# lists/views.py
from django.shortcuts import render
from django.http import HttpResponse #ajouter à
def home_page(request):
return HttpResponse()
Correction du retour de django.http.HttpResponse
. Testons-le.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_home_page_returns_current_html (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\--your_path--\django-TDD\lists\tests.py", line 20, in test_home_page_returns_current_html
self.assertTrue(html.startswith('<html>'))
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.005s
FAILED (failures=1)
Destroying test database for alias 'default'...
ʻAttribute Error a été résolu et ʻAssertion Error
s'est produite. Vous pouvez voir que le message «False is not ture» s'affiche car «html.startwith ('')« est False.
Modifiez lists / views.py.
# lists/views.py
from django.shortcuts import render
from django.http import HttpResponse
def home_page(request):
return HttpResponse('<html>') #Changement
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_home_page_returns_current_html (lists.tests.SmokeTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\--your_path--\django-TDD\lists\tests.py", line 21, in test_home_page_returns_current_html
self.assertIn('<title>To-Do lists</title>', html)
AssertionError: '<title>To-Do lists</title>' not found in '<html>'
----------------------------------------------------------------------
Ran 2 tests in 0.005s
FAILED (failures=1)
Destroying test database for alias 'default'...
C'est aussi une «erreur d'assertion». On dit que ``
# lists/views.py
from django.shortcuts import render
from django.http import HttpResponse
def home_page(request):
return HttpResponse('<html><title>To-Do lists</title>') #Changement
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F.
======================================================================
FAIL: test_home_page_returns_current_html (lists.tests.SmokeTest)
----------------------------------------------------------------------
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
----------------------------------------------------------------------
Ran 2 tests in 0.013s
FAILED (failures=1)
Destroying test database for alias 'default'...
C'est aussi une «erreur d'assertion». On dit que `` '</ html>' 'ne peut pas être trouvé. Modifiez lists / views.py.
# lists/views.py
from django.shortcuts import render
from django.http import HttpResponse
def home_page(request):
return HttpResponse('<html><title>To-Do lists</title></html>') #Changement
Cela devrait enfin fonctionner.
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.004s
OK
Destroying test database for alias 'default'...
Ça s'est bien passé. Maintenant que le test unitaire a réussi, démarrons le serveur de développement, puis exécutons le test fonctionnel.
#Lancer le serveur de développement
$ python manage.py runserver
#Lancer une autre cmd et exécuter un test fonctionnel
$ python functional_tests.py
DevTools listening on ws://127.0.0.1:51108/devtools/browser/9d1c6c55-8391-491b-9b14-6130c3314bba
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "functional_tests.py", line 21, in test_can_start_a_list_and_retrieve_it_later
self.fail('Finish the test!')
AssertionError: Finish the test!
----------------------------------------------------------------------
Ran 1 test in 7.244s
FAILED (failures=1)
Le test fonctionnel est FAILED, car j'ai utilisé «.fail» dans «unittest.TestCase» pour m'assurer qu'une erreur se produit même si le test réussit. Par conséquent, vous pouvez voir que le test fonctionnel a réussi!
Engageons-nous.
$ git add .
$ git commit -m "Basic view now return minimal HTML"
Assurez-vous de l'avoir couvert jusqu'à présent.
L'application Django a démarré.
J'ai utilisé le lanceur de test unitaire de Django.
--Je comprends la différence entre un test fonctionnel et un test unitaire.
--Création d'une vue à l'aide des objets de requête et de réponse de Django
J'ai pu confirmer le processus de création de fonctions en tournant le test unitaire et le cycle d'ajout / correction de code.
Recommended Posts