Python Coverage reporta 100% de cobertura em uma Class Based View não testada!
Eu estava tentando ver a cobertura de testes para um arquivo que tinha múltuplas Class Based Views (CBV). Apesar de não haver nenhum teste disponível, o relatório reportava que o meu arquivo estava todo coberto por tests! Eu realmente não consegui entender o que estava acontecendo e eu demorei dias até finalmente ter alguma resposta! Esse texto deixa a resposta um pouco mais acessível pras próximas pessoas que passarem por isso 😊
Testando uma Class Based View
Tudo começou quando eu tinha um arquivo views
que tinha algumas views bem básicas: uma página de boas vindas (um template apenas), páginas de login e logout e uma página pra criar uma conta (signup). A view
do Signup era a única que tinha um método sendo sobrescrito pela minha view
: o método get
. O método apenas redirecionaca o usuário para uma página interna caso já estivesse autenticado.
# home/views.py
from django.views.generic import TemplateView
from django.views.generic.edit import CreateView
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import redirect
class SignupView(CreateView):
form_class = UserCreationForm
template_name = 'home/register.html'
success_url = '/smart/notes'
def get(self, request, *args, **kwargs):
# redireciona o usuário para uma página interna se autenticado
if self.request.user.is_authenticated:
return redirect('notes.list')
return super().get(request, *args, **kwargs)
class LogoutInterfaceView(LogoutView):
template_name = 'home/logout.html'
class LoginInterfaceView(LoginView):
template_name = 'home/login.html'
class HomeView(TemplateView):
template_name = 'home/welcome.html'
extra_context = {'today': datetime.today()}
Eu queria testar essas views então eu criei o teste mais básico possível: checava que a página de boas vindas retornava 200 (sucesso) e que o template retornado era o correto:
# home/tests/test_home_views.py
def test_home_endpoint_returns_welcome_page(client):
response = client.get(path='/')
assert response.status_code == 200
assert 'home/welcome.html' in response.template_name
Cobertura de testes
Antes de continuar adicionando testesd, eu decidi dar uma olhada na cobertura, pra planejar minha estratégia de testes. Eu instalei a biblioteca coverage e rodei os seguintes comandos:
$ coverage run -m pytest
$ coverage html
$ open htmlcov/index.html
O último comando vai abrir um nevegador onde eu posso ver o relatório completo por arquivo:
O problema
Esse relatório foi uma completa surpresa pra mim! Eu só tinha testado uma view das múltiplas views que eu tinha no arquivo. Como que o relatório chegou a um total de 88% do arquivo sendo coberto por testes?
Eu abri o arquivo do relatório do arquivo que eu havia testado, e ele me mostrava que quase todas as Class Based Views estavam sendo testadas, com a única exceção sendo o código que eu havia sobreescrito no SignupView
:
De novo, fiquei extremamente surpresa! Eu apenas testei a HomeView
, porque a LoginInterfaceView
, LogoutInterfaceView
e SignupView
estavam sendo marcadas como testadas?
Entendendo o que aconteceu
Bem… depois de dias buscando uma resposta eu encontrei essa resposta:
Coverage.py apenas te mostra quais linhas de código foram executadas.
E um segundo comentário na mesma conversa abre ainda mais os olhos:
Durante os testes, Django tem que carregar todas as classes e módulos em memória então o programa (suas classes e configurações) são executadas.
O que está acontecendo aqui, é que CBVs são classes, quando você executa os testes, o Django vai carregá-las na memória, o que significa que elas serão executadas assim que o teste começa a rodar. Quando eu rodei o coverage
, ele procurou quais partes do código foram executadas. Como as classes foram pra memória (foram executadas) elas foram mostradas como testadas.
O método get
que eu substituí não é executado enquanto os testes são executados, e por isso ele foi marcado como não-testado!
Eu continuo adorando as Class Based Views, mas você deve ter cuidado ao usar a biblioteca coverage
para verificar se você as testou o suficiente!