Django: How to create a model dynamically just for testing

后端 未结 11 823
鱼传尺愫
鱼传尺愫 2020-12-02 05:15

I have a Django app that requires a settings attribute in the form of:

RELATED_MODELS = (\'appname1.modelname1.attribute1\',
                  \         


        
相关标签:
11条回答
  • 2020-12-02 06:04

    I shared my solution that I use in my projects. Maybe it helps someone.

    pip install django-fake-model

    Two simple steps to create fake model:

    1) Define model in any file (I usualy define model in test file near a test case)

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

    2) Add decorator @MyFakeModel.fake_me to your TestCase or to test function.

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

    This decorator creates table in your database before each test and remove the table after test.

    Also you may create/delete table manually: MyFakeModel.create_table() / MyFakeModel.delete_table()

    0 讨论(0)
  • 2020-12-02 06:05

    You can put your tests in a tests/ subdirectory of the app (rather than a tests.py file), and include a tests/models.py with the test-only models.

    Then provide a test-running script (example) that includes your tests/ "app" in INSTALLED_APPS. (This doesn't work when running app tests from a real project, which won't have the tests app in INSTALLED_APPS, but I rarely find it useful to run reusable app tests from a project, and Django 1.6+ doesn't by default.)

    (NOTE: The alternative dynamic method described below only works in Django 1.1+ if your test case subclasses TransactionTestCase - which slows down your tests significantly - and no longer works at all in Django 1.7+. It's left here only for historical interest; don't use it.)

    At the beginning of your tests (i.e. in a setUp method, or at the beginning of a set of doctests), you can dynamically add "myapp.tests" to the INSTALLED_APPS setting, and then do this:

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

    Then at the end of your tests, you should clean up by restoring the old version of INSTALLED_APPS and clearing the app cache again.

    This class encapsulates the pattern so it doesn't clutter up your test code quite as much.

    0 讨论(0)
  • 2020-12-02 06:06

    This solution works only for earlier versions of django (before 1.7). You can check your version easily:

    import django
    django.VERSION < (1, 7)
    

    Original response:

    It's quite strange but form me works very simple pattern:

    1. add tests.py to app which you are going to test,
    2. in this file just define testing models,
    3. below put your testing code (doctest or TestCase definition),

    Below I've put some code which defines Article model which is needed only for tests (it exists in someapp/tests.py and I can test it just with: ./manage.py test someapp ):

    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 tests also working with such model definition.

    0 讨论(0)
  • 2020-12-02 06:11

    I've figured out a way for test-only models for django 1.7+.

    The basic idea is, make your tests an app, and add your tests to INSTALLED_APPS.

    Here's an example:

    $ 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
    

    And I have different settings for different purposes(ref: splitting up the settings file), namely:

    • settings/default.py: base settings file
    • settings/production.py: for production
    • settings/development.py: for development
    • settings/testing.py: for testing.

    And in settings/testing.py, you can modify INSTALLED_APPS:

    settings/testing.py:

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

    And make sure that you have set a proper label for your tests app, namely,

    common/tests/apps.py

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

    common/tests/__init__.py, set up proper AppConfig(ref: Django Applications).

    default_app_config = 'common.tests.apps.CommonTestsConfig'
    

    Then, generate db migration by

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

    Finally, you can run your test with param --settings=<your_project_name>.settings.testing.

    If you use py.test, you can even drop a pytest.ini file along with django's manage.py.

    py.test

    [pytest]
    DJANGO_SETTINGS_MODULE=kungfu.settings.testing
    
    0 讨论(0)
  • 2020-12-02 06:12

    Combining your answers, specially @slacy's, I did this:

    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)
    

    With this, you don't try to create db tables more than once, and you don't need to change your INSTALLED_APPS.

    0 讨论(0)
提交回复
热议问题