[PYTHON] Développement piloté par les tests avec Django Partie 4

Développement piloté par les tests avec Django Partie 4

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 «

» était introuvable. Que peut-on faire pour résoudre ce problème? Tout d'abord, nous avons étendu le test fonctionnel, alors validons-le.

$ 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"

Résumé du chapitre 4

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

Développement piloté par les tests avec Django Partie 3
Développement piloté par les tests avec Django Partie 4
Développement piloté par les tests avec Django Partie 6
Développement piloté par les tests avec Django Partie 2
Développement piloté par les tests avec Django Partie 1
Développement piloté par les tests avec Django Partie 5
Démarrage du développement piloté par les tests avec PySide & Pytest
Test Django
[Test Driven Development (TDD)] Chapitre 21 Résumé
Développement d'applications à l'aide de SQLite avec Django (PTVS)
Django a commencé la partie 1
Découvrez la partie I «Monnaie multinationale» du livre «Test Driven Development» avec Python
Internationalisation avec Django
Django a commencé la partie 4
CRUD avec Django
Premier développement Django
Créez des données de test comme ça avec Python (partie 1)
[Python] Créer un environnement de développement Django avec Docker
Django Getting Started Part 2 avec eclipse Plugin (PyDev)
Créer un environnement de développement Django à l'aide de Doker Toolbox
Authentifier Google avec Django
Django 1.11 a démarré avec Python3.6
Jugement des nombres premiers avec Python
Télécharger des fichiers avec Django
Renforcez avec le test de code ⑦
Sortie PDF avec Django
Renforcez avec le test de code ⑨
Renforcez avec le test de code ③
Sortie Markdown avec Django
Utiliser Gentelella avec Django
Twitter OAuth avec Django
Renforcez avec le test de code ⑤
Renforcez avec le test de code ④
Premiers pas avec Django 1
Jugement des nombres premiers avec python
Envoyer des e-mails avec Django
bac à sable avec neo4j partie 10
Application Web réalisée avec Python3.4 + Django (Construction de l'environnement Part.1)
Téléchargement de fichiers avec django
Renforcez avec le test de code ②
Utilisez LESS avec Django
La mutualisation mécanise avec Django
Utiliser MySQL avec Django
Renforcez avec le test de code ①
Créez un environnement de développement avec Poetry Django Docker Pycharm
[Memo] Environnement de développement Django
Articles permettant le développement de systèmes avec Django (Python) _Introduction
Django à partir d'aujourd'hui
Premiers pas avec Django 2
Renforcez avec le test de code ⑧
Renforcez avec le test de code ⑨
Tutoriel pour faire du développement piloté par les tests (TDD) avec Flask-2 Decorators
Créez un environnement de développement Django avec Docker! (Docker-compose / Django / postgreSQL / nginx)
[Memo] Construire un environnement de développement pour Django + Nuxt.js avec Docker
[Django] Créez rapidement un environnement de développement de conteneur Django (Docker) avec PyCharm
Créez une application de tableau d'affichage à partir de zéro avec Django. (Partie 2)
Créez une application de tableau d'affichage à partir de zéro avec Django. (Partie 3)