web-dev-qa-db-ja.com

Django:テストのためだけに動的にモデルを作成する方法

Djangoアプリにはsettings属性が次の形式で必要です:

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

次に、定義されたattributeNに応じて、post_save信号をフックして、他のいくつかの固定モデルを更新します。

この動作をテストしたいのですが、このアプリがプロジェクトで唯一のアプリであってもテストは機能します(独自の依存関係を除いて、他のラッパーアプリをインストールする必要はありません)。テストデータベース専用のモックモデルを作成してアタッチ/登録/アクティブ化するにはどうすればよいですか? (またはそれはまったく可能ですか?)

テストフィクスチャを使用できるようにするソリューションは素晴らしいでしょう。

60
muhuk

アプリのtests/サブディレクトリに(tests.pyファイルではなく)テストを配置し、テスト専用モデルにtests/models.pyを含めることができます。

次に、tests/INSTALLED_APPS "アプリ"を含むテスト実行スクリプト( )を提供します。 (これは、INSTALLED_APPSにテストアプリがない実際のプロジェクトからアプリのテストを実行する場合は機能しませんが、プロジェクトから再利用可能なアプリのテストを実行することはめったになく、Django 1.6+はデフォルトではありません。)

[〜#〜]注[〜#〜]:以下で説明する代替動的メソッドは、Django 1.1+でのみ機能しますサブクラスTransactionTestCase-テストの速度が大幅に低下します-Django 1.7+でまったく機能しなくなります。これは歴史的な目的でのみ残されています。使用しないでください。)

テストの最初(つまり、setUpメソッド内、またはdoctestのセットの最初)で、"myapp.tests"をINSTALLED_APPS設定に動的に追加して、これを行うことができます。

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

次に、テストの最後に、古いバージョンのINSTALLED_APPSを復元し、アプリのキャッシュを再度クリアして、クリーンアップする必要があります。

このクラス はパターンをカプセル化するので、テストコードが散らかりません。

51
Carl Meyer

@paluhの回答では、テスト以外のファイルに不要なコードを追加する必要があり、私の経験では、@ carlのソリューションは、フィクスチャを使用するために必要なDjango.test.TestCaseでは機能しません。 Django.test.TestCaseを使用する場合は、フィクスチャがロードされる前に必ずsyncdbを呼び出す必要があります。これには、_pre_setupメソッドをオーバーライドする必要があります(setUpメソッドにコードを置くだけでは不十分です)。私は自分のバージョンのTestCaseを使用しています。テストモデルを使用してアプリを追加できます。次のように定義されます。

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
18
Conley Owens

このソリューションは、Djangoの以前のバージョン(1.7より前)でのみ機能します。バージョンは簡単に確認できます。

import Django
django.VERSION < (1, 7)

元の応答:

それはかなり奇妙ですが、私が非常に単純なパターンで機能するフォーム:

  1. テストするアプリにtests.pyを追加し、
  2. このファイルでは、テストモデルを定義します。
  3. 以下にテストコード(doctestまたはTestCaseの定義)を入力します。

以下に、テストにのみ必要なArticleモデルを定義するコードをいくつか置きます(someapp/tests.pyに存在し、次のようにテストできます./ 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>]
"""}

ユニットテストは、このようなモデル定義でも機能します。

11
paluh

プロジェクトで使用する solution を共有しました。多分それは誰かを助ける。

_pip install Django-fake-model_

偽のモデルを作成するための2つの簡単なステップ:

1)任意のファイルでモデルを定義します(通常、テストケースの近くのテストファイルでモデルを定義します)

_from Django_fake_model import models as f


class MyFakeModel(f.FakeModel):

    name = models.CharField(max_length=100)
_

2)デコレータ_@MyFakeModel.fake_me_をTestCaseに追加するか、関数をテストします。

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

このデコレータは、各テストの前にデータベースにテーブルを作成し、テスト後にテーブルを削除します。

また、手動でcreate/deletedelete:MyFakeModel.create_table()/MyFakeModel.delete_table()

11
Kirill Ermolov

関連する答え からの引用:

テスト専用にモデルを定義したい場合は、 Djangoチケット#7835 をチェックアウトする必要があります。特に コメント#24 の一部を以下に示します。

どうやら、tests.pyで直接モデルを直接定義できます。 Syncdbはtests.pyをインポートしないため、これらのモデルは通常のdbに同期されませんが、テストデータベースに同期され、テストで使用できます。

9
joeharrie

Django 1.7+のテスト専用モデルの方法を見つけました。

基本的な考え方は、testsをアプリにして、testsINSTALLED_APPSに追加することです。

次に例を示します。

$ 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

そして、私は異なる目的のために異なるsettingsを持っています(ref: 設定ファイルを分割する )、すなわち:

  • settings/default.py:基本設定ファイル
  • settings/production.py:本番用
  • settings/development.py:開発用
  • settings/testing.py:テスト用。

そしてsettings/testing.pyでは、INSTALLED_APPSを変更できます:

settings/testing.py

from default import *

DEBUG = True

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

そして、テストアプリに適切なラベルを設定していることを確認してください。

common/tests/apps.py

from Django.apps import AppConfig


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

common/tests/__init__.py、適切なAppConfig(ref: Django Applications )を設定します。

default_app_config = 'common.tests.apps.CommonTestsConfig'

次に、db移行を生成します

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

最後に、param --settings=<your_project_name>.settings.testingを使用してテストを実行できます。

Py.testを使用する場合は、Djangoのpytest.iniとともにmanage.pyファイルをドロップすることもできます。

py.test

[pytest]
Django_SETTINGS_MODULE=kungfu.settings.testing
9
Xiao Hanyu

テストのためだけにモデルを動的に作成するために、結合度は高くなりますが、少し異なるアプローチを選択しました。

すべてのテストをtestsアプリにあるfilesサブディレクトリに保存します。 testsサブディレクトリのmodels.pyファイルには、テスト専用モデルが含まれています。結合された部分がここに入ります。ここで、以下をsettings.pyファイルに追加する必要があります。

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

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

テストモデルにもdb_tableを設定しました。そうしないと、Djangoがtests_<model_name>という名前のテーブルを作成し、別のアプリの他のテストモデルと競合する可能性があるためです。ここにあります。私の私のテストモデル:

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'
9
Jashugan

これが私がこれを行うために使用しているパターンです。

TestCaseのサブクラスバージョンで使用するこのメソッドを作成しました。次のようになります。

@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

次に、INSTALLED_APPSに含まれていないmyapp/tests/models.pyなどの特別なテスト固有のmodels.pyファイルを作成します。

私のsetUpメソッドでは、create_models_from_app( 'myapp.tests')を呼び出し、適切なテーブルを作成します。

このアプローチの唯一の「落とし穴」は、setUpを実行するたびに実際にモデルを作成したくないということです。そのため、私はDatabaseErrorをキャッチします。このメソッドの呼び出しはテストファイルの先頭に移動する可能性があり、それが少しうまく機能すると思います。

4
slacy

あなたの答え、特に@slacyのものを組み合わせて、私はこれをしました:

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)

これにより、dbテーブルを複数回作成する必要がなくなり、INSTALLED_APPSを変更する必要がなくなります。

3
zVictor

再利用可能なDjangoアプリを作成している場合は、そのための最小限のテスト専用アプリを作成します

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

myapptest_myappの両方をINSTALLED_APPSに追加し、そこでモデルを作成したら、準備完了です。

私はこれらすべての回答とDjango ticket 7835 を通過しました。そして、ついに私はまったく異なるアプローチを取りました。私は自分のアプリ(何らかの形でqueryset.values ())分離してテストできるようにするために、また、私のパッケージにはいくつかのモデルが含まれており、テストモデルとパッケージのモデルを明確に区別する必要がありました。

そのとき、パッケージに非常に小さなDjangoプロジェクトを追加する方が簡単であることがわかりました!これにより、コードをよりきれいに分離することができます。

そこではハックすることなくきれいにモデルを定義でき、そこからテストを実行するとモデルが作成されることがわかります!

独立した再利用可能なアプリを作成していない場合でも、この方法で作成できます。test_myappアプリを作成し、別のsettings_test_myapp.pyでのみINSTALLED_APPSに追加してください。

1
Stefano

誰かがすでに言及しました Django ticket#7835 ですが、より最近のバージョンのDjangoに対してより有望に見えるより最近の返信があるようです。特に #42 は、異なる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)
0
André Fratelli