web-dev-qa-db-ja.com

Djangoテストデータベースの作成に非常に長い時間がかかる単体テスト

しばらくの間、私の単体テストには予想よりも長い時間がかかっていました。テストが実行を開始する前に遅延が発生するため、あまり成功せずに数回デバッグしようとしました。これは、テスト駆動型開発に近いリモートで何かを実行する能力に影響を与えているので(おそらく私の期待が高すぎる)、これを完全に修正できるかどうかを確認したいと思います。

テストを実行すると、テストの開始から実際の開始までに70〜80秒の遅延があります。たとえば、小さなモジュール(time python manage.py test myappを使用)のテストを実行すると、

<... bunch of unimportant print messages I print from my settings>

Creating test database for alias 'default'...
......
----------------------------------------------------------------
Ran 6 tests in 2.161s

OK
Destroying test database for alias 'default'...

real    1m21.612s
user    1m17.170s
sys     0m1.400s

1m:21の約1m18は

Creating test database for alias 'default'...

そしてその

.......

ライン。つまり、テストには3秒未満かかりますが、データベースの初期化には1:18分かかっているようです。

約30個のアプリがあり、そのほとんどに1〜3個のデータベースモデルが含まれているため、プロジェクトのサイズがわかります。ユニットテストにSQLiteを使用し、提案された改善のいくつかを実装しました。設定ファイル全体を投稿することはできませんが、必要な情報を追加できれば幸いです。

私はランナーを使用します

from Django.test.runner import DiscoverRunner
from Django.conf import settings

class ExcludeAppsTestSuiteRunner(DiscoverRunner):
    """Override the default Django 'test' command, exclude from testing
    apps which we know will fail."""

    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        if not test_labels:
            # No appnames specified on the command line, so we run all
            # tests, but remove those which we know are troublesome.
            test_labels = (
                'app1',
                'app2',
                ....
                )
            print ('Testing: ' + str(test_labels))

        return super(ExcludeAppsTestSuiteRunner, self).run_tests(
                test_labels, extra_tests, **kwargs)

そして私の設定では:

TEST_RUNNER = 'config.test_runner.ExcludeAppsTestSuiteRunner'

Django-noseDjango-nose-excludeとともに使用してみました

私はテスト自体を高速化する方法について多くのことを読みましたが、データベースの初期化を最適化または回避する方法に関するリードを見つけていません。私は、データベースでテストしないようにするための提案を見てきましたが、それを完全に回避する方法を知ることはできません。

もし私に知らせてください

  1. これは正常で予想されるものです
  2. 期待されていません(そして、できれば修正するか、何をすべきかを導きます)

繰り返しますが、テスト自体を高速化する方法についてのヘルプは必要ありませんが、初期化(またはオーバーヘッド)は必要です。上記の例では、80秒ではなく10秒かかります。

どうもありがとう

--verbose 3を使用してテスト(単一のアプリ)を実行しましたが、これはすべて移行に関連していることがわかりました。

  Rendering model states... DONE (40.500s)
  Applying authentication.0001_initial... OK (0.005s)
  Applying account.0001_initial... OK (0.022s)
  Applying account.0002_email_max_length... OK (0.016s)
  Applying contenttypes.0001_initial... OK (0.024s)
  Applying contenttypes.0002_remove_content_type_name... OK (0.048s)
  Applying s3video.0001_initial... OK (0.021s)
  Applying s3picture.0001_initial... OK (0.052s)
  ... Many more like this

私はすべての移行を押しつぶしましたが、まだ遅いです。

36
dkarchmer

私の問題を修正する最後のソリューションは、テスト中に強制的にDjangoに移行を無効にすることです。これは、このような設定から実行できます

TESTING = 'test' in sys.argv[1:]
if TESTING:
    print('=========================')
    print('In TEST Mode - Disableling Migrations')
    print('=========================')

    class DisableMigrations(object):

        def __contains__(self, item):
            return True

        def __getitem__(self, item):
            return "notmigrations"

    MIGRATION_MODULES = DisableMigrations()

または https://pypi.python.org/pypi/Django-test-without-migrations を使用します

テスト全体に約1分かかり、小さなアプリには5秒かかります。

私の場合、移行時にテストを更新するので、テストに移行は必要ありません。また、データを追加するために移行を使用しません。これは誰にとってもうまくいくわけではありません

28
dkarchmer

概要

pytestを使用してください!

操作

  1. pip install pytest-Django
  2. pytest --nomigrationsの代わりに./manage.py test

結果

  • ./manage.py test費用2分11.86秒
  • pytest --nomigrationsコスト2.18秒

ヒント

  • プロジェクトのルートディレクトリにpytest.iniというファイルを作成し、そこに デフォルトのコマンドラインオプション および/または Django設定 を指定できます。

    # content of pytest.ini
    [pytest]
    addopts = --nomigrations
    Django_SETTINGS_MODULE = yourproject.settings
    

    これで、pytestを使用してテストを実行するだけで、入力の手間を省くことができます。

  • --reuse-db をデフォルトのコマンドラインオプションに追加することにより、後続のテストをさらに高速化できます。

    [pytest]
    addopts = --nomigrations --reuse-db
    

    ただし、データベースモデルが変更されたらすぐに、pytest --create-dbを1回実行して テストデータベースの再作成を強制 する必要があります。

  • テスト中に geventモンキーパッチング を有効にする必要がある場合、プロジェクトルートディレクトリに次の内容のpytestというファイルを作成し、実行ビットをキャストできます(chmod +x pytest)そしてpytestの代わりに./pytestを実行してテストします:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # content of pytest
    from gevent import monkey
    
    monkey.patch_all()
    
    import os
    
    os.environ.setdefault("Django_SETTINGS_MODULE", "yourproject.settings")
    
    from Django.db import connection
    
    connection.allow_thread_sharing = True
    
    import re
    import sys
    
    from pytest import main
    
    if __== '__main__':
        sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
        sys.exit(main())
    

    Geventモンキーのパッチ適用が成功したかどうかをテストするためのtest_gevent.pyファイルを作成できます。

    # -*- coding: utf-8 -*-
    # content of test_gevent.py
    import time
    from Django.test import TestCase
    from Django.db import connection
    import gevent
    
    
    def f(n):
        cur = connection.cursor()
        cur.execute("SELECT SLEEP(%s)", (n,))
        cur.execute("SELECT %s", (n,))
        cur.fetchall()
        connection.close()
    
    
    class GeventTestCase(TestCase):
        longMessage = True
    
        def test_gevent_spawn(self):
            timer = time.time()
            d1, d2, d3 = 1, 2, 3
            t1 = gevent.spawn(f, d1)
            t2 = gevent.spawn(f, d2)
            t3 = gevent.spawn(f, d3)
            gevent.joinall([t1, t2, t3])
            cost = time.time() - timer
            self.assertAlmostEqual(cost, max(d1, d2, d3), delta=1.0,
                                   msg='gevent spawn not working as expected')
    

参考文献

21
Rockallite

./ manage.py test --keepdb 移行ファイルに変更がない場合

14
Manoj

データベースの初期化には本当に時間がかかります...

私は、ほぼ同数のモデル/テーブル(約77)、および約350のテストを含むプロジェクトを行っており、すべてを実行するのに合計1分かかります。 2 cpusが割り当てられ、2 GBのRAMを搭載した浮浪者マシンでの開発。また、py.testとpytest-xdistプラグインを使用して、複数のテストを並行して実行します。

もう1つできることは、tell Djangoテストデータベースを再利用し、スキーマが変更された場合にのみ再作成します。また、テストでメモリ内データベースを使用するようにSQLiteを使用できます。両方のアプローチをここで説明します: https://docs.djangoproject.com/en/dev/topics/testing/overview/#the-test-database

[〜#〜] edit [〜#〜]:上記のオプションのいずれも機能しない場合、ユニットテストにDjango SimpleTestCaseを使用するか、この回答で説明されているデータベースを作成しないカスタムテストランナーを使用してください。 dbなしのDjangoユニットテスト

次に、Djangoこのようなライブラリを使用してデータベースへの呼び出しをモックすることができます(これは間違いなく書きました): https://github.com/stphivos/Django-mock-クエリ

このようにして、ユニットテストをローカルで高速に実行し、CIサーバーに、データベースを必要とする統合テストの実行を心配させてから、本番ではない安定したdev/masterブランチにコードをマージできます。

5
fips