Criando testes de unmanaged models no Django

No projeto que estou trabalhando no momento, precisei lidar com uma situação que era criar modelos não-gerenciados para refletirem em tabelas já criadas no banco de dados. Até aí tudo bem, mas como poderia criar testes unitários para validar não só o modelo mas funções de negócio que iria precisar ficar atrelado a classe?
Depois de pesquisar um pouco sobre isso na documentação, encontrei uma forma de lidar com isso sem afetar o funcionamento real da aplicação. Mas como assim?
Como os unmanaged models funciona?
Todos os modelos que você cria no seu projeto Django, por padrão eles são managed, ou seja, o django irá criar a tabela no banco de dados apropriadamente, baseando no que o modelo representa. Segue um exemplo:
class Question(models.Model):
text = models.TextField()
Segue um modelo bem simples, mas que ele diz muita coisa para a ORM poder criar uma tabela como definida abaixo:
CREATE TABLE app_question(
id int not null,
text text not null,
PRIMARY KEY(id)
);
Caso for executar esse SQL em um banco que respeite o ANSI SQL, ela será criada. O django faz essa conversão por debaixo dos panos e preparar para a criação por meio das migrations. Você que conhece o framework sabe como funciona.
Agora pode ocorrer de a gestão desses objetos do banco de dados, como as tabelas, já existam ou são controladas fora do framework, por inúmeros motivos, e por isso não faz sentido o Django criá-las senão o encontrará problemas, sendo que não tem como ter tabelas duplicadas. Dessa forma o framework oferece no Meta do seu modelo a propriedade managed. Se você definir como False, ela não será responsável pela criação, alteração e remoção da tabela que o modelo se espelha.
O caso comum de uso de modelos não-gerenciáveis é quando precisa usar um banco de dados legado em que sua aplicação Django precisará acessar. O comando inspectdb do manage.py é um ótimo auxílio para fazer o parser das tabelas desse banco para classes de modelo. Se nunca usou, recomendo que faça um teste e acredito que vai ser bem útil um dia.
Classes não-gerenciáveis e os testes
Por não serem gerenciáveis pelo Django, não quer dizer que não vai poder:
- Inserir/alterar/remover registros
- Efetuar consultas nos registros
Mas como disse antes, não poderá criar/alterar/remover a tabela. Quando você faz testes automatizados no Django e isso envolver modelos, ao rodar os testes o comportamento do framework é:
- Criar tabelas no banco de testes usando as migrations (default em SQLite: test_nome_do_banco.sqlite)
- Prepara o suite de testes
- Faz o uso do método setUp
- Executa os testes automatizados
- Se for usado os modelos para criar dados fake, insere na tabela recém criada
- Faz o uso do método tearDown
- Remove os registros criados nos testes
- Elimina o suite de testes
- Remove as tabelas do banco de dados de teste
Esse passo a passo foi descrito de forma bem grosseira, mas no geral é como funciona. O problema é que no caso de modelos não-gerenciados não irá funcionar porque se for criar dados fake com o modelo, vai dar erro dizendo que a tabela não existe e que o modelo não autoriza o Django a criar a tabela. Como lidar com isso?
Uma abordagem simples
Buscando alternativas de lidar com isso encontrei uma solução que é alterar o valor do managed no teste ou criar um TestRunner para isso. Se um o outro vai ser melhor, vai depender do problema que quer resolver, mas a primeira é a mais prática.
Como assim? Digamos que temos um modelo não gerenciado que queremos testar:
class LegacyTable(models.Model):
id = models.PositiveIntegerField(primary_key=True, db_column="cod_legacy")
name = models.CharField(max_length=255, db_column="txt_name")
class Meta:
db_table = "legacy_table"
managed = False
Criando ele e rodar o makemigrations para o app que o modelo está, irá criar uma migração parecido com essa:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='LegacyTable',
fields=[
('id', models.PositiveIntegerField(db_column='cod_legacy', primary_key=True, serialize=False)),
('name', models.CharField(db_column='txt_name', max_length=255)),
],
options={
'db_table': 'legacy_table',
'managed': False,
},
),
]
O que podemos fazer é definir uma variável no nosso settings.py para que caso formos rodar testes automatizados ele esteja como True:
# settings.py
# ...
DEBUG = True
TESTING = True
E com isso alteramos a migração para ficar assim:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='LegacyTable',
fields=[
('id', models.PositiveIntegerField(db_column='cod_legacy', primary_key=True, serialize=False)),
('name', models.CharField(db_column='txt_name', max_length=255)),
],
options={
'db_table': 'legacy_table',
'managed': settings.TESTING,
},
),
]
Dessa forma, quando rodarmos manage.py test app a suíte de testes irá considerar como um modelo convencional, criará a tabela
no banco que será usado nos testes, e assim poderemos criar os dados fake perfeitamente. Claro que ao rodar em ambiente que não irá precisar desse tipo de situção, a propriedade TESTING precisa estar False.
Com isso conseguimos criar testes perfeitamente sem precisar fazer customizações malucas. Quem tiver outras soluções, coloque aqui nos comentários!
Até mais!
About the author
Sou um desenvolvedor Python e Java focado em resolver problemas. Ajudo meus clientes através da criação de produtos digitais como sistemas web, APIs, coleta de dados e processos ETL utilizando soluções Big Data. Além disso sou um baterista e F1 Virtual Driver em construção, e um procrastinador em desconstrução.
Comments
Join the discussionNo comments yet. Be the first to comment.




