web-dev-qa-db-ja.com

Djangoの異なる設定で単体テストを行う方法は?

Django単体テストの設定をオーバーライドする簡単なメカニズムはありますか?特定の数の最新オブジェクトを返すモデルのマネージャーがあります。それが返すオブジェクトの数は定義されていますNUM_LATEST設定によって。

これにより、誰かが設定を変更した場合にテストが失敗する可能性があります。 setUp()の設定をオーバーライドし、その後tearDown()でそれらを復元するにはどうすればよいですか?それが不可能な場合、メソッドにパッチを適用したり、設定を模擬したりする方法はありますか?

編集:ここに私のマネージャーコードがあります:

_class LatestManager(models.Manager):
    """
    Returns a specific number of the most recent public Articles as defined by 
    the NEWS_LATEST_MAX setting.
    """
    def get_query_set(self):
        num_latest = getattr(settings, 'NEWS_NUM_LATEST', 10)
        return super(LatestManager, self).get_query_set().filter(is_public=True)[:num_latest]
_

マネージャーは_settings.NEWS_LATEST_MAX_を使用してクエリセットをスライスします。 getattr()は、設定が存在しない場合にデフォルトを提供するために単に使用されます。

102
Soviut

編集:この回答は、small数のspecificテストの設定を変更する場合に適用されます。

Django 1.4なので、テスト中に設定をオーバーライドする方法があります。 https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings

TestCaseにはself.settingsコンテキストマネージャーがあり、テストメソッドまたはTestCaseサブクラス全体に適用できる@override_settingsデコレーターもあります。

これらの機能は、Django 1.3ではまだ存在していませんでした。

テストのallの設定を変更する場合は、テスト用に別の設定ファイルを作成します。これにより、メインの設定ファイルから設定を読み込んで上書きできます。他の回答では、これに対するいくつかの良いアプローチがあります。 hspander'sdmitrii's の両方のアプローチで成功したバリエーションを見てきました。

147
slinkp

インスタンスプロパティの設定や読み取りなど、UnitTestサブクラスに対しては何でもできます。

from Django.conf import settings

class MyTest(unittest.TestCase):
   def setUp(self):
       self.old_setting = settings.NUM_LATEST
       settings.NUM_LATEST = 5 # value tested against in the TestCase

   def tearDown(self):
       settings.NUM_LATEST = self.old_setting

Djangoテストケースはシングルスレッドで実行されるため、NUM_LATEST値を変更する他に何が必要なのでしょうか?テストルーチンによってその「何か」がトリガーされる場合、私はモンキーパッチを適用しても、テスト自体の信頼性を無効にすることなくテストが保存されるかどうかはわかりません。

44
Jarret Hardie

Update:以下のソリューションは、Django 1.3.x以前でのみ必要です。> 1.4については、 slinkpの答え

テストで設定を頻繁に変更し、Python≥2.5を使用する場合、これも便利です。

from contextlib import contextmanager

class SettingDoesNotExist:
    pass

@contextmanager
def patch_settings(**kwargs):
    from Django.conf import settings
    old_settings = []
    for key, new_value in kwargs.items():
        old_value = getattr(settings, key, SettingDoesNotExist)
        old_settings.append((key, old_value))
        setattr(settings, key, new_value)
    yield
    for key, old_value in old_settings:
        if old_value is SettingDoesNotExist:
            delattr(settings, key)
        else:
            setattr(settings, key, old_value)

その後、次のことができます。

with patch_settings(MY_SETTING='my value', OTHER_SETTING='other value'):
    do_my_tests()
20
akaihola

テストの実行時に--settingsオプションを渡すことができます

python manage.py test --settings=mysite.settings_local
18
MicroPyramid

実行時の設定構成のオーバーライドが役立つ場合がありますが、私の意見では、テスト用に別のファイルを作成する必要があります。これにより、テストのための構成が大幅に節約され、これにより、最終的に不可逆的なこと(ステージングデータベースのクリーニングなど)を行うことがなくなります。

テストファイルが「my_project/test_settings.py」に存在する場合、追加します

settings = 'my_project.test_settings' if 'test' in sys.argv else 'my_project.settings'

manage.pyで。これにより、python manage.py test test_settingsのみを使用します。 pytestのような他のテストクライアントを使用している場合、これをpytest.iniに簡単に追加できます。

16
hspandher

@override_settingsは、実稼働環境とテスト環境の構成に大きな違いがない場合に最適です。

それ以外の場合は、異なる設定ファイルを用意した方が良いでしょう。この場合、プロジェクトは次のようになります。

your_project
    your_app
        ...
    settings
        __init__.py
        base.py
        dev.py
        test.py
        production.py
    manage.py

そのため、ほとんどの設定をbase.pyに設定し、他のファイルではそこからすべてをインポートして、いくつかのオプションをオーバーライドする必要があります。 test.pyファイルは次のようになります。

from .base import *

DEBUG = False

DATABASES = {
    'default': {
        'ENGINE': 'Django.db.backends.sqlite3',
        'NAME': 'app_db_test'
    }
}

PASSWORD_HASHERS = (
    'Django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

そして、@ MicroPyramid answerのように--settingsオプションを指定するか、Django_SETTINGS_MODULE環境変数を指定してからテストを実行する必要があります。

export Django_SETTINGS_MODULE=settings.test
python manage.py test 
8

いくつかのdoctestを修正しようとしているときにこれを見つけました。

>>> from Django.conf import settings

>>> settings.SOME_SETTING = 20

>>> # Your other imports
>>> from Django.core.paginator import Paginator
>>> # etc
3
Jiaaro

Pytestを使用しています。

私はこれを次の方法で解決できました。

import Django    
import app.setting
import modules.that.use.setting

# do some stuff with default setting
setting.VALUE = "some value"
Django.setup()
import importlib
importlib.reload(app.settings)
importlib.reload(modules.that.use.setting)
# do some stuff with settings new value
1
Brontes