カスタムDjango manage.pyコマンドをテストドライバーから直接呼び出すにはどうすればよいですか?


174

データベーステーブルでバックエンド操作を行うDjango manage.pyコマンドの単体テストを記述したいと思います。コードから管理コマンドを直接呼び出すにはどうすればよいですか?

manage.py testを使用して設定されたテスト環境(テストデータベース、テスト用ダミーメールアウトボックスなど)を使用できないため、tests.pyからオペレーティングシステムのシェルでコマンドを実行したくありません。

回答:


312

そのようなものをテストする最良の方法-必要な機能をコマンド自体からスタンドアロンの関数またはクラスに抽出します。「コマンド実行に関するもの」から抽象化し、追加の要件なしでテストを作成するのに役立ちます。

しかし、何らかの理由でコマンドからロジックを分離できない場合は、次のようなcall_commandメソッドを使用して、任意のコードからそれを呼び出すことができます。

from django.core.management import call_command

call_command('my_command', 'foo', bar='baz')

19
テスト可能なロジックを別の場所(モデルメソッド?マネージャーメソッド?スタンドアロン関数?)に配置するために+1するため、call_command機構をいじる必要はまったくありません。また、機能の再利用が容易になります。
カールマイヤー

36
ロジックを抽出したとしても、この関数は必要な引数などのコマンド固有の動作をテストし、ライブラリ関数を呼び出して実際の作業を行うことを確認するのに役立ちます。
Igor Sobreira

冒頭の段落は、あらゆる境界状況に適用されます。独自のビジネスロジックコードを、ユーザーなどとのインターフェイスに制限されているコードから移動します。ただし、コード行を記述した場合、バグが発生する可能性があるため、テストは実際には境界の背後に到達する必要があります。
Phlip 2016

これはcall_command('check')、システムチェックがテストで合格していることを確認するために、のようなものには依然として役立つと思います。
Adam Barnes、

22

call_commandトリックを実行する代わりに、次のようにしてタスクを実行できます。

from myapp.management.commands import my_management_task
cmd = my_management_task.Command()
opts = {} # kwargs for your command -- lets you override stuff for testing...
cmd.handle_noargs(**opts)

10
call_commandがstdin、stdout、stderrのキャプチャも提供しているのに、なぜこれを行うのですか?そして、ドキュメントがこれを行う正しい方法を指定している場合?
ボートコーダー、2014

17
それは非常に良い質問です。3年前、多分私はあなたのための答えがあったと思います;)
Nate

1
Ditto Nate-彼の答えが1年半前に私が見つけたものだったとき-私はそれに基づいて作成しただけです...
Danny Staple

2
ポスト掘り出し、しかし今日これは私を助けました:私は常に(使用するDjangoサイトに応じて)コードベースのすべてのアプリケーションを使用しているわけではなくcall_command、テスト済みのアプリケーションをにロードする必要がありますINSTALLED_APPS。テストのためだけにアプリをロードする必要がある場合と、これを使用する場合とでは、これを選択しました。
Micka Novl

call_commandおそらく、ほとんどの人が最初に試すべきことです。この回答は、Unicodeテーブル名をinspectdbコマンドに渡す必要がある問題の回避に役立ちました。python / bashはコマンドライン引数をasciiとして解釈していましたが、それがget_table_descriptiondjangoの呼び出しを爆破していました。
bigh_29


14

call_command上のDjangoのドキュメントは言及に失敗outにリダイレクトする必要がありますsys.stdout。コード例は次のようになります。

from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
import sys

class ClosepollTest(TestCase):
    def test_command_output(self):
        out = StringIO()
        sys.stdout = out
        call_command('closepoll', stdout=out)
        self.assertIn('Expected output', out.getvalue())

1

ネイトの答えに基づいて私はこれを持っています:

def make_test_wrapper_for(command_module):
    def _run_cmd_with(*args):
        """Run the possibly_add_alert command with the supplied arguments"""
        cmd = command_module.Command()
        (opts, args) = OptionParser(option_list=cmd.option_list).parse_args(list(args))
        cmd.handle(*args, **vars(opts))
    return _run_cmd_with

使用法:

from myapp.management import mycommand
cmd_runner = make_test_wrapper_for(mycommand)
cmd_runner("foo", "bar")

ここでの利点は、追加のオプションとOptParseを使用している場合、これにより問題が解決されることです。それは完全ではありません-そしてそれはまだ出力をパイプしません-それはテストデータベースを使用します。その後、データベースの影響をテストできます。

私は、Micheal Foordsモックモジュールを使用し、テスト中にstdoutを再配線することで、この手法をさらに活用できることを確信しています-出力、終了条件などをテストしてください。


4
なぜcall_commandを使用するだけでなく、このすべての問題に行くのですか?
ボートコーダー、2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.