Django: Como criar um modelo dinamicamente apenas para teste

votos
52

Eu tenho um aplicativo Django que requer um settingsatributo na forma de:

RELATED_MODELS = ('appname1.modelname1.attribute1',
                  'appname1.modelname2.attribute2', 
                  'appname2.modelname3.attribute3', ...)

Em seguida, conecta seu sinal post_save para atualizar algum outro modelo fixado em função da attributeNdefinido.

Eu gostaria de testar esse comportamento e testes devem funcionar mesmo se este aplicativo é o único no projeto (exceto por suas próprias dependências, nenhum outro aplicativo invólucro precisa ser instalado). Como posso criar e anexar / registrar / ativar modelos simulados apenas para o banco de dados de teste? (Ou é possível?)

Soluções que permitam-me a usar dispositivos de teste seria ótimo.

Publicado 02/02/2009 em 12:35
fonte usuário
Em outras línguas...                            


11 respostas

votos
46

Você pode colocar seus testes em um tests/subdiretório do aplicativo (em vez de um tests.pyarquivo), e incluem uma tests/models.pycom os modelos só de teste.

Em seguida, fornecer um script de execução de teste ( exemplo ) que inclui o seu tests/"app" em INSTALLED_APPS. (Isto não funciona quando a execução de testes de aplicativos a partir de um projeto real, que não tem o app testes em INSTALLED_APPS, mas eu raramente encontrá-lo útil para executar testes de aplicativos reutilizáveis a partir de um projeto, e Django 1.6 + não faz por padrão. )

( NOTA : O método dinâmico alternativo descrito abaixo só funciona em Django 1.1+ se seu caso de teste subclasses TransactionTestCase. - o que retarda seus testes de forma significativa - e já não funciona em todos no Django 1.7+ É deixado aqui apenas para interesse histórico; não use-o.)

No início de seus testes (ou seja, em um método de configuração, ou no início de um conjunto de doctests), você pode adicionar dinamicamente "myapp.tests"ao INSTALLED_APPS, e, em seguida, faça o seguinte:

from django.core.management import call_command
from django.db.models import loading
loading.cache.loaded = False
call_command('syncdb', verbosity=0)

Então, no final de seus testes, você deve limpar, restaurando a versão antiga do INSTALLED_APPS e limpar o cache do aplicativo novamente.

Essa classe encapsula o padrão de modo que não fiquem amontoados seu código de teste tanto.

Respondeu 02/02/2009 em 15:49
fonte usuário

votos
17

@ Resposta de paluh requer a adição de código indesejado em um arquivo não-teste e na minha experiência, @ solução de carl não funciona com django.test.TestCase que é necessário para usar acessórios. Se você quiser usar django.test.TestCase, você precisa ter certeza de que você chamar syncdb antes das luminárias ficar carregado. Isso requer substituindo o método _pre_setup (colocando o código no método de configuração não é suficiente). Eu uso minha própria versão de TestCase que vamos me adicionar aplicativos com modelos de teste. Ela é definida como se segue:

from django.conf import settings
from django.core.management import call_command
from django.db.models import loading
from django import test

class TestCase(test.TestCase):
    apps = ()

    def _pre_setup(self):
        # Add the models to the db.
        self._original_installed_apps = list(settings.INSTALLED_APPS)
        for app in self.apps:
            settings.INSTALLED_APPS.append(app)
        loading.cache.loaded = False
        call_command('syncdb', interactive=False, verbosity=0)
        # Call the original method that does the fixtures etc.
        super(TestCase, self)._pre_setup()

    def _post_teardown(self):
        # Call the original method.
        super(TestCase, self)._post_teardown()
        # Restore the settings.
        settings.INSTALLED_APPS = self._original_installed_apps
        loading.cache.loaded = False
Respondeu 20/04/2010 em 05:00
fonte usuário

votos
10

Esta solução funciona somente para versões anteriores do django(antes 1.7). Pode verificar a sua versão facilmente:

import django
django.VERSION < (1, 7)

resposta original:

É muito estranho, mas formar me trabalha muito simples padrão:

  1. adicionar tests.py de aplicativo que você está indo para testar,
  2. neste arquivo apenas definir modelos de teste,
  3. abaixo colocar o seu código de teste (doctest ou definição TestCase),

Abaixo eu coloquei algum código que define o artigo modelo que é necessária apenas para testes (ela existe em someapp / tests.py e posso testá-lo apenas com: someapp teste ./manage.py ):

class Article(models.Model):
    title = models.CharField(max_length=128)
    description = models.TextField()
    document = DocumentTextField(template=lambda i: i.description)

    def __unicode__(self):
        return self.title

__test__ = {"doctest": """
#smuggling model for tests
>>> from .tests import Article

#testing data
>>> by_two = Article.objects.create(title="divisible by two", description="two four six eight")
>>> by_three = Article.objects.create(title="divisible by three", description="three six nine")
>>> by_four = Article.objects.create(title="divisible by four", description="four four eight")

>>> Article.objects.all().search(document='four')
[<Article: divisible by two>, <Article: divisible by four>]
>>> Article.objects.all().search(document='three')
[<Article: divisible by three>]
"""}

Os testes de unidade também trabalhar com tal definição do modelo.

Respondeu 01/12/2009 em 17:24
fonte usuário

votos
8

Citando uma resposta relacionada :

Se você quiser modelos definidos para o teste só então você deve verificar se Django ticket # 7835 em particular comentário # 24 parte do qual é dado abaixo:

Aparentemente, você pode simplesmente definir modelos diretamente em seu tests.py. Syncdb não importa tests.py, para que esses modelos não serão sincronizadas com o db normal, mas eles vão ficar sincronizado com o banco de dados de teste, e pode ser usado em testes.

Respondeu 17/03/2013 em 13:34
fonte usuário

votos
8

Eu escolhi um, embora mais acoplados, abordagem ligeiramente diferente para criar dinamicamente modelos apenas para teste.

I manter todos os meus testes em um testssubdiretório que vive no meu filesaplicativo. O models.pyarquivo no testssubdiretório contém meus modelos só de teste. A parte associada vem aqui, onde eu preciso adicionar o seguinte ao meu settings.pyarquivo:

# check if we are testing right now
TESTING = 'test' in sys.argv

if TESTING:
    # add test packages that have models
    INSTALLED_APPS += ['files.tests',]

Eu também definir db_table no meu modelo de teste, porque senão Django teria criado a tabela com o nome tests_<model_name>, o que pode ter causado um conflito com outros modelos de teste em outro aplicativo. Aqui é o meu o meu modelo de teste:

class Recipe(models.Model):

    '''Test-only model to test out thumbnail registration.'''

    dish_image = models.ImageField(upload_to='recipes/')

    class Meta:
        db_table = 'files_tests_recipe'
Respondeu 03/01/2012 em 23:10
fonte usuário

votos
7

Eu descobri uma maneira em modelos só teste para Django 1.7+.

A idéia básica é, fazer o seu testsum aplicativo, e adicionar o seu testspara INSTALLED_APPS.

Aqui está um exemplo:

$ ls common
__init__.py   admin.py      apps.py       fixtures      models.py     pagination.py tests         validators.py views.py

$ ls common/tests
__init__.py        apps.py            models.py          serializers.py     test_filter.py     test_pagination.py test_validators.py views.py

E eu tenho diferentes settingspara diferentes fins (ref: dividir o arquivo de configurações ), a saber:

  • settings/default.py: Arquivo de configurações de base
  • settings/production.py: Para a produção
  • settings/development.py: para desenvolvimento
  • settings/testing.py: Para o teste.

E em settings/testing.py, você pode modificar INSTALLED_APPS:

settings/testing.py:

from default import *

DEBUG = True

INSTALLED_APPS += ['common', 'common.tests']

E certifique-se que você definiu uma etiqueta adequada para a sua testes aplicativo, ou seja,

common/tests/apps.py

from django.apps import AppConfig


class CommonTestsConfig(AppConfig):
    name = 'common.tests'
    label = 'common_tests'

common/tests/__init__.py, Configurar adequada AppConfig(ref: Aplicações Django ).

default_app_config = 'common.tests.apps.CommonTestsConfig'

Em seguida, gerar db por migração

python manage.py makemigrations --settings=<your_project_name>.settings.testing tests

Finalmente, você pode executar o teste com param --settings=<your_project_name>.settings.testing.

Se você usar py.test, você pode até cair um pytest.iniarquivo juntamente com Django manage.py.

py.test

[pytest]
DJANGO_SETTINGS_MODULE=kungfu.settings.testing
Respondeu 20/01/2016 em 13:08
fonte usuário

votos
7

Eu compartilhei minha solução que eu uso em meus projetos. Talvez ele ajuda a alguém.

pip install django-fake-model

Dois passos simples para criar modelo de falso:

1) Definir modelo em qualquer arquivo (I usualy definir modelo no arquivo de teste perto de um caso de teste)

from django_fake_model import models as f


class MyFakeModel(f.FakeModel):

    name = models.CharField(max_length=100)

2) Adicionar decorador @MyFakeModel.fake_mepara o seu TestCase ou para função de teste.

class MyTest(TestCase):

    @MyFakeModel.fake_me
    def test_create_model(self):
        MyFakeModel.objects.create(name='123')
        model = MyFakeModel.objects.get(name='123')
        self.assertEqual(model.name, '123')

Este decorador cria a tabela em seu banco de dados antes de cada teste e remover a tabela após o teste.

Além disso, você pode criar / excluir tabela manualmente: MyFakeModel.create_table()/MyFakeModel.delete_table()

Respondeu 28/09/2015 em 14:17
fonte usuário

votos
3

Este é o padrão que eu estou usando para fazer isso.

Eu escrevi este método que eu uso em uma versão subclasse de TestCase. Ele é o seguinte:

@classmethod
def create_models_from_app(cls, app_name):
    """
    Manually create Models (used only for testing) from the specified string app name.
    Models are loaded from the module "<app_name>.models"
    """
    from django.db import connection, DatabaseError
    from django.db.models.loading import load_app

    app = load_app(app_name)
    from django.core.management import sql
    from django.core.management.color import no_style
    sql = sql.sql_create(app, no_style(), connection)
    cursor = connection.cursor()
    for statement in sql:
        try:
            cursor.execute(statement)
        except DatabaseError, excn:
            logger.debug(excn.message)
            pass

Então, eu crio um arquivo models.py específicas de teste especial em algo como myapp/tests/models.pyque não está incluído no INSTALLED_APPS.

No meu método de configuração, eu chamo create_models_from_app ( 'myapp.tests') e cria as tabelas adequadas.

O único "pegadinha" com esta abordagem é que você realmente não quer criar os modelos já tempo setUpcorre, o que é por isso que eu pegar DatabaseError. Eu acho que a chamada para esse método poderia ir no topo do arquivo de teste e que iria trabalhar um pouco melhor.

Respondeu 18/04/2012 em 22:55
fonte usuário

votos
2

Combinando suas respostas, especialmente @ slacy de, eu fiz isso:

class TestCase(test.TestCase):
    initiated = False

    @classmethod
    def setUpClass(cls, *args, **kwargs):
        if not TestCase.initiated:
            TestCase.create_models_from_app('myapp.tests')
            TestCase.initiated = True

        super(TestCase, cls).setUpClass(*args, **kwargs)

    @classmethod
    def create_models_from_app(cls, app_name):
        """
        Manually create Models (used only for testing) from the specified string app name.
        Models are loaded from the module "<app_name>.models"
        """
        from django.db import connection, DatabaseError
        from django.db.models.loading import load_app

        app = load_app(app_name)
        from django.core.management import sql
        from django.core.management.color import no_style
        sql = sql.sql_create(app, no_style(), connection)
        cursor = connection.cursor()
        for statement in sql:
            try:
                cursor.execute(statement)
            except DatabaseError, excn:
                logger.debug(excn.message)

Com isso, você não tente criar tabelas db mais de uma vez, e você não precisa mudar suas INSTALLED_APPS.

Respondeu 18/05/2012 em 19:13
fonte usuário

votos
0

Alguém já mencionado Django ticket # 7835 , mas parece haver uma resposta mais recente, que parece muito mais promissor para versões mais recentes do Django. Especificamente nº 42 , que propõe uma diferente TestRunner:

from importlib.util import find_spec
import unittest

from django.apps import apps
from django.conf import settings
from django.test.runner import DiscoverRunner


class TestLoader(unittest.TestLoader):
    """ Loader that reports all successful loads to a runner """
    def __init__(self, *args, runner, **kwargs):
        self.runner = runner
        super().__init__(*args, **kwargs)

    def loadTestsFromModule(self, module, pattern=None):
        suite = super().loadTestsFromModule(module, pattern)
        if suite.countTestCases():
            self.runner.register_test_module(module)
        return suite


class RunnerWithTestModels(DiscoverRunner):
    """ Test Runner that will add any test packages with a 'models' module to INSTALLED_APPS.
        Allows test only models to be defined within any package that contains tests.
        All test models should be set with app_label = 'tests'
    """
    def __init__(self, *args, **kwargs):
        self.test_packages = set()
        self.test_loader = TestLoader(runner=self)
        super().__init__(*args, **kwargs)

    def register_test_module(self, module):
        self.test_packages.add(module.__package__)

    def setup_databases(self, **kwargs):
        # Look for test models
        test_apps = set()
        for package in self.test_packages:
            if find_spec('.models', package):
                test_apps.add(package)
        # Add test apps with models to INSTALLED_APPS that aren't already there
        new_installed = settings.INSTALLED_APPS + tuple(ta for ta in test_apps if ta not in settings.INSTALLED_APPS)
        apps.set_installed_apps(new_installed)
        return super().setup_databases(**kwargs)
Respondeu 27/12/2018 em 22:36
fonte usuário

votos
0

Se você estiver escrevendo uma Django app reutilizável, criar um aplicativo dedicado-teste mínimo para ele !

$ django-admin.py startproject test_myapp_project
$ django-admin.py startapp test_myapp

adicionar tanto myappe test_myappao INSTALLED_APPS, criar seus modelos lá e é bom para ir!

Tenho passado por todas essas respostas, bem como bilhete de Django 7835 , e eu finalmente fui para uma abordagem totalmente diferente. Eu queria que meu aplicativo (de alguma forma estendendo queryset.values ()) para ser capaz de ser testado em isolamento; Também, meu pacote não inclui alguns modelos e eu queria uma distinção clara entre modelos de teste e os pacotes.

Foi quando eu percebi que era mais fácil adicionar um pequeno projeto Django no pacote! Isso também permite uma separação muito mais limpo do IMHO código:

De lá você pode limpa e sem qualquer corte definir seus modelos, e você sabe que vai ser criado quando você executar os testes de lá!

Se você não está escrevendo um aplicativo independente, reutilizável você ainda pode ir por este caminho: criar um test_myappaplicativo e adicioná-lo aos seus INSTALLED_APPS apenas em separado settings_test_myapp.py!

Respondeu 23/07/2014 em 11:10
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more