Pytest
Sobre
Framework para realizar testes unitários compreendendo testes de:
1 - models.py
2 - forms.py
3 - views.py
Na raiz do projeto existe o arquivo pytest.ini
contendo a configuração necessário para que o framework identifique
os testes no projeto Django.
Executar
pytest
Relatório de cobertura dos testes
----------- coverage: platform win32, python 3.8.6-final-0 -----------
Name Stmts Miss Cover
--------------------------------------------------------------------
__init__.py 0 0 100%
usuario\__init__.py 1 0 100%
usuario\admin.py 1 0 100%
usuario\apps.py 4 0 100%
usuario\models.py 43 14 67%
usuario\tests\__init__.py 0 0 100%
usuario\tests\tests.py 13 0 100%
--------------------------------------------------------------------
TOTAL 80 14 82%
FAIL Required test coverage of 90% not reached. Total coverage: 82.50%
Gerar a base dos testes de uma app específica
Para agilizar a criação dos testes, foi criado um comando para gerar a base dos testes de uma app específica. Este comando
irá gerar os arquivos tests_forms.py
, tests_models.py
e tests_views.py
separando-os por pastas de cada modelo
que ficará localizada dentro da pasta tests
da app.
python manage.py build nome_da_app --tests
Exemplo de testes contidos na app usuario
Para facilitar a replicação dos testes para as apps que você desenvolver, foram criados os arquivos de
testes tests_forms.py
, tests_models.py
e tests_views.py
na app
usuario
contendo os testes para os models, forms e views.
Exemplo de teste de model
import pytest
from django.core import mail
from faker import Faker
from model_bakery import baker
from usuario.models import Usuario
class TestUsuarioModels:
"""Teste básicos para o model Usuario.
Nos campos CPF geramos um valor fictício para teste 24935340002
no site https://www.4devs.com.br/gerador_de_cpf
."""
@pytest.fixture
def init(self, db):
self.faker = Faker("pt_BR")
"""Fixture para inicializar os testes utilizando baker"""
self.usuario = baker.make(
Usuario, cpf="24935340002", email="usuario@email.com.br"
)
"""baker cria um objeto do model Usuario com os campos obrigatórios. Se necessario passar algum campo opcional,
basta passar como parametro no baker.make"""
def test_count_user(self, init):
"""Teste para verificar se o usuario se foi criado apenas um usuario"""
assert Usuario.objects.all().count() == 1
def test_soft_delete_user(self, init):
"""Teste para verificar se o usuario foi deletado"""
Usuario.objects.all().delete()
assert Usuario.objects.filter(deleted=False).count() == 0
@pytest.mark.skip
def test_hard_delete_user(self, init):
assert self.usuario.delete() is True
def test_create_user(self, init):
"""Teste para verificar se o usuario foi criado com sucesso"""
assert self.usuario.id is not None
def test_save_user_method(self, init):
"""Teste para verificar se o usuario foi criado com sucesso, e verificação se o count de usuarios é 2"""
usuario = Usuario(
cpf="24935340002",
nome=self.faker.name(),
email=self.faker.company_email(),
)
usuario.save()
count = Usuario.objects.all().count()
assert count == 2
def test_update_user(self, init):
"""Teste para verificar se o usuario foi atualizado com sucesso"""
self.usuario.nome = "Maria"
self.usuario.save()
usuario_email = Usuario.objects.get(nome="Maria")
assert usuario_email.nome == "Maria"
def test_user_str(self, init):
"""Teste para verificar a passagem de parametros para o metodo __str__"""
assert (
str(self.usuario) == f"Usuario: {self.usuario.cpf} | {self.usuario.email}"
)
def test_send_email_account_created(self, init):
mail.send_mail(
"Teste campo subject do email",
"Teste campo corpo do email",
"from@yourdjangoapp.com",
[self.usuario.email],
fail_silently=False,
)
assert len(mail.outbox) == 1
assert mail.outbox[0].subject == "Teste campo subject do email"
assert mail.outbox[0].body == "Teste campo corpo do email"
assert mail.outbox[0].to[0] == self.usuario.email
Exemplo de teste de forms
import pytest
from faker import Faker
from usuario.forms import UsuarioForm
from usuario.models import Usuario
class TestUsuarioForms:
"""Testes básicos para o form Usuario."""
@pytest.fixture
def init(self, db):
self.faker = Faker("pt_BR")
self.valid_data = {
"cpf": "24935340002",
"nome": self.faker.name(),
"email": self.faker.company_email(),
"password": "123456789",
"password2": "123456789",
}
self.invalid_data = {
"nome": self.faker.name(),
"email": self.faker.company_email(),
}
def test_usuario_create(self, init):
"""Teste para verificar se o form de criação de usuário é válido.
Passando apenas os campos necessários para a criação de um usuário."""
form = UsuarioForm(data=self.valid_data)
assert form.is_valid() is True
def test_usuario_form_invalid(self, init):
"""Teste para verificar se o form de criação de usuário é inválido"""
form = UsuarioForm(data=self.invalid_data)
assert form.is_valid() is False
def test_usuario_form_save(self, init):
"""Teste que verifica se o form de criação de usuário salva no banco de dados"""
form = UsuarioForm(data=self.valid_data)
form.save()
assert Usuario.objects.all().count() == 1
Exemplo de teste de views
import pytest
from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.contrib.messages.storage.fallback import FallbackStorage
from django.test import RequestFactory
from django.urls import reverse
from faker import Faker
from model_bakery import baker
from usuario.models import Usuario
from usuario.views import (
UsuarioCreateView,
UsuarioDeleteView,
UsuarioDetailView,
UsuarioIndexTemplateView,
UsuarioListView,
UsuarioUpdateView,
)
class TestUsuarioViews:
"""Testes básicos para a views Usuario."""
@pytest.fixture
def init(self, db):
self.faker = Faker("pt_BR")
self.user = User.objects.create_superuser(
username="teste", email="teste@email.com.br", password="senha_padrao_deve_ser_mudada"
)
baker.make(
Usuario,
django_user=self.user,
cpf="24935340002",
email="usuario@email.com.br",
)
baker.make(Usuario, cpf="24935340002", email="usuario2@email.com.br")
self.factory = RequestFactory()
self.usuario = Usuario.objects.all().first()
"""" Parametros para testar permissões de usuário"""
self.contenttype = ContentType.objects.get_for_model(Usuario)
self.django_usuario_dois = User.objects.create_user(
username="usuariodois@email.com",
email="usuariodois@email.com",
is_staff=True,
is_active=True,
)
self.usuario_permissao = Permission.objects.get(
codename="add_usuario", content_type=self.contenttype
)
self.django_usuario_dois.user_permissions.add(
self.usuario_permissao,
)
def test_usuario_index(self, init):
"""Testa se o template index está sendo renderizado corretamente"""
url = reverse("usuario:usuario-index")
request = self.factory.get(url)
request.user = self.user
response = UsuarioIndexTemplateView.as_view()(request)
assert response.status_code == 200
def test_usuario_list(self, init):
"""Testa se o template list está sendo renderizado corretamente"""
url = reverse("usuario:usuario-list")
request = self.factory.get(url)
request.user = self.user
response = UsuarioListView.as_view()(request)
assert response.status_code == 200
def test_usuario_detail(self, init):
"""Testa se o template detail está sendo renderizado corretamente"""
url = reverse("usuario:usuario-detail", args={self.usuario.pk})
request = self.factory.get(url)
request.user = self.user
response = UsuarioDetailView.as_view()(request, pk=self.usuario.pk)
assert response.status_code == 200
def test_usuario_create(self, init):
"""Testa se o template create está sendo renderizado corretamente com metodo get"""
url = reverse("usuario:usuario-create")
request = self.factory.get(url)
request.user = self.user
response = UsuarioCreateView.as_view()(request)
assert response.status_code == 200
def test_usuario_usuario_create_post(self, init):
"""Testa se o template create está sendo renderizado corretamente com metodo post"""
data = {
"cpf": "24935340002",
"nome": self.faker.name(),
"email": self.faker.company_email(),
}
url = reverse("usuario:usuario-create")
request = self.factory.post(url)
request.user = self.user
response = UsuarioCreateView.as_view()(request, data=data)
assert response.status_code == 200
def test_usuario_update(self, init):
"""Testa se o template update está sendo renderizado corretamente com metodo put"""
url = reverse("usuario:usuario-update", args={self.usuario.pk})
request = self.factory.put(url)
request.user = self.user
response = UsuarioUpdateView.as_view()(request, pk=self.usuario.pk)
assert response.status_code == 200
def test_usuario_delete(self, init):
"""Testa a exclusão de um usuário utilizando o metodo delete"""
usuario = self.usuario
url = reverse("usuario:usuario-delete", args={usuario.pk})
request = self.factory.delete(url)
"""Adiciona a sessão e a mensagem de sucesso para a requisição"""
setattr(request, "session", "session")
messages = FallbackStorage(request)
setattr(request, "_messages", messages)
request.user = self.user
response = UsuarioDeleteView.as_view()(request, pk=usuario.pk)
assert response.status_code == 302
def test_usuario_list_queryset_superuser_status(self, init, client):
"""Retornar o status code 200 ao verificar itens cadastrados a partir do superuser logado"""
client.force_login(self.user)
request = self.factory.get(reverse("usuario:usuario-list"))
request.user = self.user
response = UsuarioListView.as_view()(request)
assert response.status_code == 200
def test_usuario_list_queryset_superuser(self, init, client):
"""Retornar a quantidade de itens cadastrados a partir do superuser logado"""
client.force_login(self.user)
request = self.factory.get(reverse("usuario:usuario-list"))
request.user = self.user
response = UsuarioListView.as_view()(request)
assert len(response.context_data["object_list"]) == 2
""" Testes para verificar o funcionamento das permissões de usuário.
seguindo os parametros definidos no fixture init que estão comentados"""
@pytest.mark.skip
def test_usuario_list_queryset_usuario_status(self, init, client):
"""Retornar o status code 200 ao verificar itens cadastrados a partir do usuario logado"""
client.force_login(self.django_usuario_dois)
request = self.factory.get(reverse("usuario:usuario-list"))
request.user = self.django_usuario_dois
response = UsuarioListView.as_view()(request)
assert response.status_code == 200
@pytest.mark.skip
def test_usuario_list_queryset_usuario(self, init, client):
"""Retornar a quantidade de itens cadastrados a partir do usuario logado"""
client.force_login(self.django_usuario_dois)
request = self.factory.get(reverse("usuario:usuario-list"))
request.user = self.django_usuario_dois
response = UsuarioListView.as_view()(request)
assert len(response.context_data["object_list"]) == 1
Verificar os modelos que ainda não foram testados
Para simplificar o processo de verificação, desenvolvemos um script que identifica os modelos que ainda
não foram testados. O script analisa os modelos que estão no arquivo models.py
das apps do projeto e compara-os
com os testes que foram criados nos arquivos test_models.py
, test_views.py
e test_forms.py
de cada app.
python manage.py check_tests
Arquivo de configuração
A linha addopts = --cov --cov-fail-under=90
especifica o percentual mínimo de cobertura aceito para que o projeto
passe no processo de CI
[pytest]
DJANGO_SETTINGS_MODULE = base.settings
python_files = tests.py test_*.py *_tests.py
addopts = --cov --cov-fail-under = 90
Links
Pip | Docs |
---|---|
Pip | Doc |