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/
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'...
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)
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.
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.
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.)
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.
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.
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.
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
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.
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
Recommended Posts