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

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

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 - Chapitre 3 ⇒⇒ Cliquez ici pour Partie 4-Chapitre 4

Part1. The Basics of TDD and Django

Chapter5. Saving User Input: Testing the Database

Effectuons un développement piloté par les tests en supposant qu'il y a une entrée de l'utilisateur.

Wiring Up Our Form to Send a Post Request

S'il y a une entrée de l'utilisateur, nous devons la tester pour nous assurer qu'elle est enregistrée avec succès. Imaginez une requête POST à partir du HTML standard. Insérons une balise de formulaire dans home.html.

<!-- lists/home.html -->
<html>
    <head>
        <title>To-Do lists</title>
    </head>
    <body>
        <h1>Your To-Do list</h1>
            <form method="post">
                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">
            </form>
        <table id="id_list_table">
        </table>
    </body>
</html>

Faisons un test fonctionnel.

$ python functional_tests.py


DevTools listening on ws://127.0.0.1:58348/devtools/browser/2ec9655c-1dd9-4369-a97b-fb7099978b93
E
======================================================================
ERROR: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 42, in test_can_start_a_list_and_retrieve_it_later
    table = self.browser.find_element_by_id('id_list_table')
  File "C:--your_path--\django-TDD\venv-tdd\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 360, in find_element_by_id
    return self.find_element(by=By.ID, value=id_)
  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":"[id="id_list_table"]"}
  (Session info: chrome=79.0.3945.130)


----------------------------------------------------------------------
Ran 1 test in 9.982s

FAILED (errors=1)

Le test fonctionnel a échoué avec un contenu inattendu. Quand j'ai vérifié l'écran d'exécution par Selenium, c'était une erreur d'accès par csrf_token. Ajoutons une balise CSRF en utilisant la balise de modèle Django.

<!-- lists/home.html -->
<html>
    <head>
        <title>To-Do lists</title>
    </head>
    <body>
        <h1>Your To-Do list</h1>
            <form method="post">
                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">
                {% csrf_token %}
            </form>
        <table id="id_list_table">
        </table>
    </body>
</html>

Effectuez un test fonctionnel.

DevTools listening on ws://127.0.0.1:58515/devtools/browser/0bbd1ede-a958-4371-9d8a-99b5cd55b9c3
F
======================================================================
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

----------------------------------------------------------------------
Ran 1 test in 13.718s

FAILED (failures=1)

Le contenu de l'erreur est "La nouvelle tâche à faire n'apparaît pas dans le tableau". Cela est dû au fait que le côté serveur n'a pas encore ajouté le traitement des demandes POST.

Processing a POST Request on the Server

Ajoutons le traitement POST à la vue home_page. Avant cela, ouvrez * list / tests.py * et ajoutez un test à HomePageTest pour voir si le POST est enregistré.

# 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')

    def test_can_save_a_POST_request(self):
        response = self.client.post('/', data={'item_text': "A new list item"})
        self.assertIn('A new list item', response.content.decode())

Consultez les * lists / views.py * actuelles ici.

# lists/views.py

from django.shortcuts import render


def home_page(request):
    return render(request, 'lists/home.html')

La vue actuelle ne renvoie que * home.html * en réponse à la demande. Faisons un test unitaire.

python manage.py test

======================================================================
FAIL: test_can_save_a_POST_request (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:--your_path--\django-TDD\lists\tests.py", line 14, in test_can_save_a_POST_request
    self.assertIn('A new list item', response.content.decode())
AssertionError: 'A new list item' not found in '<!-- lists/home.html -->\n<html>\n    <head>\n        <title>To-Do lists</title>\n    </head>\n    <body>\n        <h1>Your To-Do list</h1>\n            <form method="post">\n                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">\n                <input type="hidden" name="csrfmiddlewaretoken" value="WxS3OX6BLnYLQuT4saIKUU4O18Z9mDZDIfhIrUiBjeeizFlf2ajmbj86QyzEo04R">\n            </form>\n        <table id="id_list_table">\n        </table>\n    </body>\n</html>\n'

----------------------------------------------------------------------

Si vous vérifiez le résultat du test, vous pouvez voir que le contenu de response.content.decode () est affiché. Si vous vérifiez le contenu, vous pouvez voir que le jeton CSRF est ajouté par Django sous la forme <input type =" hidden "> by {% csrf_token%}.

Modifiez la vue pour renvoyer le ʻitem_text` lancé dans POST pour réussir le test.

# lists/views.py

from django.shortcuts import HttpResponse  #ajouter à
from django.shortcuts import render


def home_page(request):
    if request.method == 'POST':
        return HttpResponse(request.POST['item_text'])
    return render(request, 'lists/home.html')

Cela a réussi le test unitaire, mais ce n'est pas vraiment ce que je veux faire.

Passing Python Variables to Be Rendered in the Template

Dans le modèle Djagno, vous pouvez utiliser {{}} pour utiliser des variables de View. Ajoutons-le à home.html afin que nous puissions l'utiliser pour afficher le contenu POSTÉ.

    <body>
        <h1>Your To-Do list</h1>
            <form method="post">
                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">
                {% csrf_token %}
            </form>
        <table id="id_list_table">
            <tr><td>{{ new_item_text }}</td></tr>
        </table>
    </body>

Vous pouvez l'afficher dans home.html en renvoyant new_item_text dans View, mais vous devez également tester s'il renvoie home.html en réponse à la demande en premier lieu. Ajoutons-le au test unitaire.

def test_can_save_a_POST_request(self):
    response = self.client.post('/', data={'item_text': "A new list item"})
    self.assertIn('A new list item', response.content.decode())
    self.assertTemplateUsed(response, 'lists/home.html')  #ajouter à

Quand j'ai exécuté le test unitaire, j'ai eu ʻAssertionError: No templates used to render the response`. C'est parce que HTML n'est pas spécifié dans la réponse au POST. Changeons la vue.

def home_page(request):
    if request.method == 'POST':
        return render(request, 'lists/home.html', {
            'new_item_text': request.POST['item_text'],
        })
    return render(request, 'lists/home.html')

Le test unitaire est maintenant réussi. Lançons maintenant un test fonctionnel.

$ 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

----------------------------------------------------------------------

ʻAssertionError: False n'est pas vrai: la nouvelle tâche à faire n'apparaît pas dans le tableau` même si j'aurais dû être en mesure d'ajouter et d'afficher l'élément. Afin de vérifier plus en détail les détails de l'erreur, essayez de réécrire une partie du test fonctionnel et de l'exécuter.


self.assertTrue(
    any(row.text == "1: Buy dorayaki" for row in rows),
    f"New to-do item did not appear in table. Contents were: \
    \n{table.text}"
)

Exécutez à nouveau.

$ 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 47, in test_can_start_a_list_and_retrieve_it_later
    \n{table.text}"
AssertionError: False is not true : New to-do item did not appear in table. Contents were:
Buy dorayaki

----------------------------------------------------------------------

J'ai pu confirmer le texte du tableau actuel. En regardant cela, il semble qu'une erreur se soit produite parce que l'endroit où elle devrait être «« 1: Buy dorayaki »« est «Buy dorayaki».

La vue qui résout ce problème sera probablement plus tardive. Cette fois pour voir si ʻitem_text` est renvoyé, réécrivez le test fonctionnel de cette façon.

# functional_tests.py
[...]
self.assertIn('1: Buy dorayaki', [row.text for row in rows])
[...]

Exécutez un test fonctionnel.

$ 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 50, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('1: Buy dorayaki', [row.text for row in rows])
AssertionError: '1: Buy dorayaki' not found in ['Buy dorayaki']

----------------------------------------------------------------------
Ran 1 test in 8.901s

Après tout, j'ai eu une erreur. Le but est de compter les éléments ajoutés, Le moyen le plus rapide de réussir le test fonctionnel est d'ajouter «1:».

<table id="id_list_table">
    <tr><td>1: {{ new_item_text }}</td></tr>
</table>

Maintenant, quand j'exécute le test fonctionnel, il dit `self.fail (" Termine le test! ") ʻEt j'ai pu exécuter le test fonctionnel (pour le moment).

Maintenant que le test fonctionnel a réussi, nous mettrons à jour le nouveau test fonctionnel.

# django-tdd/functional_tests.py

[...]

#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(1)

#La page a été actualisée à nouveau et j'ai pu voir que de nouveaux éléments ont été ajoutés
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertIn('1: Buy dorayaki', [row.text for row in rows])
self.assertIn('2: Demand payment for the dorayaki',
[row.text for row in rows])

#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.

[...]

Faisons cela.

$ 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 56, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('1: Buy dorayaki', [row.text for row in rows])
AssertionError: '1: Buy dorayaki' not found in ['1: Demand payment for the dorayaki']

----------------------------------------------------------------------
Ran 1 test in 13.815s

Après tout, il semble que le test fonctionnel ne puisse pas réussir car les éléments ne sont pas comptés dans ʻid_list_table`.

J'ai mis à jour le test fonctionnel, donc je vais le valider.

$ git add .
$ git commit -m "post request returns id_list_table"

Il est sage d'isoler et de traiter le jugement "s'il y a du texte" dans le test fonctionnel. Refactorisons le test fonctionnel.

# functional_tests.py

[...]
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])

[...]

        #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.
        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(1)

        #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')

[...]

The Django ORM and Our First Model

Créons une table pour ajouter des éléments à l'aide de l'ORM (Object-Relational Mapper) de Django. Cela sera décrit dans * lists / models.py *, mais écrivons d'abord le test unitaire.

# lists/tests.Ajouter à py

from lists.models import Item


class ItemModelTest(TestCase):

    def test_saving_and_retrieving_item(self):
        first_item = Item()
        first_item.text = 'The first (ever) list item'
        first_item.save()

        second_item = Item()
        second_item.text = 'Item the second'
        second_item.save()

        saved_items = Item.objects.all()
        self.assertEqual(saved_items.count(), 2)

        first_saved_item = saved_items[0]
        second_saved_item = saved_items[1]
        self.assertEqual(first_saved_item.text, 'The first (ever) list item')
        self.assertEqual(second_saved_item.text, 'Item the second')

Il y a deux points à vérifier lors de la définition d'un modèle.

Confirmez-le avec un test unitaire.

$ python manage.py test

======================================================================
ERROR: lists.tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: lists.tests
Traceback (most recent call last):
  File "C:\Users\you_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:\Users\you_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 4, in <module>
    from lists.models import Item
ImportError: cannot import name 'Item' from 'lists.models' (C:--your_path--\django-TDD\lists\models.py)

J'ai eu «Erreur d'importation» parce que je n'ai pas défini de modèle. Définissons le modèle immédiatement.

# lists/models.py

from django.db import models


class Item(object):
    pass

Lorsque j'exécute le test unitaire, le résultat est ʻAttribute Error: l'objet 'Item' n'a pas d'attribut'save ''. Vous devez étendre la Model class pour ajouter la méthode save à la classe ʻItem. Réécrivons lists / models.py`.

# lists/models.py

from django.db import models


class Item(models.Model):
    pass

Le résultat du test unitaire est django.db.utils.OperationalError: no such table: lists_item. C'est parce que j'ai ajouté la classe ʻItem` à * lists / models.py *, mais la table réelle n'a pas été créée.

Our First Database Migration

Migrons en utilisant l'ORM de Django.

$ python manage.py makemigrations
Migrations for 'lists':
  lists\migrations\0001_initial.py
    - Create model Item

Un dossier appelé / migrations a été créé dans le dossier lists.

Le test unitaire a abouti à une "Erreur d'attribut: l'objet" Item "n'a pas d'attribut" Texte ".

The Test Gets Surprisingly Far

Ajoutez text à la classe ʻItem`.

# lists/models.py

from django.db import models


class Item(models.Model):
    text = models.TextField()

Un test unitaire donne django.db.utils.OperationalError: no such column: lists_item.text. Ceci est dû au fait que text n'a pas encore été ajouté à la table créée appelée Item. Vous devez migrer pour l'ajouter.

$ python manage.py makemigrations

You are trying to add a non-nullable field 'text' to item without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 2

Il semble qu'il soit nécessaire de définir la valeur par défaut pour models.TextField (), alors ajoutez-la.

# lists/models.py

from django.db import models


class Item(models.Model):
    text = models.TextField(default='')

J'ai changé le modèle, donc je vais le migrer.

$ python manage.py makemigrations
Migrations for 'lists':
  lists\migrations\0002_item_text.py
    - Add field text to item

Le test unitaire est maintenant réussi. Engageons-nous.

$ git add lists
$ git commit -m "Model for list Items and associated migration"

Saving the POST to the Database

Maintenant que le modèle est enregistré et récupéré, nous allons ajouter un test pour voir si le contenu de la requête POST peut être enregistré.

# lists/tests.py

def test_can_save_a_POST_requset(self):
    data = {'item_text': 'A new list item'}
    response = self.client.post('/', data)

    #S'il a été ajouté
    self.assertEqual(Item.objects.count(), 1)

    #S'il peut être retiré correctement
    new_item = Item.objects.first()
    self.assertEqual(new_item, data['item_text'])

    #Le contenu enregistré a-t-il répondu?
    self.assertIn(data['item_text'], response.content.decode())

    #Si vous utilisez le modèle spécifié
    self.assertTemplateUsed(response, 'lists/home.html')

Faisons un test unitaire

$ python manage.py test

======================================================================
FAIL: test_can_save_a_POST_requset (lists.tests.ItemModelTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 43, in test_can_save_a_POST_requset
    self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1

ʻAssertionError: 0! = 1` semble se produire car aucune donnée n'est stockée dans l'élément. Réécrivons la vue pour résoudre ce problème.

# lists/views.py

from django.shortcuts import render
from lists.models import Item


def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)  #sauvegarder
    else:
        return render(request, 'lists/home.html')
    return render(request, 'lists/home.html', {
            'new_item_text': new_item_text,
        })

De plus, il semble nécessaire d'ajouter un test pour GET ainsi que pour POST.

class HomePageTest(TestCase):

    def test_users_home_template(self):
        response = self.client.get('/')  #Résolution d'URL
        self.assertTemplateUsed(response, 'lists/home.html')

    def test_can_save_a_POST_requset(self):
        data = {'item_text': 'A new list item'}
        response = self.client.post('/', data)

        #S'il a été ajouté
        self.assertEqual(Item.objects.count(), 1)

        #S'il peut être retiré correctement
        new_item = Item.objects.first()
        self.assertEqual(new_item, data['item_text'])

        #Le contenu enregistré a-t-il répondu?
        self.assertIn(data['item_text'], response.content.decode())

        #Si vous utilisez le modèle spécifié
        self.assertTemplateUsed(response, 'lists/home.html')

    def test_only_saves_items_when_necessary(self):  #ajouter à
        self.client.get('/')
        self.assertEqual(Item.objects.count(), 0)

Changeons la vue du style d'écriture qui considère le cas de GET et POST.

# lists/views.py

from django.shortcuts import render
from lists.models import Item


def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)
    else:
        new_item_text = ''
    return render(request, 'lists/home.html', {
            'new_item_text': new_item_text,
        })

Maintenant, lorsque j'exécute le test unitaire, il réussit.

Redirect After Post

Renvoyer `` new_item_text = '' 'à une requête GET ne semble pas être un bon choix. Le rôle de View est divisé en «traitement de l'entrée utilisateur» et «retour de la bonne réponse». Cette fois, pensons à "renvoyer la bonne réponse".

Après avoir reçu le POST, c'est un bon choix de le rediriger (Toujours rediriger un POST).

Réécrivons le test unitaire de cette façon.

# lists/test.py

[...]

def test_can_save_a_POST_requset(self):
    self.client.post('/', data={'item_text': 'A new list item'})
    #S'il a été ajouté
    self.assertEqual(Item.objects.count(), 1)
    #S'il peut être retiré correctement
    new_item = Item.objects.first()
    self.assertEqual(new_item.text, "A new list item")

def test_redirects_after_POST(self):
    response = self.client.post('/', data={'item_text': 'A new list item'})
    #Êtes-vous redirigé après le POST?
    self.assertEqual(response.status_code, 302)
    #Si la destination de la redirection est correcte
    self.assertEqual(response['location'], '/')

[...]

Le code d'état pour HTTP.redirect est 302. Exécutez le test unitaire. ʻAssertionError: 200! = 302. Réécrivez la vue pour rediriger vers ('/')` après le POST.

# lists/views.py

from django.shortcuts import render, redirect  #ajouter à
from lists.models import Item


def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)
        return redirect('/')
    return render(request, 'lists/home.html')

Modifiez également la vue pour activer les redirections POST. Le test unitaire est maintenant réussi.

Rendering Items in the Templates

Créons une fonction qui affiche les éléments enregistrés dans un format de liste. Vous voulez que tous les éléments soient inclus dans le HTML lorsque la demande GET arrive.

# lists/tests.py

class HomePageTest(TestCase):
    [...]

    def test_displays_all_lits_items(self):
        Item.objets.create(text="itemey 1")
        Item.objets.create(text="itemey 2")

        response = self.client.get('/')

        self.assertIn('itemey 1', response.cotents.decode())
        self.assertIn('itemey 2', response.cotents.decode())

    [...]

La requête GET actuelle renvoie simplement «home.html». Modifions la vue au moment de GET afin de pouvoir renvoyer la liste des éléments.

# lists/views.py

def home_page(request):
    if request.method == 'POST':
        new_item_text = request.POST['item_text']
        Item.objects.create(text=new_item_text)
        return redirect('/')

    items = Item.objects.all()
    return render(request, 'lists/home.html', {'items': items})

Faisons un test unitaire.

$ python manage.py test

======================================================================
FAIL: test_displays_all_lits_items (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\--your_path--\django-TDD\lists\tests.py", line 38, in test_displays_all_lits_items
    self.assertIn('itemey 1', response.content.decode())
AssertionError: 'itemey 1' not found in '<!-- lists/home.html -->\n<html>\n    <head>\n        <title>To-Do lists</title>\n    </head>\n
 <body>\n        <h1>Your To-Do list</h1>\n            <form method="post">\n                <input name="item_text" id="id_new_item" placeholder="Enter a to-do item">\n                <input type="hidden" name="csrfmiddlewaretoken" value="DX7a2J4eXPA2wxUPvoN6dKJbDKBZAzZ3XdLGQjQyTNkh6o7GE9jbOkWNAsR8kkVn">\n            </form>\n        <table id="id_list_table">\n            <tr><td>1: </td></tr>\n        </table>\n    </body>\n</html>\n'

----------------------------------------------------------------------

Il semble que ʻitemey 1 ne soit pas affiché dans response.content. Changeons pour afficher la liste des tables dans templates / lists / home.html`.

<!-- lists/home.html -->
[...]
<table id="id_list_table">
    {% for item in items %}
        <tr><td>{{ item.text }}</td></tr>
    {% endfor %}
</table>
[...]

Le test unitaire est maintenant réussi. Maintenant que le test unitaire a réussi, effectuons un test fonctionnel.

$ 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 28, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('To-Do', self.browser.title)
AssertionError: 'To-Do' not found in 'OperationalError at /'

----------------------------------------------------------------------

Quand j'ai vérifié le test fonctionnel, j'ai constaté que je ne pouvais même pas afficher le premier "To-Do". Il semble y avoir quelque chose de fondamentalement négligé. Accédons au serveur de développement (http: // localhost: 8000) et vérifions l'écran.

Ensuite, ʻOperational Error at / no such table: lists_item` s'affiche. Apparemment, la base de données que je pensais avoir créée n'a pas été créée.

Creating Our Production Database with migrate

Pourquoi le test unitaire n'a-t-il pas généré d'erreur même si la base de données n'a pas été créée? Dans le test unitaire utilisant TestCase de Django, la base de données de test était créée et détruite à chaque fois que le test unitaire était exécuté, donc cela ne se produisait pas dans le test unitaire (wow).

Par conséquent, vous devez créer une base de données pour exécuter des tests fonctionnels.

$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, lists, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying lists.0001_initial... OK
  Applying lists.0002_item_text... OK
  Applying sessions.0001_initial... OK

Depuis que j'ai enregistré les modifications de la base de données avec python manage.py make migrations, il semble que j'ai pu créer la base de données avec python manage.py migrate. Je souhaite maintenant effectuer un test fonctionnel.

$ 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
    self.check_for_row_in_list_table('1: Buy dorayaki')
  File "functional_tests.py", line 21, in check_for_row_in_list_table
    self.assertIn(row_text, [row.text for row in rows])
AssertionError: '1: Buy dorayaki' not found in ['Buy dorayaki']

Apparemment, l'affichage de l'élément n'est pas compté comme «1: ~~». Cela peut être résolu en utilisant les balises de gabarit de Django. Modifions le contenu d'affichage de la table de * lists / home.html *.

<!-- lists/home.html -->
[...]
<table id="id_list_table">
    {% for item in items %}
        <tr><td>{{forloop.counter}}: {{ item.text }}</td></tr>
    {% endfor %}
</table>
[...]

Effectuez un test fonctionnel.

$ 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 56, in test_can_start_a_list_and_retrieve_it_later
    self.check_for_row_in_list_table('2: Demand payment for the dorayaki')
  File "functional_tests.py", line 21, in check_for_row_in_list_table
    self.assertIn(row_text, [row.text for row in rows])
AssertionError: '2: Demand payment for the dorayaki' not found in ['1: Buy dorayaki', '2: Buy dorayaki', '3: Demand payment for the dorayaki']

----------------------------------------------------------------------

Si vous regardez l'écran de test exécuté par Selenium, vous verrez qu'il existe un Buy dorayaki qui a déjà été ajouté par le test fonctionnel. Il semble que `` 2: exiger le paiement du dorayaki '' ne soit pas correctement évalué.

Par conséquent, supprimez db.sqplite3 et recréez-le, puis réexécutez le test fonctionnel.

# db.Suppression de sqlite3
$ del db.sqplite3
#Reconstruire la base de données à partir de zéro
$ python manage.py migrate --noinput

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, lists, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying lists.0001_initial... OK
  Applying lists.0002_item_text... OK
  Applying sessions.0001_initial... OK

Quand j'ai réessayé le test fonctionnel, j'ai eu ʻErreur d'assertion: Terminez le test! `. Il semble que le test fonctionnel a réussi.

Engageons-nous.

$ git add .
$ git commit -m "Redirect after POST, and show all items in template"

Résumé du chapitre 5

Cette fois, j'ai pu recevoir des articles par POST et développer leur stockage avec TDD. Je vais résumer le contenu testé cette fois dans l'échange de données par POST et la justification.

Tester le contenu Méthode de confirmation
POST Si les données POSTÉES sont enregistrées Si le nombre de données a augmenté après le POST
Si les données POSTées peuvent être récupérées sans aucun problème Si les dernières données et le contenu POSTÉ sont les mêmes
Si vous pouvez rediriger vers la bonne URL après le POST Si l'état de la réponse après POST est 302, réponse['location']:Est la bonne destination de redirection
GET Si le modèle est renvoyé correctement Si le modèle spécifié est utilisé
La requête GET se distingue-t-elle de la requête POST? Si le nombre d'articles a augmenté au moment de GET
La liste d'articles est-elle affichée au moment de la demande GET? Si le contenu renvoyé contient l'élément ajouté
Stockage de données Si les données sont correctement enregistrées Si le nombre d'articles a augmenté du montant ajouté en ajoutant des articles
Si l'élément récupéré correspond aux données enregistrées

Recommended Posts

Développement piloté par les tests avec Django Partie 3
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
Test Django
Django a commencé la partie 1
Django a commencé la partie 4
CRUD avec 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 ⑤
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
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
[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)
Tutoriel pour faire du développement piloté par les tests (TDD) avec Flask-1 Test Client Edition
Traitement d'image avec Python (partie 2)
Etudier Python avec freeCodeCamp part1
Faites Django avec CodeStar (Python3.6.8, Django2.2.9)
Django à partir de zéro (partie: 2)
Images en bordure avec python Partie 1
Lancez-vous avec Django! ~ Tutoriel ⑤ ~
Django à partir de zéro (partie: 1)
Tester les logiciels embarqués avec Google Test
Grattage avec Selenium + Python Partie 1
Environnement de site Web de configuration minimale avec django