Testverktyg¶
Django tillhandahåller en liten uppsättning verktyg som är användbara när du skriver tester.
Testklienten¶
Testklienten är en Python-klass som fungerar som en dummy-webbläsare, så att du kan testa dina vyer och interagera med din Django-drivna applikation programmatiskt.
Några av de saker du kan göra med testklienten är följande:
Simulera GET- och POST-begäranden på en URL och observera svaret - allt från HTTP på låg nivå (resultathuvuden och statuskoder) till sidinnehåll.
Se kedjan av omdirigeringar (om det finns några) och kontrollera webbadressen och statuskoden i varje steg.
Testa att en given begäran återges av en given Django-mall, med en mallkontext som innehåller vissa värden.
Observera att testklienten inte är avsedd att ersätta Selenium eller andra ramverk ”i webbläsaren”. Djangos testklient har ett annat fokus. Kort och gott:
Använd Djangos testklient för att fastställa att rätt mall återges och att mallen får rätt kontextdata.
Använd
RequestFactory
för att testa vyfunktioner direkt, förbi routing- och middleware-lagren.Använd ramverk i webbläsaren som Selenium för att testa renderad HTML och beteendet på webbsidor, nämligen JavaScript-funktionalitet. Django ger också särskilt stöd för dessa ramverk; se avsnittet om
LiveServerTestCase
för mer information.
En omfattande testsvit bör använda en kombination av alla dessa testtyper.
Översikt och ett snabbt exempel¶
För att använda testklienten, instansiera django.test.Client
och hämta webbsidor:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post("/login/", {"username": "john", "password": "smith"})
>>> response.status_code
200
>>> response = c.get("/customer/details/")
>>> response.content
b'<!DOCTYPE html...'
Som exemplet visar kan du instansiera Client
från en session i den interaktiva Python-tolken.
Notera några viktiga saker om hur testklienten fungerar:
Testklienten kräver inte att webbservern är igång. Faktum är att det går alldeles utmärkt utan att någon webbserver körs alls! Det beror på att den undviker HTTP:s overhead och hanterar Django-ramverket direkt. Detta hjälper till att få enhetstesterna att köras snabbt.
När du hämtar sidor ska du komma ihåg att ange sökvägen i URL:en, inte hela domänen. Detta är till exempel korrekt:
>>> c.get("/login/")
Detta är felaktigt:
>>> c.get("https://www.example.com/login/")
Testklienten kan inte hämta webbsidor som inte drivs av ditt Django-projekt. Om du behöver hämta andra webbsidor kan du använda en modul från Pythons standardbibliotek, t.ex.
urllib
.För att lösa URL:er använder testklienten den URLconf som pekas ut av din inställning
ROOT_URLCONF
.Även om ovanstående exempel skulle fungera i Pythons interaktiva tolk, är en del av testklientens funktionalitet, särskilt den mallrelaterade funktionaliteten, endast tillgänglig när tester körs.
Anledningen till detta är att Djangos testlöpare utför lite svart magi för att avgöra vilken mall som laddades av en viss vy. Denna svarta magi (i huvudsak en patchning av Djangos mallsystem i minnet) sker endast under testkörning.
Som standard kommer testklienten att inaktivera alla CSRF-kontroller som utförs av din webbplats.
Om du av någon anledning vill att testklienten ska utföra CSRF-kontroller kan du skapa en instans av testklienten som tvingar fram CSRF-kontroller. För att göra detta, skicka in argumentet
enforce_csrf_checks
när du konstruerar din klient:>>> from django.test import Client >>> csrf_client = Client(enforce_csrf_checks=True)
Att göra förfrågningar¶
Använd klassen django.test.Client
för att göra förfrågningar.
- class Client(enforce_csrf_checks=False, raise_request_exception=True, json_encoder=DjangoJSONEncoder, *, headers=None, query_params=None, **defaults)[source]¶
En testande HTTP-klient. Tar emot flera argument som kan anpassa beteendet.
med
headers
kan du ange standardrubriker som ska skickas med varje begäran. Till exempel, för att ställa in enUser-Agent
header:client = Client(headers={"user-agent": "curl/7.79.1"})
med
query_params
kan du ange standardfrågesträngen som kommer att ställas in vid varje förfrågan.Godtyckliga nyckelordsargument i
**defaults
ställer in WSGI environ-variabler. Till exempel, för att ställa in skriptnamnet:client = Client(SCRIPT_NAME="/app/")
Observera
Nyckelordsargument som börjar med prefixet
HTTP_
anges som rubriker, men parameternheaders
bör föredras för läsbarhetens skull.Värdena från nyckelordsargumenten
headers
,query_params
ochextra
som skickas tillget()
,post()
etc. har företräde framför de standardvärden som skickas till klasskonstruktören.Argumentet
enforce_csrf_checks
kan användas för att testa CSRF-skydd (se ovan).Argumentet
raise_request_exception
gör det möjligt att kontrollera om undantag som uppstår under begäran också ska uppstå i testet. Standardvärdet ärTrue
.Argumentet
json_encoder
gör det möjligt att ange en anpassad JSON-kodare för JSON-serialiseringen som beskrivs ipost()
.Changed in Django 5.1:Argumentet
query_params
har lagts till.När du har en
Client
-instans kan du anropa någon av följande metoder:- get(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en GET-begäran på den angivna
path
och returnerar ettResponse
-objekt, som dokumenteras nedan.Nyckel-värdeparen i ordlistan
query_params
används för att ställa in frågesträngar. Ett exempel:>>> c = Client() >>> c.get("/customers/details/", query_params={"name": "fred", "age": 7})
…kommer att resultera i en utvärdering av en GET-begäran som motsvarar:
/customers/details/?name=fred&age=7
Det är också möjligt att skicka dessa parametrar till parametern
data
.query_params
är dock att föredra eftersom den fungerar för alla HTTP-metoder.Parametern
headers
kan användas för att ange vilka rubriker som ska skickas i begäran. Till exempel:>>> c = Client() >>> c.get( ... "/customers/details/", ... query_params={"name": "fred", "age": 7}, ... headers={"accept": "application/json"}, ... )
…skickar HTTP-huvudet
HTTP_ACCEPT
till detaljvyn, vilket är ett bra sätt att testa kodvägar som använderdjango.http.HttpRequest.accepts()
-metoden.Godtyckliga nyckelordsargument ställer in WSGI environ-variabler. Till exempel headers för att ange skriptnamnet:
>>> c = Client() >>> c.get("/", SCRIPT_NAME="/app/")
Om du redan har GET-argumenten i URL-kodad form kan du använda den kodningen i stället för att använda data-argumentet. Till exempel kan den föregående GET-begäran också ställas som:
>>> c = Client() >>> c.get("/customers/details/?name=fred&age=7")
Om du anger en URL med både kodade GET-data och antingen ett query_params- eller data-argument kommer dessa argument att ha företräde.
Om du anger
follow
tillTrue
kommer klienten att följa alla omdirigeringar och ettredirect_chain
-attribut kommer att anges i svarsobjektet som innehåller tuples av de mellanliggande webbadresserna och statuskoderna.Om du hade en URL
/redirect_me/
som omdirigerades till/next/
, som omdirigerades till/final/
, är det här vad du skulle se:>>> response = c.get("/redirect_me/", follow=True) >>> response.redirect_chain [('http://testserver/next/', 302), ('http://testserver/final/', 302)]
Om du anger
secure
tillTrue
kommer klienten att emulera en HTTPS-begäran.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- post(path, data=None, content_type=MULTIPART_CONTENT, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en POST-begäran på den angivna
path
och returnerar ettResponse
-objekt, som dokumenteras nedan.Nyckel-värdeparen i ordlistan
data
används för att skicka POST-data. Till exempel:>>> c = Client() >>> c.post("/login/", {"name": "fred", "passwd": "secret"})
…kommer att resultera i en utvärdering av en POST-begäran till denna URL:
/login/
…med denna POST-data:
name=fred&passwd=secret
Om du anger
content_type
som application/json, serialiserasdata
medjson.dumps()
om det är en dict, lista eller tuple. Serialisering utförs medDjangoJSONEncoder
som standard, och kan åsidosättas genom att tillhandahålla ettjson_encoder
argument tillClient
. Denna serialisering sker också förput()
,patch()
ochdelete()
förfrågningar.Om du anger någon annan
content_type
(t.ex. text/xml för en XML-nyttolast) skickas innehållet idata
som det är i POST-begäran, medcontent_type
i HTTP-rubrikenContent-Type
.Om du inte anger något värde för
content_type
kommer värdena idata
att överföras med en innehållstyp av multipart/form-data. I det här fallet kommer nyckel-värdeparen idata
att kodas som ett multipartmeddelande och användas för att skapa POST-datanyttolasten.Om du vill skicka in flera värden för en viss nyckel - t.ex. för att ange urvalen för en
<select multiple>
- ska du ange värdena som en lista eller tupel för den nyckel som krävs. Till exempel skulle detta värde avdata
skicka tre valda värden för fältet med namnetchoices
:{"choices": ["a", "b", "d"]}
Att skicka filer är ett specialfall. För att POSTa en fil behöver du bara ange filfältets namn som en nyckel och ett filhandtag till filen du vill ladda upp som ett värde. Om ditt formulär till exempel har fälten
name
ochattachment
, är det senare enFileField
:>>> c = Client() >>> with open("wishlist.doc", "rb") as fp: ... c.post("/customers/wishes/", {"name": "fred", "attachment": fp}) ...
Du kan också tillhandahålla vilket filliknande objekt som helst (t.ex.
StringIO
ellerBytesIO
) som ett filhandtag. Om du laddar upp till enImageField
behöver objektet ettname
-attribut som skickarvalidate_image_file_extension
-validatorn. Till exempel:>>> from io import BytesIO >>> img = BytesIO( ... b"GIF89a\x01\x00\x01\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00" ... b"\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01\x00\x00" ... ) >>> img.name = "myimage.gif"
Observera att om du vill använda samma filhandtag för flera
post()
-anrop måste du manuellt återställa filpekaren mellan inläggen. Det enklaste sättet att göra detta är att manuellt stänga filen efter att den har levererats tillpost()
, som demonstreras ovan.Du bör också se till att filen öppnas på ett sätt som gör det möjligt att läsa data. Om filen innehåller binära data, t.ex. en bild, innebär det att du måste öppna filen i läget
rb
(read binary).Parametrarna
headers
,query_params
ochextra
fungerar på samma sätt som förClient.get()
.Om URL:en som du begär med en POST innehåller kodade parametrar, kommer dessa parametrar att göras tillgängliga i request.GET-data. Om du t.ex. skulle göra begäran:
>>> c.post( ... "/login/", {"name": "fred", "passwd": "secret"}, query_params={"visitor": "true"} ... )
… kan vyn som hanterar denna begäran fråga request.POST för att hämta användarnamn och lösenord, och kan fråga request.GET för att avgöra om användaren var en besökare.
Om du anger
follow
tillTrue
kommer klienten att följa alla omdirigeringar och ettredirect_chain
-attribut kommer att anges i svarsobjektet som innehåller tuples av de mellanliggande webbadresserna och statuskoderna.Om du anger
secure
tillTrue
kommer klienten att emulera en HTTPS-begäran.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- head(path, data=None, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en HEAD-förfrågan på den angivna
path
och returnerar ettResponse
-objekt. Denna metod fungerar precis somClient.get()
, inklusive parametrarnafollow
,secure
,headers
,query_params
ochextra
, förutom att den inte returnerar en meddelandekropp.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- options(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en OPTIONS-begäran på den angivna
vägen
och returnerar ettsvar
-objekt. Användbart för att testa RESTful-gränssnitt.När
data
tillhandahålls används det som förfrågningsunderlag och ettContent-Type
-huvud sätts tillcontent_type
.Parametrarna
follow
,secure
,headers
,query_params
ochextra
fungerar på samma sätt som förClient.get()
.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- put(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en PUT-förfrågan på den angivna
path
och returnerar ettResponse
-objekt. Användbart för att testa RESTful-gränssnitt.När
data
tillhandahålls används det som förfrågningsunderlag och ettContent-Type
-huvud sätts tillcontent_type
.Parametrarna
follow
,secure
,headers
,query_params
ochextra
fungerar på samma sätt som förClient.get()
.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- patch(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en PATCH-förfrågan på den angivna
banan
och returnerar ettsvar
-objekt. Användbart för att testa RESTful-gränssnitt.Parametrarna
follow
,secure
,headers
,query_params
ochextra
fungerar på samma sätt som förClient.get()
.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- delete(path, data='', content_type='application/octet-stream', follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en DELETE-begäran på den angivna
vägen
och returnerar ettsvar
-objekt. Användbart för att testa RESTful-gränssnitt.När
data
tillhandahålls används det som förfrågningsunderlag och ettContent-Type
-huvud sätts tillcontent_type
.Parametrarna
follow
,secure
,headers
,query_params
ochextra
fungerar på samma sätt som förClient.get()
.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- trace(path, follow=False, secure=False, *, headers=None, query_params=None, **extra)[source]¶
Gör en TRACE-begäran på den angivna
vägen
och returnerar ettsvar
-objekt. Användbar för att simulera diagnostiska prober.Till skillnad från de andra förfrågningsmetoderna tillhandahålls inte
data
som en nyckelordsparameter för att följa RFC 9110 Section 9.3.8, som föreskriver att TRACE-förfrågningar inte får ha en kropp.Parametrarna
follow
,secure
,headers
,query_params
ochextra
fungerar på samma sätt som förClient.get()
.Changed in Django 5.1:Argumentet
query_params
har lagts till.
- login(**credentials)¶
- alogin(**credentials)¶
Asynkron version:
alogin()`
Om din webbplats använder Djangos authentication system och du hanterar inloggning av användare, kan du använda testklientens
login()
-metod för att simulera effekten av att en användare loggar in på webbplatsen.När du anropar den här metoden kommer testklienten att ha alla cookies och sessionsdata som krävs för att klara inloggningsbaserade tester som kan ingå i en vy.
Formatet på argumentet
credentials
beror på vilken authentication backend <authentication-backends>` du använder (som konfigureras av dinAUTHENTICATION_BACKENDS
-inställning). Om du använder standardautentiseringsbackend som tillhandahålls av Django (ModelBackend
), börcredentials
vara användarens användarnamn och lösenord, som tillhandahålls som nyckelordsargument:>>> c = Client() >>> c.login(username="fred", password="secret") # Now you can access a view that's only available to logged-in users.
Om du använder en annan autentiseringsbackend kan den här metoden kräva andra referenser. Den kräver de autentiseringsuppgifter som krävs av din backends
authenticate()
metod.login()
returnerarTrue
om autentiseringsuppgifterna accepterades och inloggningen lyckades.Slutligen måste du komma ihåg att skapa användarkonton innan du kan använda den här metoden. Som vi förklarade ovan körs testlöparen med hjälp av en testdatabas, som inte innehåller några användare som standard. Därför kommer användarkonton som är giltiga på din produktionswebbplats inte att fungera under testförhållanden. Du måste skapa användare som en del av testsviten - antingen manuellt (med hjälp av Django Model API) eller med en testfixtur. Kom ihåg att om du vill att din testanvändare ska ha ett lösenord kan du inte ställa in användarens lösenord genom att ställa in lösenordsattributet direkt - du måste använda funktionen
set_password()
för att lagra ett korrekt hashat lösenord. Alternativt kan du använda hjälpmetodencreate_user()
för att skapa en ny användare med ett korrekt hashat lösenord.
- force_login(user, backend=None)¶
- aforce_login(user, backend=None)¶
Asynkron version:
aforce_login()
Om din webbplats använder Djangos authentication system kan du använda metoden
force_login()
för att simulera effekten av att en användare loggar in på webbplatsen. Använd den här metoden istället förlogin()
när ett test kräver att en användare är inloggad och detaljerna om hur en användare loggar in inte är viktiga.Till skillnad från
login()
hoppar den här metoden över autentiserings- och verifieringsstegen: inaktiva användare (is_active=False
) tillåts logga in och användarens autentiseringsuppgifter behöver inte anges.Användaren kommer att ha sitt
backend
-attribut inställt till värdet avbackend
-argumentet (som bör vara en prickad Python-sökvägssträng), eller tillsettings.AUTHENTICATION_BACKENDS[0]
om ett värde inte tillhandahålls. Funktionenauthenticate()
som anropas avlogin()
annoterar normalt användaren så här.Den här metoden är snabbare än
login()
eftersom de dyra hashingalgoritmerna för lösenord kringgås. Du kan också snabba upplogin()
genom att använda en svagare hasher när du testar.
- logout()¶
- alogout()¶
Asynkron version:
alogout()
Om din webbplats använder Djangos autentiseringssystem, kan metoden
logout()
användas för att simulera effekten av att en användare loggar ut från din webbplats.När du har anropat den här metoden kommer testklienten att ha alla cookies och sessionsdata rensade till standardvärden. Efterföljande förfrågningar kommer att se ut att komma från en
AnonymousUser
.
Testning av svar¶
Metoderna get()
och post()
returnerar båda ett Response
-objekt. Detta Response
-objekt är inte samma som HttpResponse
-objektet som returneras av Django-vyer; testsvarsobjektet har några ytterligare data som är användbara för testkod för att verifiera.
Specifikt har ett Response
-objekt följande attribut:
- class Response¶
- client¶
Den testklient som användes för att göra den begäran som resulterade i svaret.
- content¶
Svarets innehåll, som en bytestring. Detta är det slutliga sidinnehållet som återges av vyn, eller ett eventuellt felmeddelande.
- context¶
Mallen
Context
-instans som användes för att rendera den mall som producerade svarsinnehållet.Om den renderade sidan använde flera mallar kommer
context
att vara en lista medContext
-objekt, i den ordning de renderades.Oavsett hur många mallar som används vid rendering kan du hämta kontextvärden med operatorn
[]
. Exempelvis kan kontextvariabelnname
hämtas med hjälp av:>>> response = client.get("/foo/") >>> response.context["name"] 'Arthur'
Använder du inte Django-mallar?
Detta attribut fylls endast i när du använder
DjangoTemplates
backend. Om du använder en annan mallmotor kancontext_data
vara ett lämpligt alternativ för svar med det attributet.
- exc_info¶
En tupel med tre värden som ger information om det eventuella ohanterade undantag som inträffade under vyn.
Värdena är (type, value, traceback), samma som returneras av Pythons
sys.exc_info()
. Deras betydelser är:typ: Typ av undantag.
värde: Instansen för undantaget.
traceback: Ett traceback-objekt som kapslar in anropsstacken vid den punkt där undantaget ursprungligen inträffade.
Om inget undantag inträffade kommer
exc_info
att varaNone
.
- json(**kwargs)¶
Svarets text, tolkad som JSON. Extra nyckelordsargument skickas till
json.loads()
. Till exempel: json.loads:>>> response = client.get("/foo/") >>> response.json()["name"] 'Arthur'
Om rubriken
Content-Type
inte är"application/json"
, kommer ettValueError`
att uppstå när du försöker analysera svaret.
- request¶
Förfrågningsdata som stimulerade svaret.
- wsgi_request¶
Instansen
WSGIRequest
som genererades av testhanteraren som genererade svaret.
- status_code¶
HTTP-status för svaret, som ett heltal. För en fullständig lista över definierade koder, se IANA status code registry.
- templates¶
En lista över
Template
-instanser som används för att rendera det slutliga innehållet, i den ordning de renderades. För varje mall i listan, användtemplate.name
för att få mallens filnamn, om mallen laddades från en fil. (Namnet är en sträng som till exempel'admin/index.html'
.)Använder du inte Django-mallar?
Detta attribut fylls endast i när du använder
DjangoTemplates
backend. Om du använder en annan mallmotor kantemplate_name
vara ett lämpligt alternativ om du bara behöver namnet på den mall som används för rendering.
- resolver_match¶
En instans av
ResolverMatch
för svaret. Du kan till exempel använda attributetfunc
för att verifiera den vy som serverade svaret:# my_view here is a function based view. self.assertEqual(response.resolver_match.func, my_view) # Class-based views need to compare the view_class, as the # functions generated by as_view() won't be equal. self.assertIs(response.resolver_match.func.view_class, MyView)
Om den angivna URL:en inte hittas kommer åtkomst till detta attribut att ge upphov till ett
Resolver404
undantag.
Precis som med ett vanligt svar kan du också komma åt rubrikerna via HttpResponse.headers
. Du kan till exempel bestämma innehållstypen i ett svar med hjälp av response.headers['Content-Type']
.
Undantag¶
Om du riktar testklienten mot en vy som ger upphov till ett undantag och Client.raise_request_exception
är True
, kommer undantaget att vara synligt i testfallet. Du kan sedan använda ett standardblock try ... except
eller assertRaises()
för att testa för undantag.
De enda undantagen som inte är synliga för testklienten är Http404
, PermissionDenied
, SystemExit
, och SuspiciousOperation
. Django fångar dessa undantag internt och omvandlar dem till lämpliga HTTP-svarskoder. I dessa fall kan du kontrollera response.status_code
i ditt test.
Om Client.raise_request_exception
är False
kommer testklienten att returnera ett 500-svar som skulle returneras till en webbläsare. Svaret har attributet exc_info
för att ge information om det ohanterade undantaget.
Beständigt tillstånd¶
Testklienten är tillståndsbaserad. Om ett svar returnerar en cookie, kommer denna cookie att lagras i testklienten och skickas med alla efterföljande get()
- och post()
-förfrågningar.
Utgångspolicyerna för dessa cookies följs inte. Om du vill att en cookie ska upphöra att gälla måste du antingen ta bort den manuellt eller skapa en ny instans av Client
(vilket i praktiken innebär att alla cookies tas bort).
En testklient har attribut som lagrar information om beständigt tillstånd. Du kan komma åt dessa egenskaper som en del av ett testvillkor.
- Client.cookies¶
Ett Python
SimpleCookie
-objekt som innehåller de aktuella värdena för alla klientcookies. Se dokumentationen för modulenhttp.cookies
för mer information.
- Client.session¶
Ett ordboksliknande objekt som innehåller sessionsinformation. Se session documentation för fullständig information.
För att ändra sessionen och sedan spara den måste den först lagras i en variabel (eftersom en ny
SessionStore
skapas varje gång denna egenskap används):def test_something(self): session = self.client.session session["somekey"] = "test" session.save()
Ställa in språk¶
När du testar applikationer som stöder internationalisering och lokalisering kanske du vill ställa in språket för en testklientbegäran. Metoden för att göra detta beror på om LocaleMiddleware
är aktiverad eller inte.
Om middleware är aktiverat kan språket ställas in genom att skapa en cookie med namnet LANGUAGE_COOKIE_NAME
och ett värde som motsvarar språkkoden:
from django.conf import settings
def test_language_using_cookie(self):
self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fr"})
response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
eller genom att inkludera HTTP-huvudet Accept-Language
i begäran:
def test_language_using_header(self):
response = self.client.get("/", headers={"accept-language": "fr"})
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Observera
När du använder dessa metoder, se till att återställa det aktiva språket i slutet av varje test:
def tearDown(self):
translation.activate(settings.LANGUAGE_CODE)
Mer information finns i Hur Django upptäcker språkpreferenser.
Om mellanvaran inte är aktiverad kan det aktiva språket ställas in med translation.override()
:
from django.utils import translation
def test_language_using_override(self):
with translation.override("fr"):
response = self.client.get("/")
self.assertEqual(response.content, b"Bienvenue sur mon site.")
Mer information finns i Explicit inställning av det aktiva språket.
Exempel¶
Följande är ett enhetstest med hjälp av testklienten:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
# Every test needs a client.
self.client = Client()
def test_details(self):
# Issue a GET request.
response = self.client.get("/customer/details/")
# Check that the response is 200 OK.
self.assertEqual(response.status_code, 200)
# Check that the rendered context contains 5 customers.
self.assertEqual(len(response.context["customers"]), 5)
Tillhandahållna testfallsklasser¶
Vanliga Python-enhetstestklasser utökar en basklass av unittest.TestCase
. Django tillhandahåller några tillägg till denna basklass:
Hierarki av klasser för enhetstestning av Django¶
Du kan konvertera en normal unittest.TestCase
till någon av underklasserna: ändra basklassen för ditt test från unittest.TestCase
till underklassen. Alla Pythons standardfunktioner för enhetstester kommer att finnas tillgängliga, och de kommer att kompletteras med några användbara tillägg som beskrivs i varje avsnitt nedan.
SimpleTestCase
¶
En subklass av unittest.TestCase
som lägger till denna funktionalitet:
Några användbara påståenden som:
Kontrollerar att en anropsbar
utlöser ett visst undantag
.Kontrollerar att en anropsbar
utlöser en viss varning
.Testar formulärfält
rendering och felbehandling
.Testar
HTML-svar för närvaro/avsaknad av ett visst fragment
.Kontrollerar att en mall
har/har inte använts för att generera ett visst svarsinnehåll
.Kontrollerar att två
URL:er
är lika.Verifiera att en HTTP
redirect
utförs av appen.Robust testning av två
HTML-fragment
för likhet/olikhet ellercontainment
.Robust test av två
XML-fragment
för likhet/olikhet.Robust test av två
JSON-fragment
för jämlikhet.
Möjligheten att köra tester med modified settings.
Om dina tester gör några databasfrågor, använd underklasserna TransactionTestCase
eller TestCase
.
- SimpleTestCase.databases¶
SimpleTestCase
tillåter inte databasfrågor som standard. Detta hjälper till att undvika att skriva frågor som påverkar andra tester eftersom varjeSimpleTestCase
-test inte körs i en transaktion. Om du inte är orolig för det här problemet kan du inaktivera det här beteendet genom att ställa in klassattributetdatabases
till'__all__'
på din testklass.
Varning
SimpleTestCase
och dess underklasser (t.ex. TestCase
, …) förlitar sig på setUpClass()
och tearDownClass()
för att utföra vissa klassomfattande initialiseringar (t.ex. åsidosätta inställningar). Om du behöver åsidosätta dessa metoder, glöm inte att anropa super
implementationen:
class MyTestCase(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
...
@classmethod
def tearDownClass(cls):
...
super().tearDownClass()
Var noga med att ta hänsyn till Pythons beteende om ett undantag uppstår under setUpClass()
. Om det händer kommer varken testerna i klassen eller tearDownClass()
att köras. I fallet med django.test.TestCase
kommer detta att läcka transaktionen som skapats i super()
vilket resulterar i olika symptom inklusive ett segmenteringsfel på vissa plattformar (rapporterat på macOS). Om du avsiktligt vill skapa ett undantag som unittest.SkipTest
i setUpClass()
, se till att göra det innan du anropar super()
för att undvika detta.
TransactionTestCase
¶
TransactionTestCase
ärver från SimpleTestCase
för att lägga till några databasspecifika funktioner:
Återställning av databasen till ett känt tillstånd i slutet av varje test för att underlätta testning och användning av ORM.
Databas
fixtures
.De återstående specialiserade
assert*
-metoderna.
Djangos klass TestCase
är en mer vanligt förekommande underklass till TransactionTestCase
som använder sig av databasens transaktionsmöjligheter för att snabba upp processen med att återställa databasen till ett känt tillstånd i slutet av varje test. En konsekvens av detta är dock att vissa databasbeteenden inte kan testas inom en Django TestCase
-klass. Du kan till exempel inte testa att ett kodblock körs inom en transaktion, vilket krävs när du använder select_for_update()
. I dessa fall bör du använda TransactionTestCase
.
TransactionTestCase
och TestCase
är identiska med undantag för det sätt på vilket databasen återställs till ett känt tillstånd och möjligheten för testkoden att testa effekterna av commit och rollback:
Ett
TransactionTestCase
återställer databasen efter testkörningen genom att trunkera alla tabeller. EttTransactionTestCase
kan anropa commit och rollback och observera effekterna av dessa anrop på databasen.Ett
TestCase
trunkerar däremot inte tabeller efter ett test. Istället innesluts testkoden i en databastransaktion som rullas tillbaka i slutet av testet. Detta garanterar att rollback i slutet av testet återställer databasen till dess ursprungliga tillstånd.
Varning
TestCase
som körs på en databas som inte stöder rollback (t.ex. MySQL med lagringsmotorn MyISAM), och alla instanser av TransactionTestCase
, kommer att rulla tillbaka i slutet av testet genom att ta bort alla data från testdatabasen.
Appar kommer inte att se sina data laddas om; om du behöver den här funktionen (till exempel bör tredjepartsappar aktivera detta) kan du ställa in `serialized_rollback = True
i TestCase
-kroppen.
TestCase
¶
Detta är den vanligaste klassen att använda för att skriva tester i Django. Den ärver från TransactionTestCase
(och i förlängningen SimpleTestCase
). Om din Django-applikation inte använder en databas, använd SimpleTestCase
.
Klassen:
Omsluter testerna i två nästlade
atomic()
-block: ett för hela klassen och ett för varje test. Därför, om du vill testa något specifikt databas transaktionsbeteende, användTransactionTestCase
.Kontrollerar uppskjutbara databasbegränsningar i slutet av varje test.
Det ger också en ytterligare metod:
- classmethod TestCase.setUpTestData()[source]¶
Det
atomiska
block på klassnivå som beskrivs ovan gör det möjligt att skapa initialdata på klassnivå, en gång för helaTestCase
. Denna teknik möjliggör snabbare tester jämfört med att användasetUp()
.Till exempel:
from django.test import TestCase class MyTests(TestCase): @classmethod def setUpTestData(cls): # Set up data for the whole TestCase cls.foo = Foo.objects.create(bar="Test") ... def test1(self): # Some test using self.foo ... def test2(self): # Some other test using self.foo ...
Observera att om testerna körs på en databas utan transaktionsstöd (t.ex. MySQL med MyISAM-motorn) kommer
setUpTestData()
att anropas före varje test, vilket upphäver hastighetsfördelarna.Objekt som tilldelas klassattribut i
setUpTestData()
måste stödja skapandet av djupa kopior medcopy.deepcopy()
för att isolera dem från ändringar som utförs av varje testmetod.
- classmethod TestCase.captureOnCommitCallbacks(using=DEFAULT_DB_ALIAS, execute=False)[source]¶
Returnerar en kontexthanterare som fångar upp
transaction.on_commit()
callbacks för den angivna databasanslutningen. Den returnerar en lista som innehåller, vid avslutning av kontexten, de fångade återuppringningsfunktionerna. Från den här listan kan du göra påståenden om återuppringningarna eller anropa dem för att åberopa deras sidoeffekter, vilket emulerar en commit.using
är aliaset för den databasanslutning för vilken återuppringningar ska registreras.Om
execute
ärTrue
kommer alla callbacks att anropas när kontexthanteraren avslutas, om inget undantag inträffade. Detta emulerar en commit efter det omslutna kodblocket.Till exempel:
from django.core import mail from django.test import TestCase class ContactTests(TestCase): def test_post(self): with self.captureOnCommitCallbacks(execute=True) as callbacks: response = self.client.post( "/contact/", {"message": "I like your site"}, ) self.assertEqual(response.status_code, 200) self.assertEqual(len(callbacks), 1) self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, "Contact Form") self.assertEqual(mail.outbox[0].body, "I like your site")
LiveServerTestCase
¶
LiveServerTestCase
gör i princip samma sak som TransactionTestCase
med en extra funktion: den startar en live Django-server i bakgrunden vid installationen och stänger av den vid nedmonteringen. Detta gör det möjligt att använda andra automatiserade testklienter än Django dummy client som till exempel Selenium-klienten, för att utföra en serie funktionella tester i en webbläsare och simulera en riktig användares handlingar.
Live-servern lyssnar på localhost
och binder till port 0 som använder en fri port som tilldelas av operativsystemet. Serverns URL kan nås med self.live_server_url
under testerna.
För att visa hur man använder LiveServerTestCase
, låt oss skriva ett Selenium-test. Först och främst måste du installera paketet selenium:
$ python -m pip install "selenium >= 4.8.0"
...\> py -m pip install "selenium >= 4.8.0"
Lägg sedan till ett LiveServerTestCase
-baserat test i din apps testmodul (till exempel: myapp/tests.py
). I det här exemplet antar vi att du använder staticfiles
-appen och vill ha statiska filer serverade under körningen av dina tester liknande det vi får vid utvecklingstid med DEBUG=True
, dvs. utan att behöva samla in dem med collectstatic
. Vi kommer att använda StaticLiveServerTestCase
subklassen som tillhandahåller den funktionaliteten. Ersätt den med django.test.LiveServerTestCase
om du inte behöver det.
Koden för detta test kan se ut på följande sätt:
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.webdriver import WebDriver
class MySeleniumTests(StaticLiveServerTestCase):
fixtures = ["user-data.json"]
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.selenium = WebDriver()
cls.selenium.implicitly_wait(10)
@classmethod
def tearDownClass(cls):
cls.selenium.quit()
super().tearDownClass()
def test_login(self):
self.selenium.get(f"{self.live_server_url}/login/")
username_input = self.selenium.find_element(By.NAME, "username")
username_input.send_keys("myuser")
password_input = self.selenium.find_element(By.NAME, "password")
password_input.send_keys("secret")
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
Slutligen kan du köra testet på följande sätt:
$ ./manage.py test myapp.tests.MySeleniumTests.test_login
...\> manage.py test myapp.tests.MySeleniumTests.test_login
Detta exempel kommer automatiskt att öppna Firefox och sedan gå till inloggningssidan, ange inloggningsuppgifterna och trycka på knappen ”Logga in”. Selenium erbjuder andra drivrutiner om du inte har Firefox installerat eller vill använda en annan webbläsare. Exemplet ovan är bara en liten bråkdel av vad Selenium-klienten kan göra; kolla in full reference för mer information.
Observera
När man använder en SQLite-databas i minnet för att köra testerna kommer samma databasanslutning att delas av två parallella trådar: den tråd i vilken live-servern körs och den tråd i vilken testfallet körs. Det är viktigt att förhindra samtidiga databasfrågor via denna delade anslutning av de två trådarna, eftersom det ibland slumpmässigt kan leda till att testerna misslyckas. Du måste alltså se till att de två trådarna inte kommer åt databasen samtidigt. I synnerhet innebär detta att du i vissa fall (t.ex. strax efter att du har klickat på en länk eller skickat in ett formulär) kan behöva kontrollera att Selenium får ett svar och att nästa sida laddas innan du fortsätter med ytterligare testkörning. Det gör du t.ex. genom att låta Selenium vänta tills HTML-taggen <body>
hittas i svaret (kräver Selenium > 2.13):
def test_login(self):
from selenium.webdriver.support.wait import WebDriverWait
timeout = 2
...
self.selenium.find_element(By.XPATH, '//input[@value="Log in"]').click()
# Wait until the response is received
WebDriverWait(self.selenium, timeout).until(
lambda driver: driver.find_element(By.TAG_NAME, "body")
)
Det knepiga här är att det egentligen inte finns något sådant som en ”sidladdning”, särskilt i moderna webbappar som genererar HTML dynamiskt efter att servern har genererat det ursprungliga dokumentet. Så att kontrollera om <body>
finns i svaret är inte nödvändigtvis lämpligt för alla användningsfall. Mer information finns i Selenium FAQ och Selenium documentation.
Testfall funktioner¶
Standard testklient¶
- SimpleTestCase.client¶
Varje testfall i en instans av django.test.*TestCase
har tillgång till en instans av en Django-testklient. Denna klient kan nås som self.client
. Den här klienten återskapas för varje test, så du behöver inte oroa dig för att tillstånd (t.ex. cookies) överförs från ett test till ett annat.
Detta innebär att istället för att instansiera en Client
i varje test:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def test_details(self):
client = Client()
response = client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
client = Client()
response = client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
…kan du hänvisa till self.client
, så här:
from django.test import TestCase
class SimpleTest(TestCase):
def test_details(self):
response = self.client.get("/customer/details/")
self.assertEqual(response.status_code, 200)
def test_index(self):
response = self.client.get("/customer/index/")
self.assertEqual(response.status_code, 200)
Anpassning av testklienten¶
- SimpleTestCase.client_class¶
Om du vill använda en annan Client
-klass (t.ex. en underklass med anpassat beteende) använder du klassattributet client_class
:
from django.test import Client, TestCase
class MyTestClient(Client):
# Specialized methods for your environment
...
class MyTest(TestCase):
client_class = MyTestClient
def test_my_stuff(self):
# Here self.client is an instance of MyTestClient...
call_some_test_code()
Lastning av fixtur¶
- TransactionTestCase.fixtures¶
En testfallsklass för en databasbaserad webbplats är inte till någon större nytta om det inte finns några data i databasen. Tester är mer läsbara och det är mer underhållbart att skapa objekt med hjälp av ORM, till exempel i TestCase.setUpTestData()
, men du kan också använda fixtures.
En fixtur är en samling data som Django vet hur man importerar till en databas. Om din webbplats till exempel har användarkonton kan du skapa en fixtur med falska användarkonton för att fylla på din databas under tester.
Det enklaste sättet att skapa en fixtur är att använda kommandot manage.py dumpdata
. Detta förutsätter att du redan har en del data i din databas. Se dumpdata documentation
för mer information.
När du har skapat en fixtur och placerat den i en fixtures
katalog i en av dina INSTALLED_APPS
, kan du använda den i dina enhetstester genom att ange ett fixtures
klassattribut på din django.test.TestCase
subclass:
from django.test import TestCase
from myapp.models import Animal
class AnimalTestCase(TestCase):
fixtures = ["mammals.json", "birds"]
def setUp(self):
# Test definitions as before.
call_setup_methods()
def test_fluffy_animals(self):
# A test that uses the fixtures.
call_some_test_code()
Det här är vad som kommer att hända i detalj:
Under
setUpClass()
installeras alla namngivna fixturer. I det här exemplet kommer Django att installera alla JSON-fixturer med namnetmammals
, följt av alla fixturer med namnetbirds
. Se ämnet Fixturer för mer information om hur man definierar och installerar fixturer.
För de flesta enhetstester som använder TestCase
behöver Django inte göra något annat, eftersom transaktioner används för att rensa databasen efter varje test av prestandaskäl. Men för TransactionTestCase
kommer följande åtgärder att äga rum:
I slutet av varje test kommer Django att spola databasen, vilket återställer databasen till det tillstånd den befann sig i direkt efter att
migrate
anropades.För varje efterföljande test kommer fixturerna att laddas om innan
setUp()
körs.
I vilket fall som helst kan du vara säker på att resultatet av ett test inte kommer att påverkas av ett annat test eller av ordningen på testgenomförandet.
Som standard laddas fixturer endast in i databasen default
. Om du använder flera databaser och anger TransactionTestCase.databases
kommer fixturer att laddas i alla angivna databaser.
För TransactionTestCase
gjordes fixturer tillgängliga under setUpClass()
.
URLconf-konfiguration¶
Om din applikation innehåller vyer kanske du vill inkludera tester som använder testklienten för att aktivera dessa vyer. Det står dock en slutanvändare fritt att distribuera vyerna i din applikation till vilken URL som helst. Detta innebär att dina tester inte kan förlita sig på att dina vyer kommer att finnas tillgängliga på en viss URL. Dekorera din testklass eller testmetod med @override_settings(ROOT_URLCONF=...)
för URLconf-konfiguration.
Stöd för flera databaser¶
- TransactionTestCase.databases¶
Django skapar en testdatabas som motsvarar varje databas som definieras i DATABASER
-definitionen i dina inställningar och som minst ett test hänvisar till via databaser
.
En stor del av den tid det tar att köra ett Django TestCase
förbrukas dock av anropet till flush
som säkerställer att du har en ren databas i slutet av varje testkörning. Om du har flera databaser krävs flera spolningar (en för varje databas), vilket kan vara en tidskrävande aktivitet - särskilt om dina tester inte behöver testa aktivitet i flera databaser.
Som en optimering spolar Django bara databasen default
i slutet av varje testkörning. Om din installation innehåller flera databaser och du har ett test som kräver att alla databaser är rena, kan du använda attributet databaser
på testsviten för att begära att extra databaser spolas.
Till exempel:
class TestMyViews(TransactionTestCase):
databases = {"default", "other"}
def test_index_page_view(self):
call_some_test_code()
Denna testfallsklass kommer att rensa testdatabaserna default
och other
efter att ha kört test_index_page_view
. Du kan också använda '__all__'
för att ange att alla testdatabaser måste rensas.
Flaggan databases
styr också vilka databaser som TransactionTestCase.fixtures
laddas in i. Som standard laddas fixturerna endast in i databasen default
.
Frågor mot databaser som inte finns i databaser
kommer att ge assertionfel för att förhindra att tillstånd läcker mellan tester.
- TestCase.databases¶
Som standard kommer endast standard
-databasen att omslutas av en transaktion under exekveringen av ett TestCase
och försök att fråga andra databaser kommer att resultera i assertionfel för att förhindra att tillstånd läcker mellan tester.
Använd klassattributet databases
på testklassen för att begära transaktionsomslag mot databaser som inte är standard
.
Till exempel:
class OtherDBTests(TestCase):
databases = {"other"}
def test_other_db_query(self): ...
Detta test tillåter endast frågor mot databasen other
. Precis som för SimpleTestCase.databases
och TransactionTestCase.databases
kan konstanten '__all__'
användas för att ange att testet ska tillåta frågor till alla databaser.
Åsidosätta inställningar¶
Varning
Använd funktionerna nedan för att tillfälligt ändra värdet på inställningar i tester. Manipulera inte django.conf.settings
direkt eftersom Django inte kommer att återställa de ursprungliga värdena efter sådana manipulationer.
För teständamål är det ofta användbart att ändra en inställning tillfälligt och återgå till det ursprungliga värdet efter att testkoden har körts. För detta användningsfall tillhandahåller Django en standard Python-kontexthanterare (se PEP 343) som heter settings()
, som kan användas så här:
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/accounts/login/?next=/sekrit/")
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL="/other/login/"):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
Detta exempel åsidosätter inställningen LOGIN_URL
för koden i with
-blocket och återställer dess värde till det tidigare tillståndet efteråt.
Det kan vara krångligt att omdefiniera inställningar som innehåller en lista med värden. I praktiken är det ofta tillräckligt att lägga till eller ta bort värden. Django tillhandahåller kontexthanteraren modify_settings()
för enklare ändringar av inställningar:
from django.test import TestCase
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
with self.modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
"remove": [
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
],
}
):
response = self.client.get("/")
# ...
För varje åtgärd kan du ange antingen en lista med värden eller en sträng. När värdet redan finns i listan har append
och prepend
ingen effekt; det har inte heller remove
när värdet inte finns.
Om du vill åsidosätta en inställning för en testmetod tillhandahåller Django dekoratorn override_settings()
(se PEP 318). Den används så här:
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
@override_settings(LOGIN_URL="/other/login/")
def test_login(self):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
Dekoratorn kan också tillämpas på TestCase
-klasser:
from django.test import TestCase, override_settings
@override_settings(LOGIN_URL="/other/login/")
class LoginTestCase(TestCase):
def test_login(self):
response = self.client.get("/sekrit/")
self.assertRedirects(response, "/other/login/?next=/sekrit/")
På samma sätt tillhandahåller Django dekoratorn modify_settings()
:
from django.test import TestCase, modify_settings
class MiddlewareTestCase(TestCase):
@modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
}
)
def test_cache_middleware(self):
response = self.client.get("/")
# ...
Dekoratorn kan också tillämpas på testfallsklasser:
from django.test import TestCase, modify_settings
@modify_settings(
MIDDLEWARE={
"append": "django.middleware.cache.FetchFromCacheMiddleware",
"prepend": "django.middleware.cache.UpdateCacheMiddleware",
}
)
class MiddlewareTestCase(TestCase):
def test_cache_middleware(self):
response = self.client.get("/")
# ...
Observera
När de får en klass modifierar dessa dekoratorer klassen direkt och returnerar den; de skapar inte och returnerar en modifierad kopia av den. Så om du försöker justera ovanstående exempel för att tilldela returvärdet ett annat namn än LoginTestCase
eller MiddlewareTestCase
, kan du bli förvånad över att upptäcka att de ursprungliga testfallsklasserna fortfarande påverkas lika mycket av dekoratorn. För en given klass tillämpas modify_settings()
alltid efter override_settings()
.
Varning
Inställningsfilen innehåller vissa inställningar som endast konsulteras under initialisering av Django internals. Om du ändrar dem med override_settings
ändras inställningen om du kommer åt den via modulen django.conf.settings
, men Djangos internals kommer åt den på ett annat sätt. Att använda override_settings()
eller modify_settings()
med dessa inställningar kommer förmodligen inte att göra vad du förväntar dig att det ska göra.
Vi rekommenderar inte att du ändrar inställningen DATABASER
. Det är möjligt att ändra inställningen CACHES
, men det är lite knepigt om du använder interna funktioner som använder cachelagring, som django.contrib.sessions
. Du måste till exempel initiera sessionsbackend på nytt i ett test som använder cachade sessioner och åsidosätter CACHES
.
Slutligen bör du undvika att använda dina inställningar som konstanter på modulnivå eftersom override_settings()
inte fungerar på sådana värden eftersom de bara utvärderas första gången modulen importeras.
Du kan också simulera att en inställning inte finns genom att radera den efter att inställningarna har åsidosatts, så här:
@override_settings()
def test_something(self):
del settings.LOGIN_URL
...
När du åsidosätter inställningar måste du se till att hantera de fall där din appkod använder en cache eller liknande funktion som behåller tillståndet även om inställningen ändras. Django tillhandahåller django.test.signals.setting_changed
-signalen som låter dig registrera återuppringningar för att städa upp och på annat sätt återställa tillståndet när inställningarna ändras.
Django själv använder denna signal för att återställa olika data:
Åsidosatta inställningar |
Återställning av data |
---|---|
ANVÄNDNING_TZ, TIDSZON |
Tidszon för databaser |
TEMPLAT |
Mall för motorer |
FORM_RENDERER |
Standardrenderare |
SERIALISERING_MODULER |
Serialisatorer cache |
LOKALA_ SÖKVÄGAR, SPRÅKKOD |
Standardöversättning och inlästa översättningar |
STATIC_ROOT, STATIC_URL, LAGRING |
Konfiguration av lagringsutrymmen |
Återställning av standardrenderaren när inställningen FORM_RENDERER
ändras har lagts till.
Isolera appar¶
- utils.isolate_apps(*app_labels, attr_name=None, kwarg_name=None)¶
Registrerar de modeller som definieras i en omsluten kontext i sitt eget isolerade
apps
-register. Denna funktion är användbar när du skapar modellklasser för tester, eftersom klasserna kommer att raderas rent efteråt och det inte finns någon risk för namnkollisioner.De appetiketter som det isolerade registret ska innehålla måste skickas som enskilda argument. Du kan använda
isolate_apps()
som en dekorator eller en kontexthanterare. Till exempel:from django.db import models from django.test import SimpleTestCase from django.test.utils import isolate_apps class MyModelTests(SimpleTestCase): @isolate_apps("app_label") def test_model_definition(self): class TestModel(models.Model): pass ...
… eller:
with isolate_apps("app_label"): class TestModel(models.Model): pass ...
Dekoratorformen kan också tillämpas på klasser.
Två valfria nyckelordsargument kan anges:
attr_name
: attribut som tilldelas det isolerade registret om det används som en klassdekorator.kwarg_name
: nyckelordsargument som skickar det isolerade registret om det används som en funktionsdekorator.
Den tillfälliga instansen
Apps
som används för att isolera modellregistreringen kan hämtas som ett attribut när den används som en klassdekorator genom att använda parameternattr_name
:@isolate_apps("app_label", attr_name="apps") class TestModelDefinition(SimpleTestCase): def test_model_definition(self): class TestModel(models.Model): pass self.assertIs(self.apps.get_model("app_label", "TestModel"), TestModel)
… eller alternativt som ett argument på testmetoden när den används som en metoddekorator genom att använda parametern
kwarg_name
:class TestModelDefinition(SimpleTestCase): @isolate_apps("app_label", kwarg_name="apps") def test_model_definition(self, apps): class TestModel(models.Model): pass self.assertIs(apps.get_model("app_label", "TestModel"), TestModel)
Tömning av testboxen¶
Om du använder någon av Djangos anpassade TestCase
-klasser kommer testlöparen att rensa innehållet i test-e-postens utkorg i början av varje testfall.
För mer information om e-posttjänster under tester, se E-posttjänster nedan.
Påståenden¶
Precis som Pythons normala klass unittest.TestCase
implementerar assertion-metoder som assertTrue()
och assertEqual()
, tillhandahåller Djangos anpassade klass TestCase
ett antal anpassade assertion-metoder som är användbara för att testa webbapplikationer:
De felmeddelanden som ges av de flesta av dessa assertion-metoder kan anpassas med argumentet msg_prefix
. Denna sträng kommer att prefixeras till alla felmeddelanden som genereras av påståendet. Detta gör att du kan ge ytterligare information som kan hjälpa dig att identifiera platsen för och orsaken till ett fel i din testsvit.
- SimpleTestCase.assertRaisesMessage(expected_exception, expected_message, callable, *args, **kwargs)[source]¶
- SimpleTestCase.assertRaisesMessage(expected_exception, expected_message)
Säger att exekvering av
callable
ger upphov tillexpected_exception
och attexpected_message
finns i undantagets meddelande. Alla andra resultat rapporteras som ett misslyckande. Det är en enklare version avunittest.TestCase.assertRaisesRegex()
med skillnaden attexpected_message
inte behandlas som ett reguljärt uttryck.Om endast parametrarna
expected_exception
ochexpected_message
anges, returneras en kontexthanterare så att den kod som testas kan skrivas inline i stället för som en funktion:with self.assertRaisesMessage(ValueError, "invalid literal for int()"): int("a")
- SimpleTestCase.assertWarnsMessage(expected_warning, expected_message, callable, *args, **kwargs)[source]¶
- SimpleTestCase.assertWarnsMessage(expected_warning, expected_message)
Analogt med
SimpleTestCase.assertRaisesMessage()
men förassertWarnsRegex()
istället förassertRaisesRegex()
.
- SimpleTestCase.assertFieldOutput(fieldclass, valid, invalid, field_args=None, field_kwargs=None, empty_value='')[source]¶
Försäkrar att ett formulärfält beter sig korrekt med olika inmatningar.
- Parametrar:
fieldclass – klassen för det fält som ska testas.
valid – en ordlista som mappar giltiga indata till deras förväntade rensade värden.
invalid – en ordlista som kopplar ogiltiga indata till ett eller flera felmeddelanden.
field_args – de args som skickas för att instansiera fältet.
field_kwargs – de kwargs som skickas för att instansiera fältet.
empty_value – den förväntade rena utdata för indata i
empty_values
.
Följande kod testar till exempel att en
EmailField
accepterara@a.com
som en giltig e-postadress, men avvisaraaa
med ett rimligt felmeddelande:self.assertFieldOutput( EmailField, {"a@a.com": "a@a.com"}, {"aaa": ["Enter a valid email address."]} )
- SimpleTestCase.assertFormError(form, field, errors, msg_prefix='')[source]¶
Säger att ett fält i ett formulär ger upphov till den angivna listan med fel.
form
är enForm
instans. Formuläret måste vara bundet men inte nödvändigtvis validerat (assertFormError()
kommer automatiskt att anropafull_clean()
på formuläret).field
är namnet på det fält i formuläret som ska kontrolleras. För att kontrollera formuläretsicke-fältfel
, användfield=None
.errors
är en lista med alla felsträngar som fältet förväntas ha. Du kan också skicka en enda felsträng om du bara förväntar dig ett fel, vilket innebär atterrors='error message'
är detsamma somerrors=['error message']
.
- SimpleTestCase.assertFormSetError(formset, form_index, field, errors, msg_prefix='')[source]¶
Säger att
formset
ger upphov till den angivna listan med fel när det renderas.formset
är en instans avFormSet
. Formsetet måste vara bundet men inte nödvändigtvis validerat (assertFormSetError()
anropar automatisktfull_clean()
på formsetet).form_index
är numret på formuläret iFormSet
(med början från 0). Användform_index=None
för att kontrollera formuläruppsättningens icke-formulärfel, dvs. de fel som du får när du anroparformset.non_form_errors()
. I så fall måste du också användafield=None
.field
ocherrors
har samma betydelse som parametrarna tillassertFormError()
.
- SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)[source]¶
Säger att en
response
producerade den givnastatus_code
och atttext
finns i desscontent
. Omcount
anges måstetext
förekomma exaktcount
gånger i svaret.Sätt
html
tillTrue
för att hanteratext
som HTML. Jämförelsen med svarsinnehållet kommer att baseras på HTML-semantik i stället för likhet tecken för tecken. Whitespace ignoreras i de flesta fall, attributordning är inte signifikant. SeassertHTMLEqual()
för mer information.Changed in Django 5.1:I äldre versioner innehöll felmeddelanden inte svarsinnehållet.
- SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)[source]¶
Säger att en
response
producerade den angivnastatus_code
och atttext
inte förekommer i desscontent
.Sätt
html
tillTrue
för att hanteratext
som HTML. Jämförelsen med svarsinnehållet kommer att baseras på HTML-semantik i stället för likhet tecken för tecken. Whitespace ignoreras i de flesta fall, attributordning är inte signifikant. SeassertHTMLEqual()
för mer information.Changed in Django 5.1:I äldre versioner innehöll felmeddelanden inte svarsinnehållet.
- SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='', count=None)[source]¶
Bekräftar att mallen med det angivna namnet användes vid rendering av svaret.
response
måste vara en svarsinstans som returneras avtest client
.template_name
bör vara en sträng som till exempel'admin/index.html'
.Argumentet
count
är ett heltal som anger hur många gånger mallen ska återges. Standard ärNone
, vilket innebär att mallen ska återges en eller flera gånger.Du kan använda detta som en kontexthanterare, så här:
with self.assertTemplateUsed("index.html"): render_to_string("index.html") with self.assertTemplateUsed(template_name="index.html"): render_to_string("index.html")
- SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')[source]¶
Bekräftar att mallen med det angivna namnet inte användes vid rendering av svaret.
Du kan använda detta som en kontexthanterare på samma sätt som
assertTemplateUsed()
.
- SimpleTestCase.assertURLEqual(url1, url2, msg_prefix='')[source]¶
Påstår att två webbadresser är desamma och ignorerar ordningen på parametrarna i frågesträngen, utom för parametrar med samma namn. Till exempel är
/path/?x=1&y=2
lika med/path/?y=2&x=1
, men/path/?a=1&a=2
är inte lika med/path/?a=2&a=1
.
- SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)[source]¶
Påstår att
response
returnerade enstatus_code
omdirigeringsstatus, omdirigerades tillexpected_url
(inklusive eventuellaGET
data), och att den slutliga sidan mottogs medtarget_status_code
.Om din begäran använde argumentet
follow
kommerexpected_url
ochtarget_status_code
att vara webbadressen och statuskoden för den sista punkten i omdirigeringskedjan.Om
fetch_redirect_response
ärFalse
kommer den slutliga sidan inte att laddas. Eftersom testklienten inte kan hämta externa webbadresser är detta särskilt användbart omexpected_url
inte är en del av din Django-app.Schema hanteras korrekt när jämförelser görs mellan två webbadresser. Om det inte finns något schema angivet på den plats dit vi omdirigeras används den ursprungliga begärans schema. Om det finns, är det schemat i
expected_url
som används för att göra jämförelser.
- SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)[source]¶
Påstår att strängarna
html1
ochhtml2
är lika. Jämförelsen är baserad på HTML-semantik. Jämförelsen tar hänsyn till följande saker:Mellanslag före och efter HTML-taggar ignoreras.
Alla typer av blanksteg betraktas som likvärdiga.
Alla öppna taggar stängs implicit, t.ex. när en omgivande tagg stängs eller när HTML-dokumentet avslutas.
Tomma taggar är likvärdiga med deras självstängande version.
Ordningsföljden på attributen i ett HTML-element är inte viktig.
Booleska attribut (t.ex.
checked
) utan argument är lika med attribut som har samma namn och värde (se exemplen).Text-, tecken- och entitetsreferenser som hänvisar till samma tecken är likvärdiga.
Följande exempel är giltiga tester och ger inte upphov till några
AssertionError
:self.assertHTMLEqual( "<p>Hello <b>'world'!</p>", """<p> Hello <b>'world'! </b> </p>""", ) self.assertHTMLEqual( '<input type="checkbox" checked="checked" id="id_accept_terms" />', '<input id="id_accept_terms" type="checkbox" checked>', )
html1
ochhtml2
måste innehålla HTML. EttAssertionError
kommer att uppstå om en av dem inte kan tolkas.Utdata i händelse av fel kan anpassas med argumentet
msg
.
- SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)[source]¶
Påstår att strängarna
html1
ochhtml2
är inte lika. Jämförelsen baseras på HTML-semantik. SeassertHTMLEqual()
för detaljer.html1
ochhtml2
måste innehålla HTML. EttAssertionError
kommer att uppstå om en av dem inte kan tolkas.Utdata i händelse av fel kan anpassas med argumentet
msg
.
- SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)[source]¶
Påstår att strängarna
xml1
ochxml2
är lika. Jämförelsen baseras på XML-semantik. På samma sätt somassertHTMLEqual()
, görs jämförelsen på parsat innehåll, vilket innebär att endast semantiska skillnader beaktas, inte syntaxskillnader. När ogiltig XML skickas i någon parameter, uppstår alltid ettAssertionError
, även om båda strängarna är identiska.XML-deklaration, dokumenttyp, bearbetningsinstruktioner och kommentarer ignoreras. Endast rotelementet och dess underordnade element jämförs.
Utdata i händelse av fel kan anpassas med argumentet
msg
.
- SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)[source]¶
Påstår att strängarna
xml1
ochxml2
är inte lika. Jämförelsen är baserad på XML-semantik. SeassertXMLEqual()
för detaljer.Utdata i händelse av fel kan anpassas med argumentet
msg
.
- SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')[source]¶
Påstår att HTML-fragmentet
needle
ingår ihaystack
en gång.Om heltalsargumentet
count
anges, kommer dessutom antalet förekomster avnålar
att kontrolleras noggrant.I de flesta fall ignoreras blanksteg och attributordningen är inte signifikant. Se
assertHTMLEqual()
för mer information.Changed in Django 5.1:I äldre versioner innehöll felmeddelanden inte
haystack
.
- SimpleTestCase.assertNotInHTML(needle, haystack, msg_prefix='')[source]¶
- New in Django 5.1.
Påstår att HTML-fragmentet
needle
inte ingår ihaystack
.I de flesta fall ignoreras blanksteg och attributordningen är inte signifikant. Se
assertHTMLEqual()
för mer information.
- SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)[source]¶
Påstår att JSON-fragmenten
raw
ochexpected_data
är lika. Vanliga JSON-regler för icke-signifikanta blanksteg gäller eftersom tungvikten är delegerad tilljson
-biblioteket.Utdata i händelse av fel kan anpassas med argumentet
msg
.
- SimpleTestCase.assertJSONNotEqual(raw, expected_data, msg=None)[source]¶
Säger att JSON-fragmenten
raw
ochexpected_data
inte är lika. SeassertJSONEqual()
för ytterligare detaljer.Utdata i händelse av fel kan anpassas med argumentet
msg
.
- TransactionTestCase.assertQuerySetEqual(qs, values, transform=None, ordered=True, msg=None)[source]¶
Säger att en frågeuppsättning
qs
matchar en viss iterabel av värdenvalues
.Om
transform
anges, jämförsvalues
med en lista som framställs genom atttransform
tillämpas på varje medlem iqs
.Som standard är jämförelsen också beroende av ordning. Om
qs
inte tillhandahåller en implicit ordning kan du ställa in parameternordered
tillFalse
, vilket gör jämförelsen till encollections.Counter
jämförelse. Om ordningen är odefinierad (om den givnaqs
inte är ordnad och jämförelsen är mot mer än ett ordnat värde), kommer ettValueError
att uppstå.Utdata i händelse av fel kan anpassas med argumentet
msg
.
- TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)[source]¶
Säger att när
func
anropas med*args
och**kwargs
så körsnum
databasfrågor.Om en
"using"
-nyckel finns ikwargs
används den som databasalias för att kontrollera antalet frågor:self.assertNumQueries(7, my_function, using="non_default_db")
Om du vill anropa en funktion med en
using
parameter kan du göra det genom att omsluta anropet med enlambda
för att lägga till en extra parameter:self.assertNumQueries(7, lambda: my_function(using=7))
Du kan också använda detta som en kontexthanterare:
with self.assertNumQueries(2): Person.objects.create(name="Aaron") Person.objects.create(name="Daniel")
Taggning av tester¶
Du kan märka dina tester så att du enkelt kan köra en viss delmängd. Du kan till exempel märka snabba eller långsamma tester:
from django.test import tag
class SampleTestCase(TestCase):
@tag("fast")
def test_fast(self): ...
@tag("slow")
def test_slow(self): ...
@tag("slow", "core")
def test_slow_but_core(self): ...
Du kan också tagga en testfallsklass:
@tag("slow", "core")
class SampleTestCase(TestCase): ...
Underklasser ärver taggar från överklasser och metoder ärver taggar från sin klass. Givet:
@tag("foo")
class SampleTestCaseChild(SampleTestCase):
@tag("bar")
def test(self): ...
SampleTestCaseChild.test
kommer att märkas med 'slow'
, 'core'
, 'bar'
och 'foo'
.
Sedan kan du välja vilka tester som ska köras. Till exempel om du bara vill köra snabba tester:
$ ./manage.py test --tag=fast
...\> manage.py test --tag=fast
Eller för att köra snabba tester och kärntesten (även om den är långsam):
$ ./manage.py test --tag=fast --tag=core
...\> manage.py test --tag=fast --tag=core
Du kan också utesluta tester med hjälp av taggar. För att köra kärntester om de inte är långsamma:
$ ./manage.py test --tag=core --exclude-tag=slow
...\> manage.py test --tag=core --exclude-tag=slow
test --exclude-tag
har företräde framför test --tag
, så om ett test har två taggar och du väljer en av dem och utesluter den andra, kommer testet inte att köras.
Testning av asynkron kod¶
Om du bara vill testa utdata från dina asynkrona vyer kommer standardtestklienten att köra dem i sin egen asynkrona loop utan något extra arbete från din sida.
Men om du vill skriva helt asynkrona tester för ett Django-projekt måste du ta hänsyn till flera saker.
För det första måste dina tester vara async def
-metoder på testklassen (för att ge dem ett asynkront sammanhang). Django kommer automatiskt att upptäcka alla async def
-tester och linda in dem så att de körs i sin egen händelseslinga.
Om du testar från en asynkron funktion måste du också använda den asynkrona testklienten. Detta är tillgängligt som django.test.AsyncClient
, eller som self.async_client
på alla tester.
- class AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults)[source]¶
AsyncClient
har samma metoder och signaturer som den synkrona (normala) testklienten, med följande undantag:
Vid initialiseringen läggs godtyckliga nyckelordsargument i
defaults
till direkt i ASGI-scopet.Headers som skickas som
extra
nyckelordsargument bör inte ha prefixetHTTP_
som krävs av den synkrona klienten (seClient.get()
). Till exempel, så här ställer du in en HTTPAccept
header:>>> c = AsyncClient() >>> c.get("/customers/details/", {"name": "fred", "age": 7}, ACCEPT="application/json")
Argumentet query_params
har lagts till.
Med hjälp av AsyncClient
måste alla metoder som gör en begäran väntas:
async def test_my_thing(self):
response = await self.async_client.get("/some-url/")
self.assertEqual(response.status_code, 200)
Den asynkrona klienten kan också anropa synkrona vyer; den körs genom Djangos :doc:asynkrona sökväg </topics/async>`, som stöder båda. Varje vy som anropas via ``AsyncClient
kommer att få ett ASGIRequest
-objekt för sin request
snarare än WSGIRequest
som den normala klienten skapar.
Varning
Om du använder testdekoratorer måste de vara async-kompatibla för att säkerställa att de fungerar korrekt. Djangos inbyggda dekoratorer kommer att bete sig korrekt, men dekoratorer från tredje part kan verka som om de inte exekveras (de kommer att ”linda in” fel del av exekveringsflödet och inte ditt test).
Om du behöver använda dessa dekoratorer, bör du dekorera dina testmetoder med async_to_sync()
inom dem istället:
from asgiref.sync import async_to_sync
from django.test import TestCase
class MyTests(TestCase):
@mock.patch(...)
@async_to_sync
async def test_my_thing(self): ...
E-posttjänster¶
Om någon av dina Django-vyer skickar e-post med hjälp av Djangos e-postfunktionalitet, vill du förmodligen inte skicka e-post varje gång du kör ett test med den vyn. Av denna anledning omdirigerar Djangos testlöpare automatiskt all Django-skickad e-post till en dummy-utkorg. Detta gör att du kan testa alla aspekter av att skicka e-post - från antalet meddelanden som skickas till innehållet i varje meddelande - utan att faktiskt skicka meddelandena.
Testlöparen åstadkommer detta genom att transparent ersätta den normala e-postbackend med en testbackend. (Oroa dig inte - detta har ingen effekt på andra e-postavsändare utanför Django, till exempel din maskins e-postserver, om du kör en sådan)
- django.core.mail.outbox¶
Under testkörning sparas varje utgående e-postmeddelande i django.core.mail.outbox
. Detta är en lista över alla EmailMessage
-instanser som har skickats. Attributet outbox
är ett specialattribut som skapas endast när e-postbackend locmem
används. Det existerar normalt inte som en del av modulen django.core.mail
och du kan inte importera det direkt. Koden nedan visar hur man kommer åt detta attribut på rätt sätt.
Här är ett exempel på ett test som undersöker django.core.mail.outbox
för längd och innehåll:
from django.core import mail
from django.test import TestCase
class EmailTest(TestCase):
def test_send_email(self):
# Send message.
mail.send_mail(
"Subject here",
"Here is the message.",
"from@example.com",
["to@example.com"],
fail_silently=False,
)
# Test that one message has been sent.
self.assertEqual(len(mail.outbox), 1)
# Verify that the subject of the first message is correct.
self.assertEqual(mail.outbox[0].subject, "Subject here")
Som nämnts :ref:tidigare <emptying-test-outbox>
, töms testutkorgen i början av varje test i ett Django *TestCase
. För att tömma utkorgen manuellt, tilldela den tomma listan till mail.outbox
:
from django.core import mail
# Empty the test outbox
mail.outbox = []
Kommandon för hantering¶
Hanteringskommandon kan testas med funktionen call_command()
. Utdata kan omdirigeras till en StringIO
-instans:
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
class ClosepollTest(TestCase):
def test_command_output(self):
out = StringIO()
call_command("closepoll", poll_ids=[1], stdout=out)
self.assertIn('Successfully closed poll "1"', out.getvalue())
Skippa prov¶
Biblioteket unittest tillhandahåller dekoratorerna @skipIf
och @skipUnless
så att du kan hoppa över tester om du i förväg vet att dessa tester kommer att misslyckas under vissa förutsättningar.
Om ditt test till exempel kräver ett visst valfritt bibliotek för att lyckas, kan du dekorera testfallet med @skipIf
. Då kommer testköraren att rapportera att testet inte kördes och varför, istället för att misslyckas med testet eller utelämna testet helt och hållet.
För att komplettera dessa testöverhoppningsbeteenden tillhandahåller Django ytterligare två skip-dekoratorer. Istället för att testa en generisk boolean, kontrollerar dessa dekoratorer databasens kapacitet och hoppar över testet om databasen inte stöder en specifik namngiven funktion.
Dekoratörerna använder en strängidentifierare för att beskriva databasfunktioner. Denna sträng motsvarar attribut i klassen för databasanslutningsfunktioner. Se django.db.backends.base.features.BaseDatabaseFeatures class för en fullständig lista över databasfunktioner som kan användas som grund för att hoppa över tester.
Hoppa över det dekorerade testet eller TestCase
om alla de namngivna databasfunktionerna stöds.
Till exempel kommer följande test inte att utföras om databasen stöder transaktioner (t.ex. skulle det * inte * köras under PostgreSQL, men det skulle under MySQL med MyISAM-tabeller):
class MyTests(TestCase):
@skipIfDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass
Hoppa över det dekorerade testet eller TestCase
om någon av de namngivna databasfunktionerna inte stöds.
Till exempel kommer följande test endast att utföras om databasen stöder transaktioner (t.ex. skulle det köras under PostgreSQL, men * inte * under MySQL med MyISAM-tabeller):
class MyTests(TestCase):
@skipUnlessDBFeature("supports_transactions")
def test_transaction_behavior(self):
# ... conditional test code
pass