Skriva och köra tester¶
Se även
testhandledning, referens för testverktyg och avancerade testämnen.
Detta dokument är uppdelat i två huvudavsnitt. Först förklarar vi hur man skriver tester med Django. Sedan förklarar vi hur man kör dem.
Skriva tester¶
Djangos enhetstester använder en modul från Pythons standardbibliotek: unittest
. Denna modul definierar tester med hjälp av ett klassbaserat tillvägagångssätt.
Här är ett exempel som underklassar från django.test.TestCase
, som är en underklass av unittest.TestCase
som kör varje test inuti en transaktion för att ge isolering:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
def setUp(self):
Animal.objects.create(name="lion", sound="roar")
Animal.objects.create(name="cat", sound="meow")
def test_animals_can_speak(self):
"""Animals that can speak are correctly identified"""
lion = Animal.objects.get(name="lion")
cat = Animal.objects.get(name="cat")
self.assertEqual(lion.speak(), 'The lion says "roar"')
self.assertEqual(cat.speak(), 'The cat says "meow"')
När du kör dina tester, är standardbeteendet för testverktyget att hitta alla testfallsklasser (det vill säga underklasser till unittest.TestCase
) i alla filer vars namn börjar med test
, automatiskt bygga en testsvit av dessa testfallsklasser och köra sviten.
För mer information om unittest
, se Python-dokumentationen.
Var ska testerna genomföras?
Standardmallen startapp
skapar en fil med namnet tests.py
i den nya applikationen. Detta kan vara bra om du bara har ett fåtal tester, men när din testsvit växer kommer du sannolikt att vilja omstrukturera den till ett testpaket så att du kan dela upp dina tester i olika undermoduler som test_models.py
, test_views.py
, test_forms.py
, etc. Känn dig fri att välja vilket organisationsschema du vill.
Se även testning av återanvändbara applikationer.
Varning
Om dina tester förlitar sig på databasåtkomst, till exempel för att skapa eller fråga modeller, se till att skapa dina testklasser som underklasser till django.test.TestCase
snarare än unittest.TestCase
.
Genom att använda unittest.TestCase
slipper du kostnaden för att köra varje test i en transaktion och rensa databasen, men om dina tester interagerar med databasen kommer deras beteende att variera beroende på i vilken ordning testlöparen kör dem. Detta kan leda till enhetstester som godkänns när de körs isolerat men som misslyckas när de körs i en svit.
Genomföra tester¶
När du har skrivit tester kan du köra dem med kommandot test
i verktyget manage.py
i ditt projekt:
$ ./manage.py test
Testupptäckt baseras på unittest-modulens inbyggd testupptäckt. Som standard kommer detta att upptäcka tester i alla filer med namnet test*.py
under den aktuella arbetskatalogen.
Du kan ange särskilda tester som ska köras genom att ange ett valfritt antal ”testetiketter” till ./manage.py test
. Varje testetikett kan vara en fullständig Python-punktad sökväg till ett paket, en modul, en TestCase
-underklass eller en testmetod. Till exempel:
# Run all the tests in the animals.tests module
$ ./manage.py test animals.tests
# Run all the tests found within the 'animals' package
$ ./manage.py test animals
# Run just one test case class
$ ./manage.py test animals.tests.AnimalTestCase
# Run just one test method
$ ./manage.py test animals.tests.AnimalTestCase.test_animals_can_speak
Du kan också ange en sökväg till en katalog för att hitta tester under den katalogen:
$ ./manage.py test animals/
Du kan ange ett anpassat filnamnsmönster med hjälp av alternativet -p
(eller --pattern
) om dina testfiler har andra namn än test*.py
:
$ ./manage.py test --pattern="tests_*.py"
Om du trycker på Ctrl-C
medan testerna körs, väntar testköraren på att det aktuella testet ska slutföras och avslutas sedan på ett elegant sätt. Under en elegant avslutning kommer testköraren att ge ut information om eventuella testfel, rapportera hur många tester som kördes och hur många fel och misslyckanden som uppstod, samt förstöra alla testdatabaser som vanligt. Att trycka på Ctrl-C
kan alltså vara mycket användbart om du glömmer att ange alternativet --failfast
, märker att vissa tester oväntat misslyckas och vill få information om misslyckandena utan att vänta på att hela testkörningen ska slutföras.
Om du inte vill vänta på att det aktuella testet ska avslutas kan du trycka på Ctrl-C
en gång till så avbryts testkörningen omedelbart, men inte på ett elegant sätt. Inga detaljer om de tester som kördes före avbrottet kommer att rapporteras, och inga testdatabaser som skapats av körningen kommer att förstöras.
Test med varningar aktiverade
Det är en bra idé att köra dina tester med Python-varningar aktiverade: python -Wa manage.py test
. Flaggan -Wa
säger till Python att visa varningar för utfasning. Django, liksom många andra Python-bibliotek, använder dessa varningar för att flagga när funktioner försvinner. Det kan också flagga områden i din kod som inte är helt fel men som kan dra nytta av en bättre implementering.
Testdatabasen¶
Tester som kräver en databas (dvs. modelltester) använder inte din ”riktiga” (produktions-) databas. Separata, tomma databaser skapas för testerna.
Oavsett om testerna godkänns eller inte, förstörs testdatabaserna när alla tester har utförts.
Du kan förhindra att testdatabaserna förstörs genom att använda alternativet test --keepdb
. Detta kommer att bevara testdatabasen mellan körningarna. Om databasen inte finns kommer den först att skapas. Eventuella migreringar kommer också att tillämpas för att hålla den uppdaterad.
Som beskrivs i föregående avsnitt får testdatabasen inte förstöras om en testkörning avbryts med våld. Vid nästa körning kommer du att få frågan om du vill återanvända eller förstöra databasen. Använd alternativet test --noinput
för att undertrycka den frågan och automatiskt förstöra databasen. Detta kan vara användbart när du kör tester på en server för kontinuerlig integration där testerna kan avbrytas av en timeout, till exempel.
Standardnamnen på testdatabaserna skapas genom att lägga till test_
till värdet för varje NAME
i DATABASES
. När SQLite används kommer testerna att använda en databas i minnet som standard (dvs. databasen skapas i minnet och kringgår filsystemet helt!) Ordlistan TEST
i DATABASER
erbjuder ett antal inställningar för att konfigurera din testdatabas. Om du till exempel vill använda ett annat databasnamn anger du NAME
i ordlistan TEST
för en viss databas i DATABASES
.
På PostgreSQL behöver USER
också läsbehörighet till den inbyggda databasen postgres
.
Förutom att använda en separat databas kommer testköraren att använda samma inställningar för databasen som du har i din inställningsfil: ENGINE
, USER
, HOST
, etc. Testdatabasen skapas av den användare som anges av USER
, så du måste se till att det angivna användarkontot har tillräckliga privilegier för att skapa en ny databas i systemet.
För finkornig kontroll över teckenkodningen i din testdatabas använder du alternativet CHARSET
TEST. Om du använder MySQL kan du också använda alternativet COLLATION
för att styra den specifika kollationering som används av testdatabasen. Se settings-dokumentationen för mer information om dessa och andra avancerade inställningar.
Om du använder en SQLite-databas i minnet med SQLite är shared cache aktiverat, så att du kan skriva tester med möjlighet att dela databasen mellan trådar.
Hittar du data från din produktionsdatabas när du kör tester?
Om din kod försöker komma åt databasen när modulerna kompileras kommer detta att ske innan testdatabasen har skapats, vilket kan ge oväntade resultat. Om du till exempel har en databasfråga i kod på modulnivå och det finns en riktig databas, kan produktionsdata förorena dina tester. Det är ändå en dålig idé att ha sådana databasfrågor för importtid i din kod - skriv om din kod så att den inte gör detta.
Detta gäller även anpassade implementeringar av ready()
.
Se även
The :ref:``avancerade ämnen för testning av flera databaser <topics-testing-advanced-multidb>`.
Ordning i vilken testerna utförs¶
För att garantera att all TestCase
-kod startar med en ren databas, ordnar Django test runner om testerna på följande sätt:
Alla
TestCase
underklasser körs först.Därefter körs alla andra Django-baserade tester (testfallsklasser baserade på
SimpleTestCase
, inklusiveTransactionTestCase
) utan att någon särskild ordning garanteras eller upprätthålls mellan dem.Sedan körs alla andra
unittest.TestCase
-tester (inklusive doctests) som kan ändra databasen utan att återställa den till dess ursprungliga tillstånd.
Observera
Den nya ordningsföljden för tester kan avslöja oväntade beroenden av testfallets ordningsföljd. Detta är fallet med doctests som förlitade sig på tillstånd som lämnats i databasen av ett visst TransactionTestCase
-test, de måste uppdateras för att kunna köras oberoende.
Observera
Fel som upptäcks vid laddning av tester ordnas före alla ovanstående för snabbare återkoppling. Detta inkluderar saker som testmoduler som inte kunde hittas eller som inte kunde laddas på grund av syntaxfel.
Du kan randomisera och/eller vända exekveringsordningen inom grupper med hjälp av alternativen test --shuffle
och --reverse
. Detta kan hjälpa till att säkerställa att dina tester är oberoende av varandra.
Rollback-emulering¶
Alla initiala data som laddas i migreringar kommer endast att vara tillgängliga i TestCase
-tester och inte i TransactionTestCase
-tester, och dessutom endast på backends där transaktioner stöds (det viktigaste undantaget är MyISAM). Detta gäller även för tester som förlitar sig på TransactionTestCase
som LiveServerTestCase
och StaticLiveServerTestCase
.
Django kan ladda om dessa data åt dig per testfall genom att ställa in alternativet serialized_rollback
till True
i kroppen av TestCase
eller TransactionTestCase
, men observera att detta kommer att sakta ner testsviten med ungefär 3x.
Tredjepartsappar eller de som utvecklas mot MyISAM måste ställa in detta; i allmänhet bör du dock utveckla dina egna projekt mot en transaktionsdatabas och använda TestCase
för de flesta tester, och därmed inte behöva denna inställning.
Den första serialiseringen går vanligtvis mycket snabbt, men om du vill utesluta vissa appar från denna process (och snabba upp testkörningarna något) kan du lägga till dessa appar i TEST_NON_SERIALIZED_APPS
.
För att förhindra att serialiserade data laddas två gånger, inaktiverar inställningen serialized_rollback=True
post_migrate
-signalen när testdatabasen rensas.
För TransactionTestCase
görs serialiserade migreringsdata tillgängliga under setUpClass()
.
Andra testförhållanden¶
Oavsett värdet på inställningen DEBUG
i din konfigurationsfil körs alla Django-tester med :setting:`DEBUG`=False. Detta är för att säkerställa att den observerade utmatningen av din kod matchar vad som kommer att ses i en produktionsinställning.
Cacher rensas inte efter varje test, och om du kör manage.py test fooapp
kan data från testerna infogas i cacheminnet i ett live-system om du kör dina tester i produktion eftersom, till skillnad från databaser, en separat ”testcache” inte används. Detta beteende kan komma att ändras i framtiden.
Förstå testutdata¶
När du kör dina tester kommer du att se ett antal meddelanden när testköraren förbereder sig. Du kan styra detaljnivån i dessa meddelanden med alternativet verbosity
på kommandoraden:
Creating test database...
Creating table myapp_animal
Creating table myapp_mineral
Detta visar att testköraren skapar en testdatabas, enligt beskrivningen i föregående avsnitt.
När testdatabasen har skapats kommer Django att köra dina tester. Om allt går bra kommer du att se något liknande detta:
----------------------------------------------------------------------
Ran 22 tests in 0.221s
OK
Om testerna misslyckas kommer du dock att se fullständig information om vilka tester som misslyckades:
======================================================================
FAIL: test_was_published_recently_with_future_poll (polls.tests.PollMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/dev/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_poll
self.assertIs(future_poll.was_published_recently(), False)
AssertionError: True is not False
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (failures=1)
En fullständig förklaring av denna felutmatning ligger utanför ramen för detta dokument, men den är ganska intuitiv. Du kan läsa dokumentationen för Pythons unittest
-bibliotek för mer information.
Observera att returkoden för testrunner-skriptet är 1 för varje antal misslyckade tester (oavsett om misslyckandet orsakades av ett fel, ett misslyckat påstående eller en oväntad framgång). Om alla tester godkänns är returkoden 0. Den här funktionen är användbar om du använder testrunner-skriptet i ett shell-skript och behöver testa om det lyckas eller misslyckas på den nivån.
Snabbare tester¶
Kör tester parallellt¶
Så länge dina tester är ordentligt isolerade kan du köra dem parallellt för att få högre hastighet på flerkärnig maskinvara. Se test --parallel
.
Hashing av lösenord¶
Standardalgoritmen för lösenordshashning är ganska långsam av designskäl. Om du autentiserar många användare i dina tester kanske du vill använda en anpassad inställningsfil och ställa in PASSWORD_HASHERS
till en snabbare hash-algoritm:
PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher",
]
Glöm inte att i PASSWORD_HASHERS
även ange den hash-algoritm som används i fixturerna, om någon.
Bevara testdatabasen¶
Alternativet test --keepdb
bevarar testdatabasen mellan testkörningar. Det hoppar över åtgärderna för att skapa och förstöra, vilket kan minska tiden för att köra tester avsevärt.
Undvika diskåtkomst för mediefiler¶
Klassen:~django.core.files.storage.InMemoryStorage är ett bekvämt sätt att förhindra diskåtkomst för mediefiler. All data sparas i minnet och kasseras sedan efter testkörningen.