Série API em Flask - Parte 6 - Criando e testando nosso modelo de usuários
Oi pessoal, vamos criar nosso modelo de usuários e realizar alguns testes em cima dele. Graças ao pacote mongoengine
fica bem fácil de criar nossas classes referente ao modelo de dados e é bem parecido com o Django, porém sem as migrations.
Para entendimento, segue os capítulos que serão abordados.
Capítulo 1: Introdução, configuração e Hello World
Capítulo 2: Organizando as dependências e requerimentos
Capítulo 3: Configurando o pytest e nosso primeiro teste
Capítulo 4: Configurando o Makefile
Capítulo 5: Adicionando o MongoDB
Capítulo 6: Criando e testando o modelo de usuários Estamos aqui
Capítulo 7: Criando usuários
Capítulo 8: Listando usuários
Capítulo 9: Buscando usuários
Capítulo 10: Editando um usuário
Capítulo 11: Deletando um usuário
Capítulo 12: Autênticação por JWT
Capítulo 13: Criando um container Docker
Capítulo 14: Arquivos de configuração para Deploy na Digital Ocean
Capítulo 15: Automatizando o processo de deploy com Fabric
Capítulo 16: CI e CD com Jenkins, Python, Flask e Fabric
Capítulo 17: Utilizando o RabbitMQ com Flask e Sendgrid para enviar e-mails de boas vindas e ativar a conta do usuário
O repositório com todo o código fonte esta aqui. Os capítulos estão em branches
.
Model Users
Dentro de nossa pasta apps
vamos criar um novo diretório chamado users
e adicionar nosso módulo models.py
. Não se esqueçam de sempre criar o __init__.py
.
Ficando assim nossa estrutura:
$ tree .
.
├── api.py
├── db.py
├── __init__.py
└── users
├── __init__.py
└── models.py
Em nosso models.py
vamos primeiro criar uma classe abtrata chamada UserMixin
, apenas para titulo de curiosidade e saber que existe, em seguida criaremos nossa classe User
. Vamos ao código:
# Python
from datetime import datetime
# Third
from mongoengine import (
BooleanField,
DateTimeField,
DictField,
EmailField,
EmbeddedDocument,
EmbeddedDocumentField,
StringField,
URLField
)
# Apps
from apps.db import db
class Roles(EmbeddedDocument):
"""
Roles permissions
"""
admin = BooleanField(default=False)
class UserMixin(db.Document):
"""
Default implementation for User fields
"""
meta = {
'abstract': True,
'ordering': ['email']
}
email = EmailField(required=True, unique=True)
password = StringField(required=True)
roles = EmbeddedDocumentField(Roles, default=Roles)
created = DateTimeField(default=datetime.now)
active = BooleanField(default=False)
def is_active(self):
return self.active
def is_admin(self):
return self.roles.admin
# Abaixo fica o código para a classe Adress
Em complemento vamos criar uma classe Address
que será utilizada em nosso model Users
. Essa classe herda de EmbbededDocument
do mongoengine
e nada mais é de uma maneira simples colocar dicionários dentro de um campo, com campos fixos.
class Address(EmbeddedDocument):
"""
Default implementation for address fields
"""
meta = {
'ordering': ['zip_code']
}
zip_code = StringField(default='')
address = StringField(default='')
number = StringField(default='')
complement = StringField(default='')
neighborhood = StringField(default='')
city = StringField(default='')
city_id = StringField(default='')
state = StringField(default='')
country = StringField(default='BRA')
# Abaixo fica o código para a classe User
E por fim criaremos nossa classe User
a qual possui herança da nossa classe abstrata UserMixin
.
class User(UserMixin):
'''
Users
'''
meta = {'collection': 'users'}
full_name = StringField(required=True)
cpf_cnpj = StringField(default='')
address = EmbeddedDocumentField(Address, default=Address)
O que fizemos até aqui foi modelar nossa entidade usuário com os campos herdados da classe UserMixin
e alteramos o nome da nossa coleção através do meta = {'collection': 'users'}
.
É importante ler a documentação do mongoengine pois ela irá ajudar muito quando precisar de criar novos campos como uma lista de strings, tags = ListField(StringField(max_length=30))
e opções mais avançadas quando se trabalha com o MongoDB.
Testes
Neste artigo eu não quis fazer através de TDD pois iria ficar muito extenso. Logo nada impede que você e o time que está trabalhando faça o desenvolvimento utilizando o TDD e refatorando o sistema.
Antes de codificar os testes eu gostaria de reforçar que geralmente eu faço as seguintes validações em cima de modelos.
Verifico se o campo existe
Verifico se o campo é requerido
Verifico se o campo é unique
Verifico o tipo do campo: String, Int, Bool
Isso é o básico de um teste. Lógico que você pode aumentar a sua gama verificando tamanho da string, validadores, mensagens de erro e etc…
Agora vamos la. Na nossa pasta tests
, vamos criar um novo diretório users
e criar um módulo chamado test_models.py
. Ficando com essa estrutura:
.
├── conftest.py
├── home
│ ├── __init__.py
│ └── test_home.py
├── __init__.py
└── users
├── __init__.py
└── test_models.py
Abaixo segue o código auto explicativo nas docstrings:
# Third
from mongoengine import (
BooleanField,
StringField,
)
# Apps
from apps.users.models import User
class TestUser:
def setup_method(self):
self.data = {
'email': 'teste1@teste.com', 'password': 'teste123',
'active': True, 'full_name': 'Teste',
'cpf_cnpj': '11111111111'
}
# Crio uma instancia do modelo User
self.model = User(**self.data)
def test_email_field_exists(self):
"""
Verifico se o campo email existe
"""
assert 'email' in self.model._fields
def test_email_field_is_required(self):
"""
Verifico se o campo email é requirido
"""
assert self.model._fields['email'].required is True
def test_email_field_is_unique(self):
"""
Verifico se o campo email é unico
"""
assert self.model._fields['email'].unique is True
def test_email_field_is_str(self):
"""
Verifico se o campo email é do tipo string
"""
assert isinstance(self.model._fields['email'], StringField)
def test_active_field_exists(self):
assert 'active' in self.model._fields
def test_active_field_is_default_true(self):
assert self.model._fields['active'].default is False
def test_active_field_is_bool(self):
"""
Verifico se o campo active é booleano
"""
assert isinstance(self.model._fields['active'], BooleanField)
def test_full_name_field_exists(self):
"""
Verifico se o campo full_name existe
"""
assert 'full_name' in self.model._fields
def test_full_name_field_is_str(self):
assert isinstance(self.model._fields['full_name'], StringField)
def test_all_fields_in_model(self):
"""
Verifico se todos os campos estão de fato no meu modelo
"""
fields = [
'active', 'address', 'cpf_cnpj', 'created', 'email',
'full_name', 'id', 'password', 'roles'
]
model_keys = [i for i in self.model._fields.keys()]
fields.sort()
model_keys.sort()
assert fields == model_keys
Em seguida basta executarmos nosso comando de testes make test
:
$ make test
find . -name '*.pyc' -exec rm --force {} +
find . -name '*.pyo' -exec rm --force {} +
find . | grep -E "__pycache__|.pyc|.DS_Store$" | xargs rm -rf
flake8
pytest --verbose --color=yes
========================================================================== test session starts ==========================================================================
platform linux -- Python 3.6.5, pytest-3.6.1, py-1.5.3, pluggy-0.6.0 -- /workspaces/venvs/flask-api-users/bin/python3
cachedir: .pytest_cache
rootdir: /workspaces/python/flask-api-users, inifile: pytest.ini
plugins: flask-0.10.0, cov-2.5.1
collected 12 items
tests/home/test_home.py::test_index_response_200 PASSED [ 8%]
tests/home/test_home.py::test_home_response_hello PASSED [ 16%]
tests/users/test_models.py::TestUser::test_email_field_exists PASSED [ 25%]
tests/users/test_models.py::TestUser::test_email_field_is_required PASSED [ 33%]
tests/users/test_models.py::TestUser::test_email_field_is_unique PASSED [ 41%]
tests/users/test_models.py::TestUser::test_email_field_is_str PASSED [ 50%]
tests/users/test_models.py::TestUser::test_active_field_exists PASSED [ 58%]
tests/users/test_models.py::TestUser::test_active_field_is_default_true PASSED [ 66%]
tests/users/test_models.py::TestUser::test_active_field_is_bool PASSED [ 75%]
tests/users/test_models.py::TestUser::test_full_name_field_exists PASSED [ 83%]
tests/users/test_models.py::TestUser::test_full_name_field_is_str PASSED [ 91%]
tests/users/test_models.py::TestUser::test_all_fields_in_model PASSED [100%]
======================================================================= 12 passed in 0.34 seconds =======================================================================
(flask-api-users)
Chegamos ao fim deste artigo. Caso queiram baixar o projeto acesse o repositório no Github. Abraços a todos.
Próximo artigo: Criando usuários