Django
Unit Testing
Suche…
Testen - ein vollständiges Beispiel
Dies setzt voraus, dass Sie die Dokumentation zum Starten eines neuen Django-Projekts gelesen haben. Nehmen wir an, die Haupt-App in Ihrem Projekt heißt td (kurz für testgetrieben). Um Ihren ersten Test zu erstellen, erstellen Sie eine Datei mit dem Namen test_view.py und fügen Sie den folgenden Inhalt ein.
from django.test import Client, TestCase
class ViewTest(TestCase):
def test_hello(self):
c = Client()
resp = c.get('/hello/')
self.assertEqual(resp.status_code, 200)
Sie können diesen Test mit durchführen
./manage.py test
und es wird am natürlichsten scheitern! Es wird ein Fehler ähnlich dem folgenden angezeigt.
Traceback (most recent call last):
File "/home/me/workspace/td/tests_view.py", line 9, in test_hello
self.assertEqual(resp.status_code, 200)
AssertionError: 200 != 404
Warum passiert das? Weil wir noch keine Sicht dafür definiert haben! Also machen wir's. Erstellen Sie eine Datei mit dem Namen views.py, und fügen Sie den folgenden Code ein
from django.http import HttpResponse
def hello(request):
return HttpResponse('hello')
Als nächstes ordnen Sie es dem / hello / durch Bearbeiten der URLs py wie folgt zu:
from td import views
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^hello/', views.hello),
....
]
Führen Sie den Test jetzt erneut aus ./manage.py test
erneut und Viola !!
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Django-Modelle effektiv testen
Eine Klasse annehmen
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('view_author', args=[str(self.id)])
class Book(models.Model):
author = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
private = models.BooleanField(default=false)
publish_date = models.DateField()
def get_absolute_url(self):
return reverse('view_book', args=[str(self.id)])
def __str__(self):
return self.name
Testbeispiele
from django.test import TestCase
from .models import Book, Author
class BaseModelTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(BaseModelTestCase, cls).setUpClass()
cls.author = Author(name='hawking')
cls.author.save()
cls.first_book = Book(author=cls.author, name="short_history_of_time")
cls.first_book.save()
cls.second_book = Book(author=cls.author, name="long_history_of_time")
cls.second_book.save()
class AuthorModelTestCase(BaseModelTestCase):
def test_created_properly(self):
self.assertEqual(self.author.name, 'hawking')
self.assertEqual(True, self.first_book in self.author.book_set.all())
def test_absolute_url(self):
self.assertEqual(self.author.get_absolute_url(), reverse('view_author', args=[str(self.author.id)]))
class BookModelTestCase(BaseModelTestCase):
def test_created_properly(self:
...
self.assertEqual(1, len(Book.objects.filter(name__startswith='long'))
def test_absolute_url(self):
...
Ein paar Punkte
-
created_properly
Tests werden verwendet, um die Zustandseigenschaften von Django-Modellen zu überprüfen. Sie helfen dabei, Sicherheitsmaßnahmen zu treffen, wenn wir Standardwerte, file_upload_paths usw. geändert haben. -
absolute_url
mag trivial erscheinen, aber ich habe festgestellt, dass es mir dabei geholfen hat, einige Fehler beim Ändern von URL-Pfaden zu vermeiden - Ebenso schreibe ich Testfälle für alle innerhalb eines Modells implementierten Methoden (unter Verwendung von
mock
Objekten usw.). - Durch die Definition eines gemeinsamen
BaseModelTestCase
wir die notwendigen Beziehungen zwischen den ModellenBaseModelTestCase
, um ein ordnungsgemäßes Testen sicherzustellen.
Wenn Sie Zweifel haben, schreiben Sie einen Test. Triviale Verhaltensänderungen werden durch die Aufmerksamkeit für Details aufgefangen und lange vergessene Teile des Codes verursachen keine unnötigen Probleme.
Zugriffssteuerung in Django-Ansichten testen
tl; dr : Erstellen Sie eine Basisklasse, die zwei Benutzerobjekte definiert (z. B. user
und another_user
). Erstellen Sie Ihre anderen Modelle und definieren Sie drei Client
Instanzen.
-
self.client
: In Browser angemeldeteruser
-
self.another_client
: Repräsentant eines anderenanother_user
-
self.unlogged_client
: Repräsentation einer nicht angemeldeten Person
Greifen Sie jetzt auf alle Ihre öffentlichen und privaten URLs von diesen drei Clientobjekten zu und richten Sie die erwartete Antwort ein. Im Folgenden habe ich die Strategie für ein Book
Objekt vorgestellt, das entweder private
(im Besitz einiger privilegierter Benutzer) oder public
(für jeden sichtbar) sein kann.
from django.test import TestCase, RequestFactory, Client
from django.core.urlresolvers import reverse
class BaseViewTestCase(TestCase):
@classmethod
def setUpClass(cls):
super(BaseViewTestCase, cls).setUpClass()
cls.client = Client()
cls.another_client = Client()
cls.unlogged_client = Client()
cls.user = User.objects.create_user(
'dummy',password='dummy'
)
cls.user.save()
cls.another_user = User.objects.create_user(
'dummy2', password='dummy2'
)
cls.another_user.save()
cls.first_book = Book.objects.create(
name='first',
private = true
)
cls.first_book.readers.add(cls.user)
cls.first_book.save()
cls.public_book = Template.objects.create(
name='public',
private=False
)
cls.public_book.save()
def setUp(self):
self.client.login(username=self.user.username, password=self.user.username)
self.another_client.login(username=self.another_user.username, password=self.another_user.username)
"""
Only cls.user owns the first_book and thus only he should be able to see it.
Others get 403(Forbidden) error
"""
class PrivateBookAccessTestCase(BaseViewTestCase):
def setUp(self):
super(PrivateBookAccessTestCase, self).setUp()
self.url = reverse('view_book',kwargs={'book_id':str(self.first_book.id)})
def test_user_sees_own_book(self):
response = self.client.get(self.url)
self.assertEqual(200, response.status_code)
self.assertEqual(self.first_book.name,response.context['book'].name)
self.assertTemplateUsed('myapp/book/view_template.html')
def test_user_cant_see_others_books(self):
response = self.another_client.get(self.url)
self.assertEqual(403, response.status_code)
def test_unlogged_user_cant_see_private_books(self):
response = self.unlogged_client.get(self.url)
self.assertEqual(403, response.status_code)
"""
Since book is public all three clients should be able to see the book
"""
class PublicBookAccessTestCase(BaseViewTestCase):
def setUp(self):
super(PublicBookAccessTestCase, self).setUp()
self.url = reverse('view_book',kwargs={'book_id':str(self.public_book.id)})
def test_user_sees_book(self):
response = self.client.get(self.url)
self.assertEqual(200, response.status_code)
self.assertEqual(self.public_book.name,response.context['book'].name)
self.assertTemplateUsed('myapp/book/view_template.html')
def test_another_user_sees_public_books(self):
response = self.another_client.get(self.url)
self.assertEqual(200, response.status_code)
def test_unlogged_user_sees_public_books(self):
response = self.unlogged_client.get(self.url)
self.assertEqual(200, response.status_code)
Die Datenbank und das Testen
Django verwendet beim Testen spezielle Datenbankeinstellungen, sodass Tests die Datenbank normalerweise verwenden können, aber standardmäßig in einer leeren Datenbank ausgeführt werden. Datenbankänderungen in einem Test werden von einem anderen nicht erkannt. Zum Beispiel werden die beiden folgenden Tests bestanden:
from django.test import TestCase
from myapp.models import Thing
class MyTest(TestCase):
def test_1(self):
self.assertEqual(Thing.objects.count(), 0)
Thing.objects.create()
self.assertEqual(Thing.objects.count(), 1)
def test_2(self):
self.assertEqual(Thing.objects.count(), 0)
Thing.objects.create(attr1="value")
self.assertEqual(Thing.objects.count(), 1)
Fixtures
Wenn Sie Datenbankobjekte für mehrere Tests verwenden möchten, erstellen Sie sie entweder in der setUp
Methode des Testfalls. Wenn Sie in Ihrem Django-Projekt Fixtures definiert haben, können diese zusätzlich so eingefügt werden:
class MyTest(TestCase):
fixtures = ["fixture1.json", "fixture2.json"]
Standardmäßig sucht Django in jeder App nach Fixtures im fixtures
Verzeichnis. Weitere Verzeichnisse können mit der Einstellung FIXTURE_DIRS
gesetzt werden:
# myapp/settings.py
FIXTURE_DIRS = [
os.path.join(BASE_DIR, 'path', 'to', 'directory'),
]
Angenommen, Sie haben ein Modell wie folgt erstellt:
# models.py
from django.db import models
class Person(models.Model):
"""A person defined by his/her first- and lastname."""
firstname = models.CharField(max_length=255)
lastname = models.CharField(max_length=255)
Dann könnten Ihre .json-Fixtures so aussehen:
# fixture1.json
[
{ "model": "myapp.person",
"pk": 1,
"fields": {
"firstname": "Peter",
"lastname": "Griffin"
}
},
{ "model": "myapp.person",
"pk": 2,
"fields": {
"firstname": "Louis",
"lastname": "Griffin"
}
},
]
Verwenden Sie die Testdatenbank erneut
Um Ihre Testläufe zu beschleunigen, können Sie dem Verwaltungsbefehl mitteilen, die Testdatenbank erneut zu verwenden (und zu verhindern, dass sie vor jedem Testlauf erstellt und gelöscht wird). Dies kann mit dem keepdb-Flag (oder der Abkürzung -k
) wie folgt durchgeführt werden:
# Reuse the test-database (since django version 1.8)
$ python manage.py test --keepdb
Begrenzen Sie die Anzahl der durchgeführten Tests
Es ist möglich, die von manage.py test
ausgeführten Tests manage.py test
indem Sie manage.py test
, welche Module vom manage.py test
sollen:
# Run only tests for the app names "app1"
$ python manage.py test app1
# If you split the tests file into a module with several tests files for an app
$ python manage.py test app1.tests.test_models
# it's possible to dig down to individual test methods.
$ python manage.py test app1.tests.test_models.MyTestCase.test_something
Wenn Sie eine Reihe von Tests durchführen möchten, können Sie ein Muster von Dateinamen übergeben. Beispielsweise möchten Sie möglicherweise nur Tests ausführen, die Ihre Modelle betreffen:
$ python manage.py test -p test_models*
Creating test database for alias 'default'...
.................................................
----------------------------------------------------------------------
Ran 115 tests in 3.869s
OK
Schließlich ist es möglich, die Testsuite beim ersten Fehler mithilfe von --failfast
. Dieses Argument ermöglicht es, den potenziellen Fehler in der Suite schnell zu ermitteln:
$ python manage.py test app1
...F..
----------------------------------------------------------------------
Ran 6 tests in 0.977s
FAILED (failures=1)
$ python manage.py test app1 --failfast
...F
======================================================================
[Traceback of the failing test]
----------------------------------------------------------------------
Ran 4 tests in 0.372s
FAILED (failures=1)