Django
Testen van een eenheid
Zoeken…
Testen - een compleet voorbeeld
Dit veronderstelt dat u de documentatie hebt gelezen over het starten van een nieuw Django-project. Laten we aannemen dat de hoofdapp in uw project de naam td heeft (een afkorting voor testgestuurd). Maak om uw eerste test te maken een bestand met de naam test_view.py en kopieer en plak de volgende inhoud erin.
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)
U kunt deze test uitvoeren door
./manage.py test
en het zal natuurlijk falen! U zult een fout zien die lijkt op het volgende.
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
Waarom gebeurt dat? Omdat we daar geen zicht op hebben gedefinieerd! Dus laten we het doen. Maak een bestand met de naam views.py en plaats daarin de volgende code
from django.http import HttpResponse
def hello(request):
return HttpResponse('hello')
Wijs het vervolgens toe aan de / hallo / door urls py als volgt te bewerken:
from td import views
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^hello/', views.hello),
....
]
Voer de test nu opnieuw uit ./manage.py test
opnieuw en altviool !!
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Django-modellen effectief testen
Een klasse aannemen
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
Voorbeelden van testen
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):
...
Enkele punten
-
created_properly
tests worden gebruikt om de staatseigenschappen van django-modellen te verifiëren. Ze helpen sitautions te vangen waar we standaardwaarden, file_upload_paths etc. hebben gewijzigd. -
absolute_url
lijkt misschien triviaal, maar ik heb ontdekt dat het me heeft geholpen bij het voorkomen van enkele bugs bij het wijzigen van URL-paden - Ik schrijf op dezelfde manier testgevallen voor alle methoden die in een model zijn geïmplementeerd (met behulp van
mock
, enz.) - Door een gemeenschappelijke
BaseModelTestCase
definiëren, kunnen we de nodige relaties tussen modellen instellen om een goede test te garanderen.
Eindelijk, bij twijfel, schrijf een test. Triviale gedragsveranderingen worden opgevangen door aandacht te besteden aan details en lang vergeten stukjes code veroorzaken geen onnodige problemen.
Toegangscontrole testen in Django-weergaven
tl; dr : Maak een basisklasse die twee gebruikersobjecten definieert (zeg user
en een andere another_user
). Maak uw andere modellen en definieer drie Client
.
-
self.client
: Vertegenwoordiger vanuser
ingelogde browser van de gebruiker -
self.another_client
: Vertegenwoordigenanother_user
's-client -
self.unlogged_client
: Vertegenwoordiger van niet-geregistreerde persoon
Krijg nu toegang tot al uw openbare en privé-URL's van deze drie clientobjecten en dicteer de reactie die u verwacht. Hieronder heb ik de strategie uiteengezet voor een Book
object dat private
(eigendom van enkele bevoorrechte gebruikers) of public
(voor iedereen zichtbaar) kan zijn.
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)
De database en testen
Django gebruikt speciale database-instellingen tijdens het testen, zodat tests de database normaal kunnen gebruiken, maar standaard op een lege database kunnen worden uitgevoerd. Databasewijzigingen in de ene test worden niet door een andere gezien. Beide van de volgende tests zullen bijvoorbeeld slagen:
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)
Wedstrijden
Als u wilt dat databaseobjecten door meerdere tests worden gebruikt, maakt u deze aan in de setUp
methode van de testcase. Als u fixtures in uw django-project hebt gedefinieerd, kunnen deze bovendien als volgt worden opgenomen:
class MyTest(TestCase):
fixtures = ["fixture1.json", "fixture2.json"]
Standaard zoekt django naar armaturen in de map met fixtures
in elke app. Verdere mappen kunnen worden ingesteld met de instelling FIXTURE_DIRS
:
# myapp/settings.py
FIXTURE_DIRS = [
os.path.join(BASE_DIR, 'path', 'to', 'directory'),
]
Laten we aannemen dat u een model als volgt hebt gemaakt:
# 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)
Dan kunnen uw .json-armaturen er zo uitzien:
# fixture1.json
[
{ "model": "myapp.person",
"pk": 1,
"fields": {
"firstname": "Peter",
"lastname": "Griffin"
}
},
{ "model": "myapp.person",
"pk": 2,
"fields": {
"firstname": "Louis",
"lastname": "Griffin"
}
},
]
Gebruik de testdatabase opnieuw
Om uw testruns te versnellen, kunt u de management-opdracht vertellen de testdatabase opnieuw te gebruiken (en te voorkomen dat deze vóór elke testrun wordt gemaakt en verwijderd). Dit kan worden gedaan met behulp van de keepdb (of shorthand -k
) vlag als volgt:
# Reuse the test-database (since django version 1.8)
$ python manage.py test --keepdb
Beperk het aantal uitgevoerde tests
Het is mogelijk om de tests die worden uitgevoerd door de manage.py test
te beperken door op te geven welke modules door de manage.py test
moeten worden ontdekt:
# 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
Als u een aantal tests wilt uitvoeren, kunt u een patroon met bestandsnamen doorstaan. U wilt bijvoorbeeld alleen tests uitvoeren die betrekking hebben op uw modellen:
$ python manage.py test -p test_models*
Creating test database for alias 'default'...
.................................................
----------------------------------------------------------------------
Ran 115 tests in 3.869s
OK
Ten slotte is het mogelijk om het testpakket te stoppen bij de eerste fout, met behulp van --failfast
. Met dit argument kunt u snel de potentiële fout in de suite vinden:
$ 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)