Djangoのテストデータベースをメモリ内でのみ実行する方法は?


125

Djangoの単体テストの実行には時間がかかるので、それを高速化する方法を探しています。SSDのインストールを検討していますが、SSDにも欠点があります。もちろん、コードでできることはありますが、構造的な修正を探しています。毎回データベースを再構築/南マイグレーションする必要があるため、単一のテストを実行するのも遅くなります。これが私の考えです...

テストデータベースは常に非常に小さいことがわかっているので、テストデータベース全体を常にRAMに保持するようにシステムを構成できないのはなぜですか。ディスクには絶対に触れないでください。Djangoでこれをどのように設定しますか?MySQLは本番環境で使用するものなので、引き続き使用したいのですが、SQLite  3などでこれが簡単にできる場合は、その方法を使用します。

SQLiteまたはMySQLには、完全にメモリ内で実行するオプションがありますか?RAMディスクを構成し、そこにデータを格納するようにテストデータベースを構成することは可能ですが、特に消去され続けるため、Django / MySQLに特定のデータベースに別のデータディレクトリを使用するように指示する方法がわかりません。各実行を再作成しました。(Mac FWIWを使用しています。)

回答:


164

テストを実行するときにデータベースエンジンをsqlite3に設定すると、Djangoはメモリ内データベースを使用します

settings.pyテストの実行時にエンジンをsqliteに設定するために、次のようなコードを使用しています。

if 'test' in sys.argv:
    DATABASE_ENGINE = 'sqlite3'

またはDjango 1.2では:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'sqlite3'}

そして最後にDjango 1.3と1.4では:

if 'test' in sys.argv:
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

(バックエンドへの完全なパスはDjango 1.3では厳密には必要ありませんが、前方互換の設定になります。)

南部の移行に問題がある場合は、次の行を追加することもできます。

    SOUTH_TESTS_MIGRATE = False

9
はい、正確に。私はそれを私の答えに入れるべきでした!これとSOUTH_TESTS_MIGRATE = Falseを組み合わせると、テストがはるかに高速になります。
エティエンヌ

7
これ素晴らしいです。新しいdjangoセットアップでは、次の行を使用します: 'ENGINE': 'sqlite3' if 'test' in sys.argv else 'django.db.backends.mysql'、
mjallday

3
@Tomasz Zielinski-うーん、それはあなたがテストしているものに依存します。しかし、私は、最後にそして時々、実際のデータベース(Postgres、MySQL、Oracle ...)でテストを実行する必要があることに完全に同意します。ただし、sqliteを使用してメモリ内でテストを実行すると、時間を大幅に節約できます。
エティエンヌ

3
私は-1を+1に逆にします。今見ているように、sqliteを使用してすばやく実行し、最終的な毎日のテストなどのMySQLに切り替える方がはるかに高速です。(投票のロックを解除するにはダミーの編集をしなければならなかったことに注意してください)
TomaszZielińskiJun

12
これ"test" in sys.argvに関する注意; それはあなたがそれをしたくないときにトリガーするかもしれませんmanage.py collectstatic -i testsys.argv[1] == "test"その問題が発生してはならない、より正確な状態です。
keturn

83

私は通常、テスト用に別の設定ファイルを作成し、それをテストコマンドで使用します。

python manage.py test --settings=mysite.test_settings myapp

これには2つの利点があります。

  1. testsys.argvでこのような魔法の単語をチェックする必要はありません。test_settings.py単に

    from settings import *
    
    # make tests faster
    SOUTH_TESTS_MIGRATE = False
    DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

    または、テスト設定を本番設定から明確に分離して、ニーズに合わせてさらに調整することもできます。

  2. もう1つの利点は、sqlite3の代わりに本番データベースエンジンを使用してテストを実行できるため、微妙なバグを回避できるため、開発中に

    python manage.py test --settings=mysite.test_settings myapp

    コードをコミットする前に一度実行します

    python manage.py test myapp

    すべてのテストが本当に成功していることを確認するだけです。


2
私はこのアプローチが好きです。さまざまな設定ファイルがたくさんあり、それらをさまざまなサーバー環境で使用していますが、この方法を使用して別のテストデータベースを選択することは考えていませんでした。アイデアをありがとう。
Alexis Bellido 2013

こんにちはアヌラーグ、私はこれを試しましたが、設定で言及されている他のデータベースも実行されます。正確な理由がわかりません。
ブペシュパンツ2014

素敵な答え。カバレッジを介してテストを実行するときに設定ファイルを指定する方法を知りたい。
Wtower 2015年

これは良いアプローチですが、DRYではありません。Djangoはすでにテストを実行していることを知っています。もしこの知識をどうにかして「フック」することができれば、あなたは準備ができているでしょう。残念ながら、そのためには管理コマンドを拡張する必要があります。たとえば、manage.pyが呼び出されるたびにMANAGEMENT_COMMANDを現在のコマンドに設定するなど、フレームワークのコアでこれをジェネリックにすることは、おそらく意味があります。
DylanYoung 2017

2
@DylanYoungを使用すると、test_settingsにメイン設定を含めて、テストしたいものをオーバーライドするだけで乾燥させることができます。
Anurag Uniyal 2017

22

MySQLは「MEMORY」と呼ばれるストレージエンジンをサポートしています。これは、データベース構成(settings.py)で次のように構成できます。

    'USER': 'root',                      # Not used with sqlite3.
    'PASSWORD': '',                  # Not used with sqlite3.
    'OPTIONS': {
        "init_command": "SET storage_engine=MEMORY",
    }

MEMORYストレージエンジンはblob / text列をサポートしていないため、これを使用してdjango.db.models.TextFieldいる場合は機能しません。


5
ブロブ/テキスト列のサポートの欠如を言及するための+1。また、トランザクションをサポートしていないようです(dev.mysql.com/doc/refman/5.6/en/memory-storage-engine.html)。
Tuukka Mustonen、2011年

インメモリテストが本当に必要な場合は、少なくともトランザクションをサポートするsqliteを使用する方が良いでしょう。
atomic77 2018年

15

主な質問にはお答えできませんが、スピードアップのためにできることがいくつかあります。

まず、MySQLデータベースがInnoDBを使用するように設定されていることを確認します。次に、トランザクションを使用して、各テストの前にデータベースの状態をロールバックできます。これにより、私の経験では、大幅なスピードアップにつながりました。settings.pyでデータベースのinitコマンドを渡すことができます(Django 1.2構文):

DATABASES = {
    'default': {
            'ENGINE':'django.db.backends.mysql',
            'HOST':'localhost',
            'NAME':'mydb',
            'USER':'whoever',
            'PASSWORD':'whatever',
            'OPTIONS':{"init_command": "SET storage_engine=INNODB" } 
        }
    }

第二に、毎回サウスマイグレーションを実行する必要はありません。設定してSOUTH_TESTS_MIGRATE = False、あなたのsettings.pyに、データベースがはるかに速く、すべての歴史的な移行を走るよりなり、プレーンsyncdbの実行で作成されます。


すばらしいヒントです。テストがからに減り369 tests in 498.704sました369 tests in 41.334s 。これは10倍以上高速です。
Gabi Purcaru、2012年

Django 1.7以降での移行のために、settings.pyに同等のスイッチはありますか?
エドワードニューウェル2015年

@EdwardNewell正確ではありません。ただし、を使用--keepしてデータベースを永続化することができ、テストの実行ごとに移行の完全なセットを再適用する必要はありません。新しい移行は引き続き実行されます。ブランチを頻繁に切り替えると、一貫性のない状態に陥りやすくなります(データベースをテストデータベースに変更して実行することで、切り替える前に新しい移行を元に戻すことができますがmigrate、少し面倒です)。
DylanYoung 2017

10

あなたは二重の微調整を行うことができます:

  • トランザクションテーブルを使用します。フィクスチャの初期状態は、TestCaseごとにデータベースのロールバックを使用して設定されます。
  • データベースのデータディレクトリをramdiskに配置します。データベースの作成に関する限り、多くのメリットが得られ、テストの実行も速くなります。

私は両方のトリックを使っており、とても満足しています。

UbuntuでMySQL用に設定する方法:

$ sudo service mysql stop
$ sudo cp -pRL /var/lib/mysql /dev/shm/mysql

$ vim /etc/mysql/my.cnf
# datadir = /dev/shm/mysql
$ sudo service mysql start

注意してください、それはテストのためだけです、メモリからデータベースを再起動した後は失われます!


ありがとう!私のために働く。mysql(フルテキストインデックス)に固有の機能を使用しているため、sqliteを使用できません。ubuntuユーザーの場合、mysqldが/ dev / shm / mysqlにアクセスできるようにapparmor設定を編集する必要があります
Ivan Virabyan

IvanとPotrの頭に乾杯。現時点ではAppArmorのmysqlプロファイルを無効にしましたが、関連するローカルプロファイルをカスタマイズするためのガイドが見つかりました:blogs.oracle.com/jsmyth/entry/apparmor_and_mysql
trojjer

うーん。ローカルプロファイルをカスタマイズしてmysqldに/ dev / shm / mysqlパスとその内容へのアクセスを許可しようとしましたが、サービスは「強制」モード(aa-complainコマンド)でのみ起動でき、「強制」ではありません。理由...別のフォーラムへの質問!理解できないのは、機能するときに「苦情」がまったくないことです。これは、mysqldがプロファイルに違反していないことを示しています...
trojjer

4

別のアプローチ:RAMディスクを使用するtempfsでMySQLの別のインスタンスを実行します。このブログ投稿の手順:DjangoでテストするためのMySQLの高速化

利点:

  • 本番サーバーが使用するものとまったく同じデータベースを使用する
  • デフォルトのmysql設定を変更する必要はありません

2

Anuragの答えを拡張して、同じtest_settingsを作成し、manage.pyに以下を追加することで、プロセスを簡略化しました

if len(sys.argv) > 1 and sys.argv[1] == "test":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.test_settings")
else:
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

sysは既にインポートされており、manage.pyはコマンドラインからのみ使用されるため、設定が煩雑になる必要がないため、よりクリーンに見えます


2
これ"test" in sys.argvに関する注意; それはあなたがそれをしたくないときにトリガーするかもしれませんmanage.py collectstatic -i testsys.argv[1] == "test"その問題が発生してはならない、より正確な状態です。
keturn

2
この方法で@keturnは、./manage.py引数なしで実行すると例外を生成します(たとえば、使用可能なプラグインを確認するには、と同じ--help
Antony Hatchkins '25 / 07/25

1
@AntonyHatchkins解決するのは簡単です:len(sys.argv) > 1 and sys.argv[1] == "test"
DylanYoung

1
@DylanYoungはい、それはまさに私がアルビンに彼の解決策に追加してほしかったものですが、彼はそれを改善することに特に興味がありません。とにかく、それは合法的なソリューションというよりは、迅速なハックのように見えます。
アントニーハッチキンズ

1
しばらくこの回答を見ていませんが、@ DylanYoungの改善を反映するようにスニペットを更新しました
Alvin

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.