Django REST Framework, üblicherweise als DRF abgekürzt, ist das Standard-Toolkit zum Erstellen von APIs auf Basis von Django. Es bietet Ihnen Serializer, Viewsets, Router und eine Authentifizierungsschicht. Was es Ihnen ebenfalls bietet und was viele Entwickler zu wenig nutzen, ist eine solide Testschicht, die auf Djangos eigenem Test-Runner aufbaut.
Dieser Leitfaden zeigt, wie man eine DRF-API auf zwei Arten testet. Erstens: automatisierte Tests, geschrieben in Python unter Verwendung von DRFs APITestCase und APIClient, die ohne einen Live-Server laufen und Regressionen bei jedem Commit erkennen. Zweitens: das Überprüfen der laufenden Endpunkte mit einem API-Client, wodurch Sie das Verhalten erkunden und den echten Dienst verifizieren können. Beide sind wichtig und fangen unterschiedliche Probleme ab.
Projekt und Testumgebung einrichten
Beginnen Sie mit einer isolierten Umgebung, damit Testabhängigkeiten vom Rest Ihres Systems getrennt bleiben:
python -m venv venv
source venv/bin/activate
pip install django djangorestframework coverage
Ein DRF-Projekt wird wie jedes Django-Projekt getestet. Konventionsgemäß befinden sich Tests in einer tests.py-Datei innerhalb jeder App oder in einem tests/-Paket, falls Sie viele haben. Djangos Test-Runner entdeckt jede Klasse, die eine TestCase-Variante unterklassifiziert, und jede Methode, deren Name mit test_ beginnt.
Die entscheidende Wahl ist, welche Basisklasse verwendet werden soll. Ein einfaches unittest.TestCase hat keine Datenbank. Djangos TestCase umhüllt jeden Test in einer Transaktion und rollt ihn zurück, sodass Tests sich gegenseitig nicht beeinflussen. DRFs APITestCase erweitert Djangos TestCase und tauscht den Client gegen DRFs APIClient aus, der DRF-Authentifizierung und Inhaltstypen versteht. Für API-Arbeiten verwenden Sie APITestCase.
Eine weitere Klasse ist es wert, bekannt zu sein. TransactionTestCase umhüllt Tests nicht in einer Transaktion, was Sie benötigen, wenn der zu testende Code Transaktionen selbst verwaltet oder auf Datenbankfunktionen angewiesen ist, die eine umhüllende Transaktion verbergen würde. Sie ist langsamer, da sie Tabellen zwischen den Tests leert, anstatt Transaktionen zurückzurollen. Greifen Sie daher nur darauf zurück, wenn TestCase das Verhalten wirklich nicht modellieren kann. Für die große Mehrheit der Endpunkt-Tests ist APITestCase die richtige und schnellste Wahl.
Es hilft auch, frühzeitig über Testdaten nachzudenken. Verwenden Sie eine setUp-Methode, um die Zeilen zu erstellen, die jeder Test in einer Klasse benötigt, oder setUpTestData, wenn die Daten schreibgeschützt sind und zur Beschleunigung klassenweit geteilt werden können. Für größere Projekte generiert eine Factory-Bibliothek wie factory_boy gültige Modellinstanzen, ohne dass Sie jedes Feld explizit angeben müssen, was Tests kurz und widerstandsfähig hält, wenn ein Modell ein neues Pflichtfeld erhält.
Serializer als Einheiten testen
Serializer führen Validierungen durch und konvertieren zwischen Modellinstanzen und JSON. Sie sind klein und rein, was sie ideal für schnelle Unit-Tests macht, die keine Endpunkte berühren.
Angenommen, Sie haben ein Article-Modell und einen ArticleSerializer. Ein Unit-Test prüft, ob gültige Daten akzeptiert und ungültige Daten abgelehnt werden:
from django.test import TestCase
from articles.serializers import ArticleSerializer
class ArticleSerializerTests(TestCase):
def test_valid_data_passes(self):
data = {"title": "Caching strategies", "body": "Use ETags."}
serializer = ArticleSerializer(data=data)
self.assertTrue(serializer.is_valid())
def test_missing_title_fails(self):
data = {"body": "No title here."}
serializer = ArticleSerializer(data=data)
self.assertFalse(serializer.is_valid())
self.assertIn("title", serializer.errors)
Diese Tests laufen in Millisekunden ab, da es kein HTTP und keine View gibt. Sie zeigen Ihnen, dass die Validierungsregeln korrekt sind, noch bevor Sie den Endpunkt überhaupt in Betracht ziehen. Diese Abdeckung auf Unit-Ebene ist die Basis der Testpyramide, beschrieben unter was automatisiertes Testen ist.
Endpunkte mit APITestCase und APIClient testen
Endpunkt-Tests überprüfen den vollständigen Pfad: Routing, die View, den Serializer und die Antwort. DRFs APIClient sendet Anfragen In-Process, sodass kein Server läuft und Tests schnell bleiben.
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from articles.models import Article
class ArticleEndpointTests(APITestCase):
def setUp(self):
Article.objects.create(title="First post", body="Hello world")
def test_list_articles_returns_200(self):
url = reverse("article-list")
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
def test_create_article(self):
url = reverse("article-list")
payload = {"title": "Second post", "body": "More content"}
response = self.client.post(url, payload, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(Article.objects.count(), 2)
Einige Details sind erwähnenswert. self.client ist eine APIClient-Instanz, die von APITestCase bereitgestellt wird. Verwenden Sie reverse() mit dem Routennamen, anstatt URLs fest zu codieren, damit eine Routenänderung nicht jeden Test unterbricht. Übergeben Sie format="json" bei Schreibvorgängen, damit der Client die Payload korrekt serialisiert. Überprüfen Sie den Statuscode mit den benannten Konstanten von rest_framework.status, da diese klarer lesbar sind als reine Zahlen. Die Statuscodes selbst werden im Leitfaden zu welche HTTP-Statuscodes REST-APIs verwenden sollten behandelt.
Authentifizierung und Berechtigungen testen
Die meisten echten APIs schützen ihre Endpunkte. Sie benötigen Tests, die beweisen, dass nicht authentifizierte Anfragen abgelehnt werden und authentifizierte Anfragen mit den richtigen Berechtigungen erfolgreich sind.
APIClient bietet zwei Möglichkeiten, einen Test zu authentifizieren. force_authenticate() überspringt die Anmeldeinformationenprüfung und fügt einen Benutzer direkt hinzu, was ideal ist, um die View-Logik selbst zu testen. login() oder credentials() durchlaufen den echten Authentifizierungspfad. Hier ist ein Berechtigungstest, der beide Ansätze verwendet:
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.test import APITestCase
from django.urls import reverse
class ArticlePermissionTests(APITestCase):
def setUp(self):
self.user = User.objects.create_user(
username="editor", password="testpass123"
)
self.url = reverse("article-list")
def test_anonymous_cannot_create(self):
payload = {"title": "Blocked", "body": "Should fail"}
response = self.client.post(self.url, payload, format="json")
self.assertEqual(
response.status_code, status.HTTP_403_FORBIDDEN
)
def test_authenticated_user_can_create(self):
self.client.force_authenticate(user=self.user)
payload = {"title": "Allowed", "body": "Should pass"}
response = self.client.post(self.url, payload, format="json")
self.assertEqual(
response.status_code, status.HTTP_201_CREATED
)
Der erste Test bestätigt, dass die Berechtigungsklasse anonyme Schreibvorgänge blockiert. Der zweite bestätigt, dass ein authentifizierter Benutzer zugelassen wird. Zusammen legen sie die Berechtigungsgrenze fest, was genau die Art von Verhalten ist, die bei Refactorings leise kaputtgeht. Wenn Sie speziell Token-Authentifizierung testen, ersetzen Sie force_authenticate durch self.client.credentials(HTTP_AUTHORIZATION="Token " + token), um den echten Header-Pfad zu testen.
Es lohnt sich, bewusst zu entscheiden, welche Methode man verwendet. force_authenticate ist schneller und isoliert die View-Logik, sodass es für die meisten Berechtigungstests geeignet ist, bei denen es Ihnen nur darum geht, ob eine Rolle etwas tun kann oder nicht. Der echte Anmeldeinformationspfad ist das, was Sie für eine kleinere Reihe von Tests wünschen, die speziell beweisen, dass der Authentifizierungsmechanismus funktioniert: dass ein ungültiges Token abgelehnt wird, dass ein abgelaufenes Token fehlschlägt, dass der Login-Endpunkt ein nutzbares Token ausgibt. Eine Mischung aus beidem bietet Ihnen eine breite Abdeckung kostengünstig und eine tiefe Abdeckung dort, wo es darauf ankommt.
Vergessen Sie nicht Berechtigungen auf Objektebene. Viele DRF-APIs erlauben es einem Benutzer, eigene Datensätze zu bearbeiten, aber nicht die eines anderen. Testen Sie dies explizit: Erstellen Sie zwei Benutzer, lassen Sie einen einen Datensatz erstellen und stellen Sie dann sicher, dass der andere Benutzer einen 403 oder 404 erhält, wenn er versucht, diesen zu ändern. Listenendpunkte verdienen die gleiche Prüfung, da ein undichter Queryset Zeilen zurückgeben kann, die ein Benutzer niemals sehen sollte, selbst wenn der Detail-Endpunkt gesperrt ist.
Suite ausführen und Abdeckung messen
Führen Sie jeden Test mit Djangos Runner aus:
python manage.py test
Der Runner entdeckt Ihre Testklassen, richtet eine temporäre Testdatenbank ein, führt jeden Test innerhalb einer Transaktion aus und rollt danach zurück. Ein sauberer Durchlauf gibt OK aus. Ein Fehler zeigt die fehlgeschlagene Assertion und deren Ort an.
Zu wissen, dass die Suite bestanden wird, ist nicht dasselbe wie zu wissen, dass sie ausreichend abdeckt. Das coverage-Tool misst, welche Zeilen tatsächlich ausgeführt wurden:
coverage run --source='.' manage.py test
coverage report
coverage html
Der Bericht listet jede Datei mit dem Prozentsatz der ausgeführten Zeilen auf. Die HTML-Ausgabe hebt ungetestete Zeilen rot hervor, was Sie direkt auf Lücken aufmerksam macht. Streben Sie eine sinnvolle Abdeckung von Views, Serializern und Berechtigungen an, anstatt einer einzelnen Schlagzeilennummer. Der Beitrag über wie man automatisierte Testskripte schreibt behandelt, was einen Test wertvoll macht, da die Abdeckung schwacher Tests keine echte Sicherheit ist.
Die Live-API mit einem Client testen
Automatisierte Python-Tests sind schnell und laufen in CI, aber sie testen die Anwendung In-Process. Sie fangen keine Probleme ab, die nur gegen den laufenden Dienst auftreten: ein falsch konfigurierter CORS-Header, ein Reverse-Proxy, der etwas entfernt, eine langsame Datenbank unter realer Last oder ein Unterschied zwischen Ihrer Testdatenbank und der Produktion. Dafür senden Sie echte Anfragen an die bereitgestellten Endpunkte.
Apidog passt hier hervorragend. Es ist eine All-in-One-API-Plattform, sodass Sie das OpenAPI-Schema Ihrer DRF-API importieren, Live-Anfragen an einen laufenden Server senden und visuell Assertionen erstellen können, ohne weiteres Python zu schreiben. DRF kann ein OpenAPI-Schema generieren, und Apidog konsumiert es direkt, was Ihren Client mit dem tatsächlichen Vertrag synchron hält. Sie können auch mehrstufige Testszenarien erstellen, z. B. sich anmelden, einen Artikel erstellen, ihn abrufen, ihn löschen und diese nach einem Zeitplan oder in CI ausführen. Dies ergänzt Ihre APITestCase-Suite, anstatt sie zu ersetzen: Unit- und Endpunkt-Tests schützen den Code, der API-Client schützt den bereitgestellten Dienst. Sie können Apidog herunterladen, um ein DRF-Schema zu importieren und auszuprobieren. Für Teams, die es vorziehen, Python durchgängig zu verwenden, zeigt der Leitfaden zum pytest API automated testing framework, wie DRF-artige Tests unter pytest ausgeführt werden.
Häufig gestellte Fragen
Was ist der Unterschied zwischen TestCase und APITestCase?
Djangos TestCase umhüllt jeden Test in einer Datenbanktransaktion und bietet Ihnen einen standardmäßigen Django-Testclient. DRFs APITestCase unterklassifiziert diesen und tauscht den Client gegen APIClient aus, der DRF-Authentifizierungsschemata, Content Negotiation und das format-Argument bei Anfragen versteht. Verwenden Sie APITestCase zum Testen von DRF-Endpunkten.
Wann sollte ich force_authenticate anstelle von login verwenden?
Verwenden Sie force_authenticate(), wenn Sie die View- und Berechtigungslogik testen möchten, ohne die Kosten und die Komplexität des echten Anmeldeinformationsflusses. Es fügt einen Benutzer direkt der Anfrage hinzu. Verwenden Sie login() oder credentials(), wenn der Authentifizierungsmechanismus selbst, wie z. B. Session- oder Token-Authentifizierung, das ist, was Sie überprüfen möchten.
Benötigen DRF-Tests einen laufenden Server?
Nein. APIClient sendet Anfragen In-Process direkt an Ihre Views, sodass die Test-Suite ohne das Starten eines Servers ausgeführt wird. Das macht sie schnell. Um den tatsächlich bereitgestellten Dienst, einschließlich Infrastruktur wie Proxys und CORS, zu testen, senden Sie echte HTTP-Anfragen mit einem API-Client wie Apidog.
Wie überprüfe ich die Testabdeckung für ein DRF-Projekt?
Installieren Sie das coverage-Paket und führen Sie dann coverage run --source='.' manage.py test aus, gefolgt von coverage report für eine Zusammenfassung oder coverage html für eine zeilenweise Ansicht. Der HTML-Bericht hebt ungetestete Zeilen hervor, sodass Sie genau sehen können, welchen Views oder Serializern Tests fehlen.
Sollte ich Serializer und Endpunkte separat testen?
Ja. Serializer-Unit-Tests sind schnell und identifizieren Validierungsfehler ohne HTTP-Overhead. Endpunkt-Tests mit APITestCase überprüfen das Routing, Berechtigungen und den vollständigen Anfragezyklus. Beides zusammen bietet Ihnen schnelles Feedback auf Unit-Ebene und die Gewissheit, dass die Verkabelung auf Integrationsebene funktioniert.
