Python Django Tutorial (7)

Dies ist ein Material für Lernsitzungen. Dieses Mal werde ich verschiedene Dinge über Test erklären.

Honke Tutorial (Im Honke wird es als Tutorial 5 erklärt) https://docs.djangoproject.com/ja/1.9/intro/tutorial05/

Andere Tutorials

Lassen Sie uns den Test ausführen

Quelle → 0b1bcccc1da95d274cd92253fa44af7a84c51404

Es ist möglicherweise nicht sehr relevant für ein Programm, das Sie ein wenig als Hobby schreiben. Für Programme, die über einen langen Zeitraum nach und nach renoviert werden, macht es Sie glücklich, einen guten Test zu schreiben.

Sprechen Sie über den Zweck des Tests, warum er notwendig ist und wie nützlich er im Original-Tutorial und im [Qiita-Artikel] ist (http://qiita.com/search?q=%E3%83%86%E3%82%B9%) Bitte erkundigen Sie sich bei E3% 83% 88).

In Python wird unittest als Standardbibliothek vorbereitet. django bietet TestCase-Klassen- und Ausführungsbefehle, die es erweitern.

Der Code für den Test wird in "tests.py" geschrieben, das beim Erstellen der Anwendung erstellt wurde. Im Tutorial habe ich eine Umfrage-App mit dem Befehl "$ python manage.py startapp polls" erstellt. Zu diesem Zeitpunkt sollte eine Datei mit dem Namen "tests.py" zusammen mit "views.py" und "models.py" erstellt werden.

./manage.py
  ├ tutorial/  #Speicherort der Einstellungsdateien usw.
  │  ├ ...
  │  └ settings.py
  └ polls/  # ← "manage.py startapp polls"Verzeichnis für Apps erstellt in
    ├ models.py
    ├ views.py
    ├ ...
    └ tests.py  #← Dies sollte auch automatisch erstellt werden. Ich werde hier einen Test schreiben

Wenn Sie tests.py öffnen, sollte standardmäßig der folgende Status angezeigt werden.

tests.py


from django.test import TestCase

# Create your tests here.

Wenn Sie der Klasse, die TestCase erbt, eine Methode hinzufügen, die mit "test" beginnt, sammelt der Django die Methode automatisch und führt sie aus. Bitte beachten Sie, dass es nur ausgeführt wird, wenn die folgenden drei Bedingungen erfüllt sind.

--Erstellen Sie eine Datei unter der Anwendung, die mit test beginnt --Erstellen Sie eine Klasse, die django.test.TestCase erbt --Starten Sie den Methodennamen mit test

tests.py


from django.test import TestCase


class PollsTest(TestCase):
    def test_hoge(self):
        print('test!')
$ ./manage.py test
Creating test database for alias 'default'...
test!
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

Machen Sie es zu einem testähnlichen Test

Das Testen ist normalerweise die Aufgabe zu überprüfen, ob der Rückgabewert einer Funktion oder Methode die erwarteten Ergebnisse liefert. TestCase of Python bietet einige Assert-Methoden zum Vergleich. Wenn das Ergebnis nicht den Erwartungen entspricht, schlägt Test fehl. Die Tabelle zeigt vier typische.

Methode Checkliste
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False

Wenn Sie sich an die obigen vier erinnern, können Sie fast alle Fälle abdecken, aber es gibt einige Assert-Methoden, die als Verknüpfungen bequem zu verwenden sind. Vielleicht möchten Sie die offizielle Website einmal lesen. http://docs.python.jp/3/library/unittest.html#assert-methods

Beispielsweise ist Code, der beim Ausführen einer Funktion auf eine Ausnahme prüft, mit (assertRaises "einfacher (und leichter zu verstehen) als mit" assertTrue "zu schreiben.

class PollsTest(TestCase):
    def test_exception(self):
        try:
            func()
        except Exception:
            pass
        else:
            self.assertTrue(False)  #Immer fehlschlagen, wenn keine Ausnahme auftritt

    def test_exception2(self):
        self.assertRaises(Exception, func)

Wertevergleich

Quelle → 4301169d6eb1a06e01d93282fbaab0f1fc2c367e

Schreiben wir einen Test mit assertEqual und assertNotEqual. Wie in der Tabelle geschrieben, sind die beiden oben genannten Vergleiche mit == und ! =, Also sind 1 und True und 0 und False gleich. Wenn Sie die Typen klar vergleichen möchten, verwenden Sie "assertIs" oder "assertIsNone" Es ist notwendig, etwas wie "assertTrue (bool (1))" zu entwickeln. (AssertIs und assertIsNone scheinen nicht in der Python2-Serie zu sein)

polls/tests.py


from django.test import TestCase


class PollsTest(TestCase):
    def test_success(self):
        self.assertEqual(1, 1)
        self.assertEqual(1, True)

    def test_failed(self):
        self.assertNotEqual(0, False)
$ ./manage.py test
Creating test database for alias 'default'...
F.
======================================================================
FAIL: test_failed (polls.tests.PollsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shimomura/pbox/work/tutorial/tutorial/polls/tests.py", line 10, in test_failed
    self.assertNotEqual(0, False)
AssertionError: 0 == False

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

Wenn der Test erfolgreich ist, wird . angezeigt, und wenn er fehlschlägt, wird F zusammen mit der Stelle angezeigt, an der er fehlgeschlagen ist. Da die Anzahl der Tests = die Anzahl der Methoden ist, wird die Anzahl der Tests als eins gezählt, unabhängig davon, wie oft Sie assert in derselben Methode aufrufen. Es ist einfacher zu verstehen, welcher Test fehlgeschlagen ist, wenn Sie den Test detailliert schreiben, aber die Testzeit erhöht sich aufgrund der Initialisierung usw.

Modelltest

Quelle → c2c65afcb0d26913f683cb7b64f388925d7896eb

Nachdem Sie nun wissen, wie die Werte verglichen werden, importieren wir das Modell und testen, ob die Methode ordnungsgemäß funktioniert. Das heißt, es gibt bisher nur eine methodenähnliche Methode im Fragenmodell ...

Da das Testziel eine Methode des Fragenmodells ist, muss im Testcode eine Instanz des Fragenmodells erstellt werden.

polls/models.py (zu testende Methode)



class Question(models.Model):
...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

polls/tests.py (Testcode)


from django.test import TestCase
from django.utils import timezone

from .models import Question


class PollsTest(TestCase):
    def test_was_published_recently(self):
        obj = Question(pub_date=timezone.now())
        self.assertTrue(obj.was_published_recently())
$ ./manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

Es ist so.

Test hinzufügen

Quelle → 1062f1b4362ef2bf4e9ea483b5e178e7ca82c0c6

Es ist einsam, dass die Anzahl der Testausführungen für eine Methode eins ist. Erhöhen wir also das Muster etwas weiter. Der Test besteht übrigens darin, zu prüfen, ob die Methode überhaupt wie beabsichtigt funktioniert. Das beabsichtigte Verhalten von "was_published_recently" besteht darin, festzustellen, ob kürzlich eine ** Frage ** veröffentlicht wurde. ** Vor kurzem ** ist innerhalb eines Tages von der Gegenwart hier. Es gibt verschiedene Testmethoden, aber im Fall einer solchen Methode ist es üblich, Werte vor und nach der Bedingung anzugeben und zu überprüfen, wie sie beurteilt werden. (Dies wird als Grenzwertanalyse, Randwerttest usw. bezeichnet.)

Kobito.1aoQFZ.png

In der Abbildung sieht es so aus. Fügen wir also einen Test hinzu, um diesen Zustand zu überprüfen. In Honke Tutorial wird ein Test als separate Methode hinzugefügt, diesmal wird jedoch "test_was_published_recently" vorerst erweitert. Machen. ① und ②, ③ und ④ sollten so nah wie möglich sein, aber diesmal ist es Zeit, also werde ich es ein wenig lockern.

polls/tests.py


from datetime import timedelta

from django.test import TestCase
from django.utils import timezone

from .models import Question


class PollsTest(TestCase):
    def test_was_published_recently(self):
        #Ein bisschen älter als ein Tag
        obj = Question(pub_date=timezone.now() - timedelta(days=1, minutes=1))
        self.assertFalse(obj.was_published_recently())

        #Ein bisschen neuer als ein Tag
        obj = Question(pub_date=timezone.now() - timedelta(days=1) + timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently())

        #Erst kürzlich veröffentlicht
        obj = Question(pub_date=timezone.now() - timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently())

        #Bald veröffentlicht
        obj = Question(pub_date=timezone.now() + timedelta(minutes=1))
        self.assertFalse(obj.was_published_recently())
$ ./manage.py test
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_was_published_recently (polls.tests.PollsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shimomura/pbox/work/tutorial/tutorial/polls/tests.py", line 25, in test_was_published_recently
    self.assertFalse(obj.was_published_recently())
AssertionError: True is not false

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
Destroying test database for alias 'default'...

Im Moment hat die Methode "was_published_recently" keine zukünftige Beurteilung, daher schlägt der Test in (4) fehl. Auf diese Weise, auch wenn Sie wissen, dass der Test fehlgeschlagen ist (= FAIL: test_was_published_recently (polls.tests.PollsTest)), wenn Sie die Methoden geschrieben haben, ohne sie zu teilen. Der Grund für den Fehler (1), (2), (3) oder (4) kann nur anhand der Zeilennummer ermittelt werden. Da Sie jedoch eine Nachricht an "assert" anhängen können, ist es etwas einfacher zu verstehen, wenn Sie eine Nachricht festlegen.

polls/tests.py (Nachricht hinzufügen, um zu bestätigen)


...
    def test_was_published_recently(self):
        #Ein bisschen älter als ein Tag
        obj = Question(pub_date=timezone.now() - timedelta(days=1, minutes=1))
        self.assertFalse(obj.was_published_recently(), 'Veröffentlicht vor 1 Tag und 1 Minute')

        #Ein bisschen neuer als ein Tag
        obj = Question(pub_date=timezone.now() - timedelta(days=1) + timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently(), 'Veröffentlicht in 1 Tag und 1 Minute')

        #Erst kürzlich veröffentlicht
        obj = Question(pub_date=timezone.now() - timedelta(minutes=1))
        self.assertTrue(obj.was_published_recently(), 'Veröffentlicht vor 1 Minute')

        #Bald veröffentlicht
        obj = Question(pub_date=timezone.now() + timedelta(minutes=1))
        self.assertFalse(obj.was_published_recently(), 'Veröffentlicht in 1 Minute')
$ ./manage.py test
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_was_published_recently (polls.tests.PollsTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/shimomura/pbox/work/tutorial/tutorial/polls/tests.py", line 25, in test_was_published_recently
    self.assertFalse(obj.was_published_recently(), 'Veröffentlicht in 1 Minute')
AssertionError: True is not false :Veröffentlicht in 1 Minute

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

Das Hinzufügen einer Nachricht erleichterte das Verständnis. Diese Methode ist auch nicht perfekt. Wenn beispielsweise die Beurteilung von ① fehlschlägt, endet der Test dort, sodass nicht bestätigt werden kann, ob ② normal funktioniert. Es kann jedoch gesagt werden, dass es nicht notwendig ist, (2) bis (4) zu überprüfen, da es notwendig ist, es zu korrigieren, egal wie der andere funktioniert, wenn auch nur ein Test fehlschlägt ...

In jedem Fall ergab der Test, dass es kein zukünftiges Urteil gab. Lassen Sie es uns also sofort beheben.

polls/models.py


...
class Question(models.Model):
...
    def was_published_recently(self):
        return timezone.now() >= self.pub_date >= timezone.now() - datetime.timedelta(days=1)
$ ./manage.py test
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

Sie haben die Methode jetzt so geändert, dass sie wie beabsichtigt funktioniert.

Test anzeigen

Honke Tutorial

Im Modelltest haben wir die Funktionsweise der Logik der definierten Methode überprüft. Wenn der Benutzer im Ansichtstest die Seite tatsächlich anzeigt,

Wird geprüft.

django bietet eine Test-Client-Klasse, die den Zugriff in einem Browser simuliert. Schreiben wir zunächst einen Test für den Umfrageindexbildschirm. In TestCase können Sie mit self.client auf die Client-Klasse zugreifen.

Überprüfen Sie den Statuscode

Quelle → 852b862423f9908480e60c5657ee028782530933

Kann die Seite normal angezeigt werden? Um zu überprüfen, überprüfen Sie status_code. Ich denke, der Statuscode, den Sie oft sehen, wenn Sie Django berühren, lautet wie folgt. → [Statuscode (Wikipedia)](https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82 % B9% E3% 82% B3% E3% 83% BC% E3% 83% 89)

Code Bedeutung
200 Erfolgreiche Fertigstellung
400 Parameterfehler. Zum Beispiel, wenn der an das Formular übergebene Wert falsch ist
401 Zugriff ohne Anmeldung an einer Seite, für die eine Authentifizierung erforderlich ist
404 Keine Seite
500 Interner Fehler. Dies ist der Fall, wenn der Python-Code seltsam ist

Wenn es in den 200ern ist, funktioniert es normal, wenn es in den 400ern ist, ist es ein Fehler aufgrund eines Betriebsfehlers auf der Clientseite. Denken Sie daran, dass die 500er Fehler aufgrund von Programmen und Servern sind. Dieses Mal werden wir bestätigen, dass die Seite normal angezeigt werden kann. Bestätigen Sie also, dass response.status_code 200 ist.

polls/tests.py


...
from django.shortcuts import resolve_url
...

class ViewTest(TestCase):
    def test_index(self):
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(200, response.status_code)

Dieses Mal habe ich "self.client.get" verwendet, da es sich um eine Seitenanzeige handelt (Zugriff über die GET-Methode). Verwenden Sie beim Testen von "Senden", z. B. "Formular", "self.client.post", da es sich um eine POST-Methode handelt. Sie müssen die URL als erstes Argument übergeben.

Bestätigung des Inhalts

Quelle → d1ad02228ca2cb2370b323fd98cd27a0cf02d7a9

Wenn der status_code ** 200 ** lautet, bedeutet dies, dass die Seite angezeigt wird, wenn sie vorerst mit einem Browser angezeigt wird. Als nächstes überprüfen wir, ob der angezeigte Inhalt wie beabsichtigt ist.

Im ursprünglichen Tutorial verwenden wir "assertContains", um den Inhalt des tatsächlich angezeigten HTML-Codes zu überprüfen. Zum Beispiel self.assertContains(response, "No polls are available.") Wenn Sie wie schreiben, stellen Sie sicher, dass "Keine Umfragen verfügbar" im angezeigten HTML-Quellcode enthalten ist.

Der Rückgabewert von "self.client.get", "response", hat den Kontext, der zum Rendern des HTML-Codes verwendet wird Indem Sie den Inhalt dieses Kontexts überprüfen, können Sie überprüfen, ob das dynamisch generierte Teil den beabsichtigten Wert hat.

polls/views.py


...
def index(request):
    return render(request, 'polls/index.html', {
        'questions': Question.objects.all(),
    })
...

Das Obige ist die zu testende Ansichtsfunktion, aber das Wörterbuch des dritten Arguments der Renderfunktion ({'Fragen': Question.objects.all ()}) Wird der Kontext an die Vorlage übergeben?

Wenn der Test ausgeführt wird, wird eine Datenbank für den Test generiert und die Datenbank für jede Testmethode neu erstellt. Selbst wenn Sie einen Test schreiben, der die Datenbank betreibt, wird er daher nicht von der Produktionsdatenbank oder anderen Tests beeinflusst. Die Indexfunktion in views.py ruft den gesamten Inhalt des Question-Objekts ab. Wie oben erwähnt, sollte der Inhalt der Datenbank leer sein. Schreiben Sie also einen Test, um zu überprüfen, ob er tatsächlich leer ist.

Der Inhalt des Kontexts ['Fragen'] ist ein QuerySet für das Fragenmodell. Die Anzahl der Fragentabellen wird durch Aufrufen von count () von QuerySet ermittelt.

polls/tests.py


...
class ViewTest(TestCase):
    def test_index(self):
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(200, response.status_code)
        self.assertEqual(0, response.context['questions'].count())

Wenn der Test erfolgreich bestanden wurde, registrieren wir die Daten und überprüfen die Änderung der Anzahl der Fälle und des Inhalts.

polls/tests.py


class ViewTest(TestCase):
    def test_index(self):
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(200, response.status_code)
        self.assertEqual(0, response.context['questions'].count())

        Question.objects.create(
            question_text='aaa',
            pub_date=timezone.now(),
        )
        response = self.client.get(resolve_url('polls:index'))
        self.assertEqual(1, response.context['questions'].count())

        self.assertEqual('aaa', response.context['questions'].first().question_text)

mock

Python Official

Derzeit gibt es kein solches Programm, aber je nach Anwendung gibt es natürlich Programme, die APIs verwenden, um sich mit der Außenwelt zu verbinden. Um Programme zu testen, die eine Kommunikation mit der Außenwelt erfordern, bietet Python eine Scheinbibliothek. Indem Sie den Teil, in dem die Kommunikation mit der Außenwelt stattfindet, durch Mock ersetzen, können Sie den Rückgabewert der API durch Ihren bevorzugten Wert ersetzen.

Kobito.eoQ3cl.png

Versuchen wir, die Funktion, die die API-Kommunikation durchführt, durch die Funktion für Dummy zu ersetzen. Verwenden Sie zum Ersetzen unittest.mock.patch. Sie können den Ort, an dem der Anruf getätigt wird, in "with" einschließen oder "decorator" in der Testmethode selbst festlegen.

from unittest import mock


def dummy_api_func():
    return 'dummy api response'


def api_func():
    return 'api response'


class PollsTest(TestCase):
    def test_mocked_api(self):
        ret = api_func()
        print('ret:', ret)
        with mock.patch('polls.tests.api_func', dummy_api_func):
            ret = api_func()  #←←←←←←←←← Dieser Anruf wird zum Dummy
            print('mocked_ret:', ret)

    @mock.patch('polls.tests.api_func', dummy_api_func)
    def test_mocked_api_with_decorator(self):
        ret = api_func()  #Wenn Sie Dekorateur verwenden, ist dies auch Dummy
        print('decorator:', ret)
$ ./manage.py testCreating test database for alias 'default'...
ret: api response
mocked_ret: dummy api response   #← Patch mit mit anwenden
.decorator: dummy api response   #← Patch vom Dekorateur anwenden

Im nächsten Tutorial werden wir die ergänzende Erklärung zu Model und der Operation von der Shell aus erklären. Zum nächsten Tutorial

Andere Tutorials

Recommended Posts

Python Django Tutorial (5)
Python Django Tutorial (2)
Python Django Tutorial (7)
Python Django Tutorial (1)
Python Django Tutorial Tutorial
Python Django Tutorial (3)
Python Django Tutorial (4)
Zusammenfassung des Python Django-Tutorials
Python-Tutorial
Python Django Tutorial Cheet Sheet
Zusammenfassung des Python-Tutorials
Django Tutorial Memo
Starten Sie das Django Tutorial 1
Django 1.11 wurde mit Python3.6 gestartet
[Docker] Tutorial (Python + PHP)
Django Python Web Framework
Django Polymorphic Associations Tutorial
Django Oscar einfaches Tutorial
Versuchen Sie Debian + Python 3.4 + django1.7 ……
Python OpenCV Tutorial Memo
[Python-Tutorial] Datenstruktur
Django Girls Tutorial Hinweis
Cloud Run Tutorial (Python)
Python Django CSS reflektiert
Mach Django mit CodeStar (Python3.6.8, Django2.2.9)
Fangen Sie mit Django an! ~ Tutorial ⑤ ~
Einführung in Python Django (2) Win
[Python-Tutorial] Kontrollstruktur-Tool
Python
Mach Django mit CodeStar (Python3.8, Django2.1.15)
Python3 + Django ~ Mac ~ mit Apache
ToDo-Listenerstellung [Python Django]
Erste Schritte mit Python Django (1)
Django
Erste Schritte mit Python Django (4)
Erste Schritte mit Python Django (3)
Fangen Sie mit Django an! ~ Tutorial ⑥ ~
Installieren Sie Python 3.7 und Django 3.0 (CentOS)
[Python] Persönliches Tutorial zum Entscheidungsbaum
Einführung in Python Django (6)
Erste Schritte mit Python Django (5)
Bis zur Veröffentlichung von Python [Django] de Web Service [Tutorial Teil 1]
8 häufig verwendete Befehle in Python Django
Erstellen Sie eine neue Anwendung mit Python, Django
Python + Django + Scikit-Learn + Mecab (1) mit Heroku
Python + Django + Scikit-Learn + Mecab (2) mit Heroku
Django Girls Tutorial Zusammenfassung Erste Hälfte
Stolpern Sie beim Django 1.7-Tutorial
Stellen Sie das Django-Lernprogramm für IIS bereit ①
Installieren Sie das Python Framework Django mit pip
Einführung in Python Django (2) Mac Edition
[Python Tutorial] Eine einfache Einführung in Python
Lernverlauf für die Teilnahme an der Team-App-Entwicklung mit Python ~ Django Tutorial 5 ~
Lernverlauf für die Teilnahme an der Entwicklung von Team-Apps mit Python ~ Django Tutorial 4 ~
Lernverlauf für die Teilnahme an der Team-App-Entwicklung mit Python ~ Django Tutorial 1, 2, 3 ~
Lernverlauf für die Teilnahme an der Team-App-Entwicklung mit Python ~ Django Tutorial 6 ~
Lernverlauf für die Teilnahme an der Team-App-Entwicklung mit Python ~ Django Tutorial 7 ~
Django Crispy Tutorial (Umgebungskonstruktion auf Mac)
Kafka Python
Django-Update