Como executar o banco de dados de teste do Django apenas na memória?

125

Meus testes de unidade do Django levam muito tempo para serem executados, então estou procurando maneiras de acelerar isso. Estou pensando em instalar um SSD , mas sei que também tem suas desvantagens. Claro, há coisas que eu poderia fazer com meu código, mas estou procurando uma correção estrutural. Mesmo a execução de um único teste é lenta, pois o banco de dados precisa ser reconstruído / migrado para o sul toda vez. Então aqui está a minha ideia ...

Como sei que o banco de dados de teste sempre será muito pequeno, por que não consigo simplesmente configurar o sistema para manter sempre o banco de dados de teste inteiro na RAM? Nunca toque no disco. Como eu configuro isso no Django? Eu preferiria continuar usando o MySQL, já que é isso que uso na produção, mas se o SQLite  3 ou algo mais facilita isso, eu continuaria assim.

O SQLite ou o MySQL tem uma opção para executar inteiramente na memória? Deveria ser possível configurar um disco RAM e, em seguida, configurar o banco de dados de teste para armazenar seus dados lá, mas não sei como instruir o Django / MySQL a usar um diretório de dados diferente para um determinado banco de dados, especialmente porque ele continua sendo apagado e recriou cada corrida. (Estou em um Mac FWIW.)

Leopd
fonte

Respostas:

164

Se você definir seu mecanismo de banco de dados como sqlite3 ao executar seus testes, o Django usará um banco de dados na memória .

Estou usando um código como este settings.pypara definir o mecanismo como sqlite ao executar meus testes:

if 'test' in sys.argv:
    DATABASE_ENGINE = 'sqlite3'

Ou no Django 1.2:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'sqlite3'}

E finalmente no Django 1.3 e 1.4:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

(O caminho completo para o back-end não é estritamente necessário com o Django 1.3, mas torna a configuração compatível.)

Você também pode adicionar a seguinte linha, caso esteja tendo problemas com as migrações do Sul:

    SOUTH_TESTS_MIGRATE = False
Etienne
fonte
9
Sim, exatamente. Eu deveria ter colocado isso na minha resposta! Combine isso com SOUTH_TESTS_MIGRATE = False e seus testes devem ser muito mais rápidos.
Etienne
7
isso é demais. em configurações mais recentes do django, use esta linha: 'ENGINE': 'sqlite3' if 'test' em sys.argv else 'django.db.backends.mysql',
mjallday 19/01/2011
3
@ Tomasz Zielinski - Hmm, depende do que você está testando. Mas concordo totalmente que, no final e de tempos em tempos, você precisa executar os testes com seu banco de dados real (Postgres, MySQL, Oracle ...). Mas executar seus testes na memória com o sqlite pode economizar muito tempo.
Etienne
3
Eu inverto -1 para +1: como o vejo agora, é muito mais rápido usar o sqlite para execuções rápidas e mudar para o MySQL para, por exemplo, testes diários finais. (Note que eu tinha que fazer uma edição fictícia para votação desbloqueio)
Tomasz Zieliński
12
Cuidado com isso "test" in sys.argv; pode ser acionado quando você não deseja, por exemplo manage.py collectstatic -i test. sys.argv[1] == "test"é uma condição mais precisa que não deve ter esse problema.
keturn
83

Normalmente, crio um arquivo de configurações separado para testes e o uso no comando test, por exemplo

python manage.py test --settings=mysite.test_settings myapp

Tem dois benefícios:

  1. Você não precisa procurar testou qualquer palavra mágica em sys.argv test_settings.pypode simplesmente ser

    from settings import *
    
    # make tests faster
    SOUTH_TESTS_MIGRATE = False
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}
    

    Ou você pode ajustá-lo ainda mais para suas necessidades, separando claramente as configurações de teste das configurações de produção.

  2. Outro benefício é que você pode executar o teste com o mecanismo de banco de dados de produção em vez do sqlite3, evitando erros sutis, portanto, ao desenvolver o uso

    python manage.py test --settings=mysite.test_settings myapp

    e antes de confirmar o código, execute uma vez

    python manage.py test myapp

    só para ter certeza de que todos os testes estão realmente passando.

Anurag Uniyal
fonte
2
Eu gosto dessa abordagem. Eu tenho vários arquivos de configurações diferentes e os uso para diferentes ambientes de servidor, mas não pensei em usar esse método para escolher um banco de dados de teste diferente. Obrigado pela ideia.
Alexis Bellido
Oi Anurag, Eu tentei isso, mas meus outros bancos de dados mencionados nas configurações também são executados. Eu não sou capaz de descobrir o motivo exato.
Bhupesh Pant
Boa resposta. Gostaria de saber como especificar um arquivo de configurações ao executar testes através da cobertura.
Wtower 10/03/2015
É uma boa abordagem, mas não SECA. O Django já sabe que você está executando testes. Se você pudesse 'conectar-se' a esse conhecimento de alguma forma, estaria preparado. Infelizmente, acredito que isso requer estender o comando de gerenciamento. Provavelmente faria sentido tornar esse genérico no núcleo da estrutura, por exemplo, tendo uma configuração MANAGEMENT_COMMAND definida como o comando atual sempre que manage.py for chamado, ou algo nesse sentido.
DylanYoung
2
@DylanYoung, você pode deixá-lo seco, incluindo as configurações principais em test_settings e substituindo as coisas que você deseja testar.
Anurag Uniyal
22

O MySQL suporta um mecanismo de armazenamento chamado "MEMORY", que você pode configurar em seu banco de dados config ( settings.py) como:

    'USER': 'root',                      # Not used with sqlite3.
    'PASSWORD': '',                  # Not used with sqlite3.
    'OPTIONS': {
        "init_command": "SET storage_engine=MEMORY",
    }

Observe que o mecanismo de armazenamento MEMORY não suporta colunas de blob / texto; portanto, se você estiver usando django.db.models.TextFieldisso, não funcionará para você.

muudscope
fonte
5
+1 por mencionar a falta de suporte para colunas de blob / texto. Também parece não suportar transações ( dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html ).
Tuukka Mustonen
Se você realmente deseja testes na memória, provavelmente é melhor usar o sqlite, que pelo menos suporta transações.
precisa saber é o seguinte
15

Não consigo responder à sua pergunta principal, mas há algumas coisas que você pode fazer para acelerar as coisas.

Primeiramente, verifique se o seu banco de dados MySQL está configurado para usar o InnoDB. Em seguida, ele pode usar transações para reverter o estado do banco de dados antes de cada teste, o que, na minha experiência, levou a uma aceleração maciça. Você pode passar um comando init do banco de dados em seu settings.py (sintaxe do Django 1.2):

DATABASES = {
    'default': {
            'ENGINE':'django.db.backends.mysql',
            'HOST':'localhost',
            'NAME':'mydb',
            'USER':'whoever',
            'PASSWORD':'whatever',
            'OPTIONS':{"init_command": "SET storage_engine=INNODB" } 
        }
    }

Em segundo lugar, você não precisa executar as migrações sul cada vez. Defina SOUTH_TESTS_MIGRATE = Falseem settings.py e o banco de dados será criado com o syncdb simples, que será muito mais rápido do que executar todas as migrações históricas.

Daniel Roseman
fonte
Ótima dica! Isso reduziu meus testes de 369 tests in 498.704spara 369 tests in 41.334s . Isso é mais de 10 vezes mais rápido!
Gabi Purcaru
Existe uma opção equivalente em settings.py para migrações no Django 1.7+?
Edward Newell
@ EdwardNewell Não exatamente. Mas você pode usar --keeppara manter o banco de dados e não exigir que seu conjunto completo de migrações seja reaplicado em cada execução de teste. Novas migrações ainda serão executadas. Se você alternar entre ramificações com frequência, é fácil entrar em um estado inconsistente (você pode reverter novas migrações antes de mudar, alterando o banco de dados para o banco de dados de teste e executando migrate, mas é um pouco trabalhoso).
DylanYoung
10

Você pode fazer ajustes duplos:

  • usar tabelas transacionais: o estado inicial dos equipamentos será definido usando a reversão do banco de dados após cada TestCase.
  • coloque o diretório de dados do banco de dados no ramdisk: você ganhará muito no que diz respeito à criação do banco de dados e a execução do teste será mais rápida.

Estou usando os dois truques e estou muito feliz.

Como configurá-lo para o MySQL no Ubuntu:

$ sudo service mysql stop
$ sudo cp -pRL /var/lib/mysql /dev/shm/mysql

$ vim /etc/mysql/my.cnf
# datadir = /dev/shm/mysql
$ sudo service mysql start

Cuidado, é apenas para teste, após a reinicialização do banco de dados da memória ser perdida!

Potr Czachur
fonte
obrigado! funciona para mim. Não consigo usar o sqlite, porque estou usando recursos específicos para o mysql (índices de texto completo). Para usuários do Ubuntu, você terá que editar sua configuração apparmor para permitir o acesso mysqld para / dev / shm / mysql
Ivan Virabyan
Um brinde ao heads-up Ivan e Potr. Desabilitou o perfil mysql do AppArmor por enquanto, mas encontrou um guia para personalizar o perfil local relevante: blogs.oracle.com/jsmyth/entry/apparmor_and_mysql
trojjer
Hmm. Eu tentei personalizar o perfil local para dar ao mysqld acesso ao caminho / dev / shm / mysql e seu conteúdo, mas o serviço só pode iniciar no modo 'reclamar' (comando aa-reclamar) e não 'impor', por alguns motivo ... Uma pergunta para outro fórum! O que eu não consigo entender é como lá não são 'queixas' em tudo quando ela não funciona, o que implica que o mysqld não está violando o perfil ...
trojjer
4

Outra abordagem: tenha outra instância do MySQL executando em um tempfs que use um disco RAM. Instruções nesta postagem do blog: Acelerando o MySQL para testes no Django .

Vantagens:

  • Você usa exatamente o mesmo banco de dados usado pelo servidor de produção
  • não é necessário alterar sua configuração padrão do mysql
Neves
fonte
2

Estendendo a resposta de Anurag, simplifiquei o processo criando as mesmas test_settings e adicionando o seguinte a manage.py

if len(sys.argv) > 1 and sys.argv[1] == "test":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.test_settings")
else:
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

parece mais limpo, pois o sys já foi importado e o manage.py é usado apenas via linha de comando, portanto, não é necessário desorganizar as configurações

Alvin
fonte
2
Cuidado com isso "test" in sys.argv; pode ser acionado quando você não deseja, por exemplo manage.py collectstatic -i test. sys.argv[1] == "test"é uma condição mais precisa que não deve ter esse problema.
keturn
2
@keturn Desta forma, gera uma excepção, quando funcionando ./manage.pysem argumentos (por exemplo, para ver as extensões estão disponíveis, mesmo que --help)
Antony Hatchkins
1
@AntonyHatchkins Isso é trivial para resolver:len(sys.argv) > 1 and sys.argv[1] == "test"
DylanYoung
1
@DylanYoung Sim, é exatamente isso que eu queria que Alvin adicionasse à sua solução, mas ele não está particularmente interessado em melhorá-la. De qualquer forma, parece mais um hack rápido do que a solução legítima.
Antony Hatchkins
1
não olhei para esta resposta em quando, eu atualizei o trecho para refletir @ melhoria da DylanYoung
Alvin
0

Use abaixo em seu setting.py

DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
Ehsan Barkhordar
fonte