Django REST Framework, geralmente abreviado para DRF, é o kit de ferramentas padrão para construir APIs sobre Django. Ele oferece serializers, viewsets, routers e uma camada de autenticação. O que ele também oferece, e que muitos desenvolvedores subutilizam, é uma sólida camada de testes que se baseia no próprio test runner do Django.
Este guia mostra como testar uma API DRF de duas maneiras. Primeiro, testes automatizados escritos em Python usando APITestCase e APIClient do DRF, que são executados sem um servidor ativo e capturam regressões a cada commit. Segundo, exercitando os endpoints em execução com um cliente de API, que é como você explora o comportamento e verifica o serviço real. Ambos são importantes e detectam problemas diferentes.
Configurar o projeto e o ambiente de teste
Comece com um ambiente isolado para que as dependências de teste permaneçam separadas do resto do seu sistema:
python -m venv venv
source venv/bin/activate
pip install django djangorestframework coverage
Um projeto DRF é testado como qualquer projeto Django. Por convenção, os testes residem em um arquivo tests.py dentro de cada app, ou em um pacote tests/ se você tiver muitos. O test runner do Django descobre qualquer classe que herde de uma variante de TestCase e qualquer método cujo nome comece com test_.
A escolha principal é qual classe base usar. Um `unittest.TestCase` simples não tem banco de dados. O TestCase do Django encapsula cada teste em uma transação e a reverte, para que os testes não interfiram uns nos outros. O APITestCase do DRF estende o TestCase do Django e substitui o cliente pelo APIClient do DRF, que entende a autenticação e os tipos de conteúdo do DRF. Para trabalho com API, use APITestCase.
Mais uma classe vale a pena conhecer. O TransactionTestCase não encapsula testes em uma transação, o que é necessário quando o código em teste gerencia transações por si mesmo ou depende de recursos de banco de dados que uma transação de encapsulamento ocultaria. É mais lento porque trunca tabelas entre os testes em vez de reverter, então use-o apenas quando o TestCase genuinamente não conseguir modelar o comportamento. Para a grande maioria dos testes de endpoint, APITestCase é a escolha certa e mais rápida.
Também é útil pensar nos dados de teste desde cedo. Use um método setUp para criar as linhas que cada teste de uma classe precisa, ou setUpTestData se os dados forem somente leitura e puderem ser compartilhados entre a classe para maior velocidade. Para projetos maiores, uma biblioteca de fábrica como factory_boy gera instâncias de modelo válidas sem que você precise especificar cada campo, o que mantém os testes curtos e resilientes quando um modelo ganha um novo campo obrigatório.
Testar serializers como unidades
Serializers realizam validação e convertem entre instâncias de modelo e JSON. Eles são pequenos e puros, o que os torna ideais para testes de unidade rápidos que não tocam nos endpoints.
Suponha que você tenha um modelo Article e um ArticleSerializer. Um teste de unidade verifica se dados válidos passam e dados inválidos falham:
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)
Esses testes são executados em milissegundos porque não há HTTP e nenhuma view. Eles informam que as regras de validação estão corretas antes mesmo de você considerar o endpoint. Essa cobertura em nível de unidade é a base da pirâmide de testes descrita em o que é teste automatizado.
Testar endpoints com APITestCase e APIClient
Testes de endpoint verificam o caminho completo: roteamento, a view, o serializer e a resposta. O APIClient do DRF envia requisições em processo, então nenhum servidor é executado e os testes permanecem rápidos.
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)
Alguns detalhes que valem a pena notar. self.client é uma instância de APIClient fornecida por APITestCase. Use reverse() com o nome da rota em vez de URLs codificadas, para que uma mudança de rota não quebre todos os testes. Passe format="json" nas escritas para que o cliente serialize o payload corretamente. Faça asserções sobre o código de status com as constantes nomeadas de rest_framework.status, pois elas são mais claras do que números brutos. Os próprios códigos de status são abordados no guia sobre quais códigos de status HTTP as APIs REST devem usar.
Testar autenticação e permissões
A maioria das APIs reais protege seus endpoints. Você precisa de testes que comprovem que requisições não autenticadas são rejeitadas e requisições autenticadas com as permissões corretas são bem-sucedidas.
O APIClient oferece duas maneiras de autenticar um teste. force_authenticate() ignora a verificação de credenciais e anexa um usuário diretamente, o que é ideal para testar a própria lógica da view. login() ou credentials() exercitam o caminho de autenticação real. Aqui está um teste de permissão usando ambas as direções:
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
)
O primeiro teste confirma que a classe de permissão bloqueia escritas anônimas. O segundo confirma que um usuário autenticado consegue passar. Juntos, eles fixam o limite de permissão, que é exatamente o tipo de comportamento que quebra silenciosamente durante refatorações. Se você estiver testando autenticação por token especificamente, troque force_authenticate por self.client.credentials(HTTP_AUTHORIZATION="Token " + token) para exercitar o caminho real do cabeçalho.
Vale a pena ser intencional sobre qual usar. force_authenticate é mais rápido e isola a lógica da view, então é adequado para a maioria dos testes de permissão onde você só se preocupa se uma função pode ou não fazer algo. O caminho real de credenciais é o que você quer para um conjunto menor de testes que provam especificamente que o mecanismo de autenticação funciona: que um token inválido é rejeitado, que um token expirado falha, que o endpoint de login emite um token utilizável. Misturar ambos oferece uma cobertura ampla de forma barata e uma cobertura profunda onde realmente importa.
Não se esqueça das permissões em nível de objeto. Muitas APIs DRF permitem que um usuário edite seus próprios registros, mas não os de outra pessoa. Teste isso explicitamente: crie dois usuários, faça um criar um registro e, em seguida, verifique se o outro usuário recebe um 403 ou 404 ao tentar modificá-lo. Os endpoints de listagem merecem o mesmo escrutínio, pois um queryset com vazamento pode retornar linhas que um usuário nunca deveria ver, mesmo quando o endpoint de detalhe está bloqueado.
Executar a suíte e medir a cobertura
Execute todos os testes com o runner do Django:
python manage.py test
O runner descobre suas classes de teste, configura um banco de dados de teste descartável, executa cada teste dentro de uma transação e reverte em seguida. Uma execução limpa imprime OK. Uma falha mostra a asserção que quebrou e onde.
Saber que a suíte passa não é o mesmo que saber que ela cobre o suficiente. A ferramenta coverage mede quais linhas realmente foram executadas:
coverage run --source='.' manage.py test
coverage report
coverage html
O relatório lista cada arquivo com a porcentagem de linhas exercitadas. A saída HTML destaca as linhas não testadas em vermelho, o que aponta diretamente para as lacunas. Busque uma cobertura significativa de views, serializers e permissões, em vez de um único número principal. O artigo sobre como escrever scripts de teste automatizados aborda o que faz um teste valer a pena ser mantido, já que a cobertura de testes fracos não é segurança real.
Testar a API ao vivo com um cliente
Testes automatizados em Python são rápidos e executados na CI, mas eles exercitam o aplicativo em processo. Eles não detectam problemas que só aparecem contra o serviço em execução: um cabeçalho CORS mal configurado, um proxy reverso removendo algo, um banco de dados lento sob carga real, ou uma diferença entre o seu banco de dados de teste e a produção. Para isso, você envia requisições reais para os endpoints implantados.
Apidog se encaixa perfeitamente aqui. É uma plataforma de API tudo-em-um, então você pode importar o esquema OpenAPI da sua API DRF, enviar requisições ao vivo para um servidor em execução e construir asserções visualmente sem escrever mais Python. O DRF pode gerar um esquema OpenAPI, e o Apidog o consome diretamente, o que mantém seu cliente sincronizado com o contrato real. Você também pode construir cenários de teste de várias etapas, por exemplo, fazer login, criar um artigo, obtê-lo, excluí-lo, e executá-los em um cronograma ou na CI. Isso complementa sua suíte APITestCase em vez de substituí-la: testes de unidade e de endpoint protegem o código, o cliente de API protege o serviço implantado. Você pode baixar o Apidog para importar um esquema DRF e experimentá-lo. Para equipes que preferem permanecer em Python de ponta a ponta, o guia do framework de teste automatizado de API pytest mostra como executar testes no estilo DRF sob pytest.
Perguntas frequentes
Qual a diferença entre TestCase e APITestCase?
O TestCase do Django encapsula cada teste em uma transação de banco de dados e oferece um cliente de teste Django padrão. O APITestCase do DRF o estende e troca o cliente pelo APIClient, que entende os esquemas de autenticação do DRF, negociação de conteúdo e o argumento format nas requisições. Use APITestCase para testar endpoints do DRF.
Quando devo usar force_authenticate em vez de login?
Use force_authenticate() quando quiser testar a lógica da view e das permissões sem o custo e a complexidade do fluxo real de credenciais. Ele anexa um usuário diretamente à requisição. Use login() ou credentials() quando o mecanismo de autenticação em si, como sessão ou autenticação por token, é o que você deseja verificar.
Os testes DRF precisam de um servidor em execução?
Não. O APIClient despacha requisições em processo diretamente para suas views, então a suíte de testes é executada sem iniciar um servidor. É isso que a torna rápida. Para testar o serviço realmente implantado, incluindo infraestruturas como proxies e CORS, você envia requisições HTTP reais com um cliente de API como o Apidog.
Como verifico a cobertura de testes para um projeto DRF?
Instale o pacote coverage e, em seguida, execute coverage run --source='.' manage.py test seguido de coverage report para um resumo ou coverage html para uma visualização linha a linha. O relatório HTML destaca as linhas não testadas para que você possa ver exatamente quais views ou serializers não possuem testes.
Devo testar serializers e endpoints separadamente?
Sim. Os testes de unidade de serializer são rápidos e detectam bugs de validação sem a sobrecarga HTTP. Os testes de endpoint com APITestCase verificam o roteamento, as permissões e o ciclo completo da requisição. Manter ambos oferece feedback rápido no nível da unidade e confiança de que a interligação funciona no nível de integração.
