Django: Come creare un modello dinamico solo per il testing

Ho un app Django che richiede un settings attributo in forma di:

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

Quindi ganci loro post_save segnale di aggiornare qualche altro modello fisso a seconda del attributeN definita.

Vorrei testare questo comportamento e test dovrebbe funzionare anche se questa applicazione è l’unico del progetto (ad eccezione per le proprie dipendenze, nessun altro wrapper app ha bisogno di essere installato). Come posso creare e collegare/register/attivare mock modelli solo per il database di test? (o è possibile a tutti?)

Soluzioni che mi permettono di utilizzare dispositivi di test sarebbe grande.

InformationsquelleAutor muhuk | 2009-02-02

 

11 Replies
  1. 51

    Si può mettere il test in un tests/ sottodirectory della app (piuttosto che un tests.py file), e includono un tests/models.py con il test-solo per i modelli.

    Quindi fornire un banco di prova per l’esecuzione di script ( esempio ), che include il vostro tests/ “app” in INSTALLED_APPS. (Questo non funziona quando l’esecuzione di app per i test da un progetto reale, che non hanno le prove app in INSTALLED_APPS, ma raramente mi è utile eseguire riutilizzabili app test da un progetto, e Django 1.6+ non per impostazione predefinita.)

    (NOTA: L’alternativa al metodo dinamico descritto di seguito funziona solo in Django 1.1+ se il test case sottoclassi TransactionTestCase – che rallenta il test in modo significativo e non funziona più a tutti in Django 1.7+. Lasciato qui solo per interesse storico, e non la uso.)

    All’inizio del test (cioè un metodo di configurazione, o all’inizio di una serie di doctests), è possibile aggiungere dinamicamente "myapp.tests" per il INSTALLED_APPS impostazione, quindi fare questo:

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

    Poi alla fine del test, si dovrebbe pulire ripristinando la vecchia versione di INSTALLED_APPS e a cancellare la cache dell’applicazione di nuovo.

    Questa classe incapsula il modello in modo da non ingombrare il codice di test, è proprio così.

    • Questo è un potente e pulito snipplet (credo sia il tuo). La creazione di una app a prima sembrava troppo poco per un finto modello. Ma credo che ormai rappresenta un mondo reale utilizzo di meglio da una unità di test di prospettiva. Grazie.
    • Sì, non so cosa è meglio, ma questo funziona per me. “La creazione di una app” mi sembra molto meno di un grande affare quando si rendono conto che tutti significa veramente è “creare un models.py file”.
    • Carlo, grazie per il frammento. Ero in procinto di andare a scrivere questo quando ho trovato questa pagina e il link. Roba buona.
    • questo è grande. Stavo giusto per chiedere questa domanda prima ho trovato questa risposta. Grazie per il post
    • Il link per l’esempio di test-esecuzione di script è morto; ecco il link aggiornato
    • Grazie Aron, aggiornati in risposta.
    • Questo approccio non sembra funzionare, almeno in Django 1.9.1: non C’è django.db.modelli.il caricamento di più
    • Sì. La risposta in particolare, dice l’approccio dinamico non funziona in Django 1.7+, e sta qui solo per interesse storico. Il giusto approccio è descritto nel primo paragrafo, e funziona ancora bene.
    • Non posso avere l’approccio descritto nel primo paragrafo di lavoro in Django 2.0+ – Sembra come se tutta questa risposta è deprecato.
    • forse il django docs può aiutare. Essi forniscono un esempio con dedicato tests/models.py e tests/test_settings.py.
    • Non ho riflesso la loro installazione, ma non funziona come previsto.

  2. 18

    @paluh la risposta richiede l’aggiunta di codice indesiderato non file di test e nella mia esperienza, @carl soluzione non funziona con django.test.TestCase che è necessario per utilizzare le attrezzature. Se si desidera utilizzare django.test.TestCase, è necessario assicurarsi che si chiamano syncdb prima di infissi caricata. Questo richiede l’override del _pre_setup metodo (mettendo il codice nel metodo di installazione non è sufficiente). Io uso la mia versione di TestCase che mi fa aggiungere le app con modelli di prova. Esso è definito come 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
    • roba grande Conley!
    • Per ottenere questo lavoro con Sud, ho dovuto passare migrate=False per call_command.
    • Se si dispone di impostazioni definite.INSTALLED_APPS come una tupla (come proposto in django docs) devi prima convertirlo in un elenco. Altrimenti non funziona bene.
  3. 11

    Ho condiviso la mia soluzione che io uso nei miei progetti. Forse aiuta qualcuno.

    pip install django-fake-model

    Due semplici passi per creare il falso modello:

    1) Definire il modello in qualsiasi file (io di solito definire il modello in un file di test nei pressi di un caso di test)

    from django_fake_model import models as f
    
    
    class MyFakeModel(f.FakeModel):
    
        name = models.CharField(max_length=100)

    2) Aggiungere decoratore @MyFakeModel.fake_me al tuo TestCase o per la funzione di test.

    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')

    Questo decoratore crea la tabella nel database prima di ogni prova e rimuovere la tabella dopo il test.

    Anche si può creare/eliminare manualmente la tabella: MyFakeModel.create_table() /MyFakeModel.delete_table()

  4. 11

    Questa soluzione funziona solo per le versioni precedenti di django (prima 1.7). È possibile verificare la versione facilmente:

    import django
    django.VERSION < (1, 7)

    Risposta originale:

    Strano, ma a me funziona in modo molto semplice modello:

    1. aggiungere tests.py di app che si sta andando a verificare,
    2. in questo file, basta definire il test dei modelli,
    3. sotto metti il tuo codice di prova (doctest o TestCase definizione),

    Qui di seguito ho messo un po ‘ di codice che definisce Articolo modello che è necessario solo per test (esiste in someapp/tests.py e posso provarlo solo con: ./manage.py test app ):

    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>]
    """}

    Unità di test anche in collaborazione con tale definizione del modello.

    • Questo non funziona per me in 1.1.1
    • I modificare me stesso… funziona… grazie
    • Questo è perfetto, funziona bene (sto usando django 1.2.1) e questo si sente come un modo “giusto” per farlo per me. Il modello di test dovrebbe esistere come parte del test per questa applicazione.
    • Aggiornamento – questo non funziona per apparecchi, ma è possibile chiamare syndb manualmente (tramite call_command) sovrascrivendo _pre_setup come descritto in Conley la risposta a questa domanda
    • Questo metodo non funziona più in Django 1.7
  5. 9

    Ho scelto un modo leggermente diverso, anche se più accoppiato, approccio per la creazione dinamica di modelli solo per il testing.

    Tengo tutte le mie prove in un tests sottodirectory che vive nel mio files app. Il models.py file in tests sottodirectory contiene il mio test-solo per i modelli. L’accoppiata viene in parte qui, dove ho bisogno di aggiungere quanto segue al mio settings.py file:

    # check if we are testing right now
    TESTING = 'test' in sys.argv
    
    if TESTING:
        # add test packages that have models
        INSTALLED_APPS += ['files.tests',]

    Ho anche impostato db_table nel mio modello di test, perché altrimenti Django avrebbe creato la tabella con il nome tests_<model_name>, che può aver causato un conflitto con altri modelli di prova in un’altra app. Ecco il mio test modello:

    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'
    • Questo avrebbe funzionato bene per un progetto, ma probabilmente non per un app. Pulire approccio, però.
    • È vero. Stavo pensando che se Django fornito con la possibilità di avere il file di impostazione delle app, quindi questo dovrebbe funzionare senza dover fare un progetto a livello di modifiche.
    • Beh, un sacco di applicazioni di prendere in considerazione il progetto di file delle impostazioni. C’è anche la possibilità di qualcosa di simile a questo: github.com/jaredly/django-appsettings
    • Che versione di Django si usa ?
  6. 9

    Citando una risposta correlati:

    Se si desidera che i modelli definiti per il test solo allora si dovrebbe verificare
    Django biglietto #7835 in particolare commento #24 di cui una parte
    è riportata di seguito:

    A quanto pare si può semplicemente definire modelli direttamente in tests.py.
    Syncdb mai importazioni tests.py, in modo che questi modelli non vengono sincronizzate il
    normale db, non è sincronizzato con il database di test, e può essere
    utilizzato nei test.

    • Questo sembra essere diventato meno affidabile in Django 1.7+, presumibilmente a causa del modo in cui le migrazioni sono in corso di gestione.
    • puoi approfondire questo?
    • Django 1.7+ non hanno “opzioni”. È stato di almeno un anno da quando ho studiato, ma se mi ricordo bene, AppConfig.ready() viene chiamato solo dopo che il DB è stato costruito e tutte le migrazioni, e le prove di modulo non è caricato fino a dopo AppConfig.ready(). Si potrebbe essere in grado di modificare qualcosa con un custom test runner, settings.py o AppConfig, ma non sono stato in grado di ottenere evidenti varianti di mettere-i-modelli-in-the-test di lavorare. Se qualcuno ha un Django 1.7+ esempio di questo lavoro, sarei felice di vederlo.
  7. 9

    Ho pensato a un modo per testare i modelli solo per django 1.7+.

    L’idea di base è, rendere il vostro tests di un’applicazione, e aggiungi il tuo tests per INSTALLED_APPS.

    Ecco un esempio:

    $ 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 ho diverse settings per scopi diversi(rif: dividere i file di impostazioni), vale a dire:

    • settings/default.py: le impostazioni di base di file
    • settings/production.py: per la produzione
    • settings/development.py: per lo sviluppo
    • settings/testing.py: per il test.

    E in settings/testing.py, è possibile modificare INSTALLED_APPS:

    settings/testing.py:

    from default import *
    
    DEBUG = True
    
    INSTALLED_APPS += ['common', 'common.tests']

    E assicurarsi di aver impostato un corretto etichetta per il test di app, vale a dire,

    common/tests/apps.py

    from django.apps import AppConfig
    
    
    class CommonTestsConfig(AppConfig):
        name = 'common.tests'
        label = 'common_tests'

    common/tests/__init__.py, impostare il corretto AppConfig(rif: Applicazioni Django).

    default_app_config = 'common.tests.apps.CommonTestsConfig'

    Quindi, generare migrazione db da

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

    Infine, è possibile eseguire il test con param --settings=<your_project_name>.settings.testing.

    Se si utilizza py.test, si può anche cadere un pytest.ini file con django manage.py.

    py.test

    [pytest]
    DJANGO_SETTINGS_MODULE=kungfu.settings.testing
    • Approccio simpatico, sembra essere l’unica soluzione in recente Django versioni. Ma vale la pena di ricordare che il DEBUG=False per il test, indipendentemente dal tuo settings.py
    • Perché vorreste DEBUG=False in testing.py?
    • non sono sicuro di averlo fatto giusto. Non è il mio desiderio, è l’impostazione predefinita comportamento di Django
    • L’unica soluzione al momento, che funziona in Django 1.10, grazie!
  8. 4

    Ecco il modello che sto usando per fare questo.

    Ho scritto questo metodo che io uso su una sottoclasse versione di TestCase. Va come segue:

    @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

    Allora, ho creato una speciale specifico per il test models.py file in qualcosa di simile a myapp/tests/models.py che non è incluso nel INSTALLED_APPS.

    Nel mio metodo di installazione, l’ho chiamata create_models_from_app(‘myapp.test’) e crea le tabelle appropriate.

    L’unico “gotcha” con questo approccio è che non si vuole veramente a creare modelli sempre tempo setUp corre, che è il motivo per cui ho pescato DatabaseError. Credo che la chiamata a questo metodo potrebbe andare in cima al file di test e che avrebbe funzionato un po ‘ meglio.

    • Dove si trova questo strumento importato da? Io sto avendo questo problema: NameError: nome globale ‘data logger’ non è definito
    • import logging; logger = logging.getLogger(__name__)
  9. 4

    Combinando le vostre risposte, appositamente @slacy, ho fatto questo:

    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)

    Con questo, non si tenta di creare tabelle di db più di una volta, e non c’è bisogno di cambiare il vostro INSTALLED_APPS.

  10. 1

    Se si sta scrivendo un riutilizzabili django-app, creare un minimo di prova-app dedicata per!

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

    aggiungere myapp e test_myapp per il INSTALLED_APPS, creare modelli e ‘ bene andare!

    Ho passato attraverso tutte queste risposte, così come django biglietto Sette mila otto cento trenta cinque, e alla fine andai per un metodo completamente differente.
    Volevo che la mia app (in qualche modo estendere queryset.valori() ) per essere in grado di essere testato in isolamento; inoltre, il mio pacchetto include alcuni modelli e volevo una netta distinzione tra modelli di prova e pacchetto di quelli.

    Che quando ho capito che era più facile per aggiungere un piccolo django progetto nel pacchetto!
    Ciò consente anche di molto più pulito di separazione di codice IMHO:

    In là è possibile in modo pulito e senza alcun hack definire i modelli, e sai che sarà creato quando si esegue il test a partire da qui!

    Se non sono iscritto indipendente, riutilizzabili app è ancora possibile andare in questo modo: creare un test_myapp app, e aggiungere al vostro INSTALLED_APPS solo in un separato settings_test_myapp.py!

  11. 0

    Qualcuno già accennato Django biglietto #7835, ma ci sembra essere un altro, più recente risposta che sembra più promettente per le versioni più recenti di Django. In particolare #42, che propone una diversa 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)

Lascia un commento