[PYTHON] Testgetriebene Entwicklung mit Django Teil 4

Testgetriebene Entwicklung mit Django Teil 4

Dies ist eine Lernnotiz zum Verständnis von Test Driven Development (TDD) in Django.

Referenzen sind [** Testgetriebene Entwicklung mit Python: Befolgen Sie die Testziege: Verwenden von Django, Selen und JavaScript (englische Ausgabe) 2. Ausgabe **](https://www.amazon.co.jp/dp/B074HXXXLS Wir werden mit dem Lernen basierend auf / ref = dp-kindle-redirect? _ Encoding = UTF8 & btkr = 1) fortfahren.

In diesem Buch führen wir Funktionstests mit der Django 1.1-Serie und FireFox durch. Dieses Mal werden wir jedoch Funktionstests mit der Djagno 3-Serie und Google Chrome durchführen. Ich habe auch einige persönliche Änderungen vorgenommen (z. B. das Ändern des Projektnamens in Config), aber es gibt keine wesentlichen Änderungen.

⇒⇒ Klicken Sie hier für Teil 1 - Kapitel 1 ⇒⇒ Klicken Sie hier für Teil 2 - Kapitel 2 ⇒⇒ Klicken Sie hier für Teil 3 - Kapitel 3

Part1. The Basics of TDD and Django

Chapter4. What Are We Doing with All These Tests? (And, Refactoring)

Ich habe den TDD-Fluss bis Kapitel 3 unterdrückt, aber er war etwas zu detailliert und langweilig (insbesondere home_page = None). Ist es, um ehrlich zu sein, notwendig, den Code zu schreiben, während die Ergebnisse des Komponententests so detailliert betrachtet werden?

Programming is Like Pulling a Bucket of Water Up from a Well

TDD ist ein bisschen langweilig und nervig, aber es ist auch ein Stopp, um die Programmiererentwicklung zu schützen. Das Entwickeln mit TDD ist eine sehr anstrengende Aufgabe, aber es ist eine Entwicklungsmethode, die auf lange Sicht geschätzt wird. Der Trick besteht darin, die Entwicklung auf der Grundlage eines möglichst kleinen Tests fortzusetzen.

Using Selenium to Test User Interactions

Beim letzten Mal haben wir die Ansicht home_page aus dem Komponententest erstellt. Lassen Sie uns diesmal den Funktionstest erweitern.

# django-tdd/functional_tests.py

from selenium import webdriver
from selenium.webdriver.common.keys import Keys  #hinzufügen
import time  #hinzufügen

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 ist neu in-Ich habe gehört, dass es eine Do-App gibt und habe auf die Homepage zugegriffen.
        self.browser.get('http://localhost:8000')

        #Nobita hat den Seitentitel und den Header zu-Ich habe bestätigt, dass es sich um eine Do-App handelt.
        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 ist zu-Aufforderung zum Ausfüllen des Do-Elements,
        inputbox = self.browser.find_element_by_id('id_new_item')
        self.assertEqual(
            inputbox.get_attribute('placeholder'),
            'Enter a to-do item'
        )

        #Nobita schrieb in das Textfeld "Buy Dorayaki"(Sein bester Freund liebt Dorayaki)
        inputbox.send_keys('Buy dorayaki')

        #Wenn Nobita die Eingabetaste drückt, wird die Seite aktualisiert
        # "1:Dorayaki kaufen"Ist zu-Es stellt sich heraus, dass es als Element zur Aufgabenliste hinzugefügt wurde
        inputbox.send_keys(Keys.ENTER)
        time.sleep(1)  #Warten Sie auf die Seitenaktualisierung.

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

        #Über das Textfeld können Sie weiterhin Elemente ausfüllen
        #Ausgefüllt "Geld für Dorayaki aufladen"(Er hat wenig Geld)

        self.fail("Finish the test!")

        #Die Seite wurde erneut aktualisiert und ich konnte sehen, dass neue Elemente hinzugefügt wurden

        #Nobita ist das zu-Ich habe mich gefragt, ob die do-App meine Artikel richtig aufzeichnet.
        #Als ich die URL überprüfte, stellte ich fest, dass die URL eine bestimmte URL für Nobita zu sein scheint

        #Als Nobita versuchte, auf eine bestimmte URL zuzugreifen, die er einmal bestätigt hatte,

        #Ich war glücklich einzuschlafen, weil die Gegenstände gelagert wurden.


if __name__ == '__main__':
    unittest.main(warnings='ignore')

Verbesserte Funktionsprüfung. Lassen Sie es uns tatsächlich testen.

#Starten Sie einen Entwicklungsserver
$ python manage.py runserver

#Starten Sie ein weiteres cmd, um einen Funktionstest auszuführen
$ 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)

Das Testergebnis war, dass das Element

nicht gefunden wurde. Was kann getan werden, um dies zu lösen? Zunächst haben wir den Funktionstest erweitert. Lassen Sie uns ihn also festschreiben.

$ 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

Lassen Sie uns die aktuellen Listen / tests.py überprüfen.

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

Wenn ich mir das anschaue, überprüfe ich, ob es eine bestimmte HTML-Zeichenfolge enthält, aber dies ist keine effektive Methode. Im Allgemeinen sollten beim Testen von Einheiten das Testen von Konstanten vermieden werden. Insbesondere ist HTML wie eine Sammlung von Konstanten (Text).

HTML sollte unter Verwendung von Vorlagen erstellt werden, und Funktionstests sollten unter der Annahme fortgesetzt werden.

Refactoring to Use a Template

Refactors listet / views.py auf, um bestimmte HTML-Dateien zurückzugeben. Das Gefühl des Refactorings mit TDD besteht darin, * vorhandene Funktionen zu verbessern, ohne sie zu ändern *. Das Refactoring kann nicht ohne Tests fortgesetzt werden. Lassen Sie uns zuerst einen Unit-Test durchführen.

$ 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'...

Wenn es sich um eine Fortsetzung des letzten Males handelt, sollte der Test problemlos bestanden werden. Jetzt erstellen wir eine Vorlage.

$ mkdir templates
$ cd templates
$ mkdir lists
$ type nul > lists\home.html
$ cd ../ # manage.Kehren Sie zu dem Verzeichnis zurück, in dem sich py befindet
<!-- templates/lists/home.html -->

<html>
    <title>To-Do lists</title>
</html>

Ändern Sie listen / views.py, um dies zurückzugeben.

# lists/views.py

from django.shortcuts import render


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

Machen wir einen Unit-Test.

$ 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'...

Sie können die Meldung "django.template.exceptions.TemplateDoesNotExist: home.html" sehen, obwohl Sie die Vorlage hätten erstellen sollen. Sie können auch sehen, dass das "return render (request, 'home.html')" in lists / views.py nicht funktioniert.

Dies liegt daran, dass Sie sich bei der Erstellung der Anwendung nicht bei Django registriert haben. Fügen wir es zu INSTALLED_APPS in config / settings.py hinzu.

# 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',  #hinzufügen
]

Lass es uns testen.

$ 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

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

Wenn Sie dies überprüfen, können Sie sehen, dass es mit self.assertTrue (html.endswith ('</ html>')) stolpert, aber listet / home.html endet sicherlich mit </ html> . Ich werde. Sie können dies überprüfen, indem Sie einem Teil von html von lists / tests.py print (repr (html))` hinzufügen und ausführen, aber am Ende des Satzes von lists / home.html den Zeilenvorschubcode \ n Wurde hinzugefügt. Einige Tests müssen geändert werden, um dies zu bestehen.

# lists/tests.py

#~~Kürzung~~
    self.assertTrue(html.strip().endswith('</html>'))  #Veränderung

Lass es uns jetzt laufen lassen.

$ python manage.py test

----------------------------------------------------------------------
Ran 2 tests in 0.032s

OK

Der Komponententest wurde bestanden. Sie haben jetzt lists / views.py geändert, um eine Vorlage zurückzugeben. Versuchen Sie dann, list / tests.py umzugestalten, um festzustellen, ob die richtige Vorlage gerendert wird.

The Django Test Client

Mit der von Django bereitgestellten .assertTemplteUsed können Sie effektiv testen, ob die richtige Vorlage zurückgegeben wird. Fügen wir es als Teil des Tests hinzu.

# lists/tests.py

# ~~Kürzung~~

def test_home_page_returns_current_html(self):
    response = self.client.get('/')  #Veränderung

    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>'))  #Veränderung

    self.assertTemplateUsed(response, 'lists/home.html')  #hinzufügen

Geändert zu einer Anforderung mit dem Djagno-Testclient anstelle einer manuellen Anforderung mit HttpRequest () zur Verwendung von ".assertTemplateUsed".

$ python manage.py test

----------------------------------------------------------------------
Ran 2 tests in 0.040s

OK

Sie können diesen Django-Testclient und .assertTemplateUsed zusammen verwenden, um zu überprüfen, ob die URL zugeordnet ist und ob die angegebene Vorlage zurückgegeben wird. Daher könnten Listen / tests.py übersichtlicher umgeschrieben werden.

# lists/tests.py

from django.test import TestCase


class HomePageTest(TestCase):

    def test_users_home_template(self):
        response = self.client.get('/')  #URL-Auflösung
        self.assertTemplateUsed(response, 'lists/home.html')

Nachdem wir den Komponententest "lists / view.py" überarbeitet haben, legen wir fest.

$ git add .
$ git commit -m "Refactor home page view to user a template"

A Little More of Our Front Page

Der Komponententest wurde bestanden, der Funktionstest ist jedoch weiterhin fehlgeschlagen. Der Inhalt der Vorlage wird im Komponententest nicht ausgewertet. Daher wird anhand eines Funktionstests festgestellt, ob die Vorlage korrekt ist.

<!-- 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)

Fügen Sie einen Ort hinzu, an dem Sie ein neues Element eingeben können.

<!-- 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

Fügen wir einen Platzhalter hinzu.

<!-- 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"]"}

Fügen Sie ein Tabellen-Tag hinzu.

<!-- 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

Dies ist ein Fehler mit .assertTrue (any (~~)) in function_tests.py. any (Iterator) gibt True zurück, wenn sich das Argument im Iterator befindet. Die Funktion zum Zurückgeben des eingegebenen Werts als "1: Buy dorayaki" wird später implementiert. Fügen wir eine benutzerdefinierte Fehlermeldung hinzu, da "Neues Aufgabenelement wurde vorerst nicht in der Tabelle angezeigt".

# functional_tests.py

# ~~Kürzung~~
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"  #hinzufügen
)
$ 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

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

Lassen Sie uns begehen.

$ git add .
$ git commit -m "Front page HTML now generated from template"

Kapitel 4 Zusammenfassung

Implementierter Funktionstest, Komponententest, Komponententest und Codierungszyklus sowie Refactoring-Ablauf. Die Spitze ist lang. ..

Recommended Posts

Testgetriebene Entwicklung mit Django Teil 3
Testgetriebene Entwicklung mit Django Teil 4
Testgetriebene Entwicklung mit Django Teil 6
Testgetriebene Entwicklung mit Django Teil 2
Testgetriebene Entwicklung mit Django Teil 1
Testgetriebene Entwicklung mit Django Teil 5
Testgetriebenes Entwicklungsstart mit PySide & Pytest
Django-Test
[Test Driven Development (TDD)] Kapitel 21 Zusammenfassung
App-Entwicklung mit SQLite mit Django (PTVS)
Django startete Teil 1
Erleben Sie Teil I "Multinationale Währung" des Buches "Test Driven Development" mit Python
Internationalisierung mit Django
Django startete Teil 4
CRUD mit Django
Erste Django-Entwicklung
Erstellen Sie solche Testdaten mit Python (Teil 1)
[Python] Erstellen Sie mit Docker eine Django-Entwicklungsumgebung
Django Erste Schritte Teil 2 mit dem Eclipse Plugin (PyDev)
Erstellen Sie mit der Doker Toolbox eine Django-Entwicklungsumgebung
Authentifizieren Sie Google mit Django
Django 1.11 wurde mit Python3.6 gestartet
Primzahlbeurteilung mit Python
Laden Sie Dateien mit Django hoch
Mit Codetest stärken ⑦
PDF mit Django ausgeben
Mit Codetest stärken ⑨
Markdown-Ausgabe mit Django
Verwenden Sie Gentelella mit Django
Twitter OAuth mit Django
Mit Codetest stärken ⑤
Mit Codetest stärken ④
Erste Schritte mit Django 1
Primzahlbeurteilung mit Python
Mail mit Django senden
Sandkasten mit neo4j Teil 10
Webanwendung erstellt mit Python3.4 + Django (Teil.1 Umgebungskonstruktion)
Datei-Upload mit Django
Mit Codetest stärken ②
Verwenden Sie WENIGER mit Django
Pooling mechanisieren mit Django
Verwenden Sie MySQL mit Django
Mit Codetest stärken ①
Erstellen Sie eine Entwicklungsumgebung mit Poetry Django Docker Pycharm
[Memo] Django-Entwicklungsumgebung
Artikel, die die Systementwicklung mit Django (Python) ermöglichen _Einführung
Django ab heute
Erste Schritte mit Django 2
Mit Codetest stärken ⑧
Mit Codetest stärken ⑨
Tutorial für die testgetriebene Entwicklung (TDD) mit Flask-2-Dekorateuren
Erstellen Sie mit Docker eine Django-Entwicklungsumgebung! (Docker-compose / Django / postgreSQL / nginx)
[Memo] Erstellen Sie mit Docker eine Entwicklungsumgebung für Django + Nuxt.js
[Django] Erstellen Sie mit PyCharm schnell eine Entwicklungsumgebung für Django-Container (Docker)
Erstellen Sie mit Django eine Bulletin-Board-App von Grund auf neu. (Teil 2)
Erstellen Sie mit Django eine Bulletin-Board-App von Grund auf neu. (Teil 3)