Enhetstester¶
Django levereras med en egen testsvit i katalogen tests
i kodbasen. Det är vår policy att se till att alla tester godkänns hela tiden.
Vi uppskattar alla bidrag till testsviten!
Django-testerna använder alla den testinfrastruktur som levereras med Django för att testa applikationer. Se Skriva och köra tester för en förklaring av hur man skriver nya tester.
Kör enhetstesterna¶
Snabbstart¶
Först, fork Django på GitHub.
För det andra, skapa och aktivera en virtuell miljö. Om du inte är bekant med hur du gör det, läs vår Bidragshandledning.
Därefter klonar du din fork, installerar några krav och kör testerna:
$ git clone https://github.com/YourGitHubName/django.git django-repo
$ cd django-repo/tests
$ python -m pip install -e ..
$ python -m pip install -r requirements/py3.txt
$ ./runtests.py
...\> git clone https://github.com/YourGitHubName/django.git django-repo
...\> cd django-repo\tests
...\> py -m pip install -e ..
...\> py -m pip install -r requirements\py3.txt
...\> runtests.py
För att installera kraven krävs troligen några operativsystemspaket som inte finns installerade på din dator. Du kan vanligtvis ta reda på vilket paket som ska installeras genom att göra en webbsökning efter den sista raden eller så i felmeddelandet. Försök att lägga till ditt operativsystem i sökfrågan om det behövs.
Om du har problem med att installera kraven kan du hoppa över det steget. Se Kör alla tester för information om hur du installerar de valfria testberoendena. Om du inte har installerat ett valfritt beroende kommer de tester som kräver det att hoppas över.
För att köra testerna krävs en inställningsmodul för Django som definierar vilka databaser som ska användas. För att hjälpa dig att komma igång tillhandahåller och använder Django en exempelinställningsmodul som använder SQLite-databasen. Se Använda en annan settings-modul för att lära dig hur du använder en annan inställningsmodul för att köra testerna med en annan databas.
Har du problem? Se Felsökning för några vanliga problem.
Kör tester med hjälp av tox
¶
`Tox <https://tox.wiki/>`_ är ett verktyg för att köra tester i olika virtuella miljöer. Django innehåller en grundläggande tox.ini
som automatiserar vissa kontroller som vår byggserver utför på pull-förfrågningar. För att köra enhetstesterna och andra kontroller (som import sorting, documentation spelling checker, och code formatting), installera och kör kommandot tox
från vilken plats som helst i Djangos källträd:
$ python -m pip install tox
$ tox
...\> py -m pip install tox
...\> tox
Som standard kör tox
testsviten med den medföljande filen med testinställningar för SQLite, black
, blacken-docs
, flake8
, isort
och stavningskontrollen för dokumentationen. Förutom de systemberoenden som anges på andra ställen i den här dokumentationen måste kommandot python3
finnas på din sökväg och länkas till rätt version av Python. En lista över standardmiljöer kan ses enligt följande:
$ tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=5.1.0
...\> tox -l
py3
black
blacken-docs
flake8>=3.7.0
docs
isort>=5.1.0
Testning av andra Python-versioner och databasbackends¶
Förutom standardmiljöerna stöder tox
körning av enhetstester för andra versioner av Python och andra databasbackends. Eftersom Djangos testsvit inte innehåller en inställningsfil för andra databasbackends än SQLite, måste du dock skapa och tillhandahålla dina egna testinställningar. Till exempel, för att köra testerna på Python 3.10 med hjälp av PostgreSQL:
$ tox -e py310-postgres -- --settings=my_postgres_settings
...\> tox -e py310-postgres -- --settings=my_postgres_settings
Detta kommando ställer in en virtuell Python 3.10-miljö, installerar Djangos testsvitberoenden (inklusive de för PostgreSQL) och anropar runtests.py
med de medföljande argumenten (i det här fallet --settings=my_postgres_settings
).
I resten av denna dokumentation visas kommandon för att köra tester utan tox
, men alla alternativ som skickas till runtests.py
kan också skickas till tox
genom att prefixera argumentlistan med --
, enligt ovan.
Tox
respekterar även miljövariabeln DJANGO_SETTINGS_MODULE
, om den är inställd. Följande är till exempel likvärdigt med kommandot ovan:
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py310-postgres
Windows-användare bör använda:
...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py310-postgres
Körning av JavaScript-testerna¶
Django innehåller en uppsättning JavaScript-enhetstester för funktioner i vissa Contrib-appar. JavaScript-testerna körs inte som standard med tox
eftersom de kräver att Node.js
installeras och inte är nödvändiga för de flesta korrigeringar. För att köra JavaScript-testerna med hjälp av tox
:
$ tox -e javascript
...\> tox -e javascript
Detta kommando kör npm install
för att säkerställa att testkraven är uppdaterade och kör sedan npm test
.
Kör tester med hjälp av django-docker-box
¶
med django-docker-box kan du köra Djangos testsvit över alla databaser och pythonversioner som stöds. Se projektsidan django-docker-box för installations- och användningsinstruktioner.
Använda en annan settings
-modul¶
Den medföljande inställningsmodulen (tests/test_sqlite.py
) gör att du kan köra testsviten med SQLite. Om du vill köra testerna med en annan databas måste du definiera din egen inställningsfil. Vissa tester, t.ex. de för contrib.postgres
, är specifika för en viss databasbackend och kommer att hoppas över om de körs med en annan backend. Vissa tester hoppas över eller förväntade misslyckanden på en viss databas backend (se DatabaseFeatures.django_test_skips
och DatabaseFeatures.django_test_expected_failures
på varje backend).
Om du vill köra testerna med olika inställningar måste du se till att modulen finns på din PYTHONPATH
och skicka modulen med --settings
.
Inställningen DATABASES
i en modul för testinställningar måste definiera två databaser:
En
standard
databas. Denna databas bör använda den backend som du vill använda för primär testning.En databas med aliaset
other
. Databasenother
används för att testa att frågor kan riktas till olika databaser. Den här databasen bör använda samma backend somdefault
och den måste ha ett annat namn.
Om du använder en backend som inte är SQLite måste du ange andra uppgifter för varje databas:
Alternativet
USER
måste ange ett befintligt användarkonto för databasen. Den användaren behöver behörighet att köraCREATE DATABASE
så att testdatabasen kan skapas.Alternativet
PASSWORD
måste innehålla lösenordet för denUSER
som har angetts.
Testdatabaser får sina namn genom att lägga till test_
till värdet i inställningarna NAME
för de databaser som definieras i DATABASES
. Dessa testdatabaser raderas när testerna är avslutade.
Du måste också se till att din databas använder UTF-8 som standardteckensats. Om din databasserver inte använder UTF-8 som standardteckensats måste du lägga till ett värde för CHARSET
i testinställningsordlistan för den aktuella databasen.
Kör bara några av testerna¶
Djangos hela testsvit tar en stund att köra, och att köra varje enskilt test kan vara överflödigt om du till exempel just har lagt till ett test i Django som du vill köra snabbt utan att köra allt annat. Du kan köra en delmängd av enhetstesterna genom att lägga till namnen på testmodulerna till runtests.py
på kommandoraden.
Om du t.ex. bara vill köra tester för generiska relationer och internationalisering, skriv:
$ ./runtests.py --settings=path.to.settings generic_relations i18n
...\> runtests.py --settings=path.to.settings generic_relations i18n
Hur får du reda på namnen på enskilda tester? Titta i tests/
- varje katalognamn där är namnet på ett test.
Om du bara vill köra en viss klass av tester kan du ange en lista med sökvägar till enskilda testklasser. Om du t.ex. vill köra TranslationTests
för modulen i18n
skriver du :
$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests
Utöver detta kan du ange en enskild testmetod på följande sätt:
$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
...\> runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
Du kan köra tester som startar vid en angiven modul på högsta nivån med alternativet --start-at
. Till exempel
$ ./runtests.py --start-at=wsgi
...\> runtests.py --start-at=wsgi
Du kan också köra tester som startar efter en angiven modul på högsta nivån med alternativet --start-after
. Till exempel:
$ ./runtests.py --start-after=wsgi
...\> runtests.py --start-after=wsgi
Observera att alternativet --reverse
inte har någon inverkan på alternativen --start-at
eller --start-after
. Dessutom kan dessa alternativ inte användas med testetiketter.
Kör Selenium-testerna¶
Vissa tester kräver Selenium och en webbläsare. För att köra dessa tester måste du installera paketet selenium och köra testerna med alternativet --selenium=<BROWSERS>
. Till exempel om du har Firefox och Google Chrome installerade:
$ ./runtests.py --selenium=firefox,chrome
...\> runtests.py --selenium=firefox,chrome
Se paketet selenium.webdriver för en lista över tillgängliga webbläsare.
Om du anger --selenium
ställs --tags=selenium
automatiskt in så att endast de tester som kräver selenium körs.
Vissa webbläsare (t.ex. Chrome eller Firefox) stöder headless-testning, som kan vara snabbare och stabilare. Lägg till alternativet --headless
för att aktivera detta läge.
Skärmdump tester¶
För att testa ändringar i administratörsgränssnittet kan seleniumtesterna köras med alternativet --screenshots
aktiverat. Skärmdumpar kommer att sparas i katalogen tests/screenshots/
.
För att definiera när skärmdumpar ska tas under ett seleniumtest måste testklassen använda dekoratorn @django.test.selenium.screenshot_cases
med en lista över skärmdumpstyper som stöds ("desktop_size"
, "mobile_size"
, "small_screen_size"
, "rtl"
, "dark"
och "high_contrast"
). Den kan sedan anropa self.take_screenshot("unique-screenshot-name")
vid önskad punkt för att generera skärmdumparna. Till exempel:
from django.test.selenium import SeleniumTestCase, screenshot_cases
from django.urls import reverse
class SeleniumTests(SeleniumTestCase):
@screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
def test_login_button_centered(self):
self.selenium.get(self.live_server_url + reverse("admin:login"))
self.take_screenshot("login")
...
Detta genererar flera skärmdumpar av inloggningssidan - en för en datorskärm, en för en mobilskärm, en för höger till vänster-språk på datorn, en för mörkt läge på datorn och en för högkontrastläge på datorn när du använder Chrome.
Alternativet --screenshots
och dekoratorn @screenshot_cases
har lagts till.
Kör alla tester¶
Om du vill köra hela testsviten måste du installera ett antal beroenden:
argon2-cffi 19.2.0+
asgiref 3.8.1+ (krävs)
colorama 0.4.6+
docutils 0.19+
Jinja2 2.11+
Pillow 6.2.1+
redis 3.4+
pymemcache, plus en stödd Python-bindning
selenium 4.8.0+
sqlparse 0.3.1+ (krävs)
tblib 1.5.0+
Du kan hitta dessa beroenden i pip requirements files i katalogen tests/requirements
i Djangos källträd och installera dem på följande sätt:
$ python -m pip install -r tests/requirements/py3.txt
...\> py -m pip install -r tests\requirements\py3.txt
Om du stöter på ett fel under installationen kan det hända att ditt system saknar ett beroende för ett eller flera av Python-paketen. Läs dokumentationen för det paket som inte fungerar eller sök på webben efter det felmeddelande som du stöter på.
Du kan också installera valfri(a) databasadapter(s) med hjälp av oracle.txt
, mysql.txt
eller postgres.txt
.
Om du vill testa backends för memcached eller Redis cache måste du också definiera en CACHES
-inställning som pekar på din memcached- respektive Redis-instans.
För att köra GeoDjango-testerna måste du konfigurera en spatial databas och installera Geospatial-biblioteken.
Var och en av dessa beroenden är valfria. Om du saknar något av dem kommer de tillhörande testerna att hoppas över.
För att köra några av de automatiska laddningstesterna måste du installera tjänsten Watchman.
Kodtäckning¶
Bidragsgivare uppmuntras att köra täckning på testsviten för att identifiera områden som behöver ytterligare tester. Installation och användning av täckningsverktyget beskrivs i testning code coverage.
För att köra täckning på Djangos testsvit med standardtestinställningarna:
$ coverage run ./runtests.py --settings=test_sqlite
...\> coverage run runtests.py --settings=test_sqlite
Efter att ha kört täckning, kombinera all täckningsstatistik genom att köra:
$ coverage combine
...\> coverage combine
Efter det genererar du html-rapporten genom att köra:
$ coverage html
...\> coverage html
När du kör täckning för Django-testerna definierar den medföljande inställningsfilen .coveragerc
coverage_html
som utdatakatalog för rapporten och utesluter även flera kataloger som inte är relevanta för resultaten (testkod eller extern kod som ingår i Django).
Bidragande appar¶
Tester för Contrib-appar finns i katalogen tests/, vanligtvis under <app_name>_tests
. Till exempel finns tester för contrib.auth
i tests/auth_tests.
Felsökning¶
Testsviten hänger sig eller visar fel på main
-grenen¶
Se till att du har den senaste punktversionen av en stödd Python-version, eftersom det ofta finns buggar i tidigare versioner som kan leda till att testsviten misslyckas eller hänger sig.
På macOS (High Sierra och nyare versioner) kan du se detta meddelande loggat, varefter testerna hänger sig:
objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in
another thread when fork() was called.
För att undvika detta kan du t.ex. ange miljövariabeln OBJC_DISABLE_INITIALIZE_FORK_SAFETY
:
$ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py
Eller lägg till export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
i startfilen för ditt skal (t.ex. ~/.profile
).
Många misslyckade tester med UnicodeEncodeError
¶
Om paketet locales
inte är installerat kommer vissa tester att misslyckas med UnicodeEncodeError
.
Du kan lösa detta på Debian-baserade system genom att t.ex. köra:
$ apt-get install locales
$ dpkg-reconfigure locales
Du kan lösa detta för macOS-system genom att konfigurera ditt shells locale:
$ export LANG="en_US.UTF-8"
$ export LC_ALL="en_US.UTF-8"
Kör kommandot locale
för att bekräfta ändringen. Lägg eventuellt till dessa exportkommandon i startfilen för ditt skal (t.ex. ~/.bashrc
för Bash) så att du slipper skriva in dem igen.
Tester som bara misslyckas i kombination¶
Om ett test godkänns när det körs isolerat men misslyckas i hela sviten har vi några verktyg som hjälper till att analysera problemet.
Alternativet --bisect
i runtests.py
kör det misslyckade testet samtidigt som testuppsättningen som det körs tillsammans med halveras vid varje iteration, vilket ofta gör det möjligt att identifiera ett litet antal tester som kan vara relaterade till felet.
Anta till exempel att det misslyckade testet som fungerar på egen hand är ModelTest.test_eq
, då använder du:
$ ./runtests.py --bisect basic.tests.ModelTest.test_eq
...\> runtests.py --bisect basic.tests.ModelTest.test_eq
kommer att försöka hitta ett test som stör det givna testet. Först körs testet med den första halvan av testsviten. Om ett fel uppstår delas den första halvan av testsviten upp i två grupper och varje grupp körs sedan med det angivna testet. Om det inte uppstår något fel med den första halvan av testsviten körs den andra halvan av testsviten med det angivna testet och delas upp på lämpligt sätt enligt beskrivningen ovan. Processen upprepas tills antalet misslyckade tester har minimerats.
Alternativet --pair
kör det givna testet tillsammans med alla andra tester från sviten, så att du kan kontrollera om ett annat test har bieffekter som orsakar felet. Så här gör du:
$ ./runtests.py --pair basic.tests.ModelTest.test_eq
...\> runtests.py --pair basic.tests.ModelTest.test_eq
kommer att para ihop test_eq
med varje testetikett.
Med både --bisect
och --pair
, om du redan misstänker vilka fall som kan vara ansvariga för felet, kan du begränsa tester som ska korsanalyseras genom att :ref:``specificera ytterligare testetiketter <runtests-specifying-labels>` efter den första:
$ ./runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
...\> runtests.py --pair basic.tests.ModelTest.test_eq queries transactions
Du kan också prova att köra en uppsättning tester i slumpmässig eller omvänd ordning med hjälp av alternativen --shuffle
och --reverse
. Detta kan hjälpa till att verifiera att körning av tester i en annan ordning inte orsakar några problem:
$ ./runtests.py basic --shuffle
$ ./runtests.py basic --reverse
...\> runtests.py basic --shuffle
...\> runtests.py basic --reverse
Se de SQL-frågor som körs under ett test¶
Om du vill undersöka den SQL som körs i misslyckade tester kan du aktivera SQL logging med alternativet --debug-sql
. Om du kombinerar detta med --verbosity=2
kommer alla SQL-frågor att skrivas ut:
$ ./runtests.py basic --debug-sql
...\> runtests.py basic --debug-sql
Se den fullständiga spårningen av ett testfel¶
Som standard körs testerna parallellt med en process per kärna. När testerna körs parallellt kommer du dock bara att se en avkortad spårning för eventuella testfel. Du kan justera detta beteende med alternativet --parallel
:
$ ./runtests.py basic --parallel=1
...\> runtests.py basic --parallel=1
Du kan också använda miljövariabeln DJANGO_TEST_PROCESSES
för detta ändamål.
Tips för att skriva tester¶
Isolering av modellregistrering¶
För att undvika att förorena det globala apps
-registret och förhindra onödigt skapande av tabeller, bör modeller som definieras i en testmetod vara bundna till en tillfällig Apps
-instans. För att göra detta, använd isolate_apps()
dekoratorn:
from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase):
@isolate_apps("app_label")
def test_model_definition(self):
class TestModel(models.Model):
pass
...
Inställning av app_label
Modeller som definieras i en testmetod utan någon explicit app_label
tilldelas automatiskt etiketten för den app där deras testklass finns.
För att säkerställa att de modeller som definieras inom ramen för isolate_apps()
-instanser är korrekt installerade, bör du skicka uppsättningen av riktade app_label
som argument:
tester/app_label/tester.py
¶from django.db import models
from django.test import SimpleTestCase
from django.test.utils import isolate_apps
class TestModelDefinition(SimpleTestCase):
@isolate_apps("app_label", "other_app_label")
def test_model_definition(self):
# This model automatically receives app_label='app_label'
class TestModel(models.Model):
pass
class OtherAppModel(models.Model):
class Meta:
app_label = "other_app_label"
...