Bagaimana saya bisa memanggil perintah Django manage.py kustom langsung dari driver uji?

174

Saya ingin menulis unit test untuk perintah manage.py Django yang melakukan operasi backend pada tabel database. Bagaimana saya menjalankan perintah manajemen langsung dari kode?

Saya tidak ingin mengeksekusi perintah pada shell Sistem Operasi dari tests.py karena saya tidak dapat menggunakan lingkungan pengujian yang diatur menggunakan tes manage.py (database pengujian, kotak keluar email percobaan, dll ...)

MikeN
sumber

Jawaban:

312

Cara terbaik untuk menguji hal-hal seperti itu - ekstrak fungsionalitas yang diperlukan dari perintah itu sendiri ke fungsi mandiri atau kelas. Ini membantu mengabstraksi dari "perintah eksekusi" dan menulis tes tanpa persyaratan tambahan.

Tetapi jika Anda karena suatu alasan tidak dapat memisahkan perintah form logika Anda dapat memanggilnya dari kode apa saja menggunakan metode call_command seperti ini:

from django.core.management import call_command

call_command('my_command', 'foo', bar='baz')
Alex Koshelev
sumber
19
+1 untuk meletakkan logika yang dapat diuji di tempat lain (metode model? Metode manajer? Fungsi mandiri?) Sehingga Anda tidak perlu mengacaukan dengan mesin call_command sama sekali. Juga membuat fungsionalitas lebih mudah untuk digunakan kembali.
Carl Meyer
36
Bahkan jika Anda mengekstrak logika, fungsi ini masih berguna untuk menguji perilaku spesifik perintah Anda, seperti argumen yang diperlukan, dan untuk memastikan itu memanggil penyihir fungsi perpustakaan Anda melakukan pekerjaan yang sebenarnya.
Igor Sobreira
Paragraf pembuka berlaku untuk setiap situasi batas. Pindahkan kode logika biz Anda sendiri dari kode yang dibatasi untuk antarmuka dengan sesuatu, seperti pengguna. Namun, jika Anda menulis baris kode, kode tersebut dapat memiliki bug, jadi pengujian harus mencapai batas apa pun.
Phlip
Saya pikir ini masih berguna untuk sesuatu seperti call_command('check'), untuk memastikan pemeriksaan sistem lulus, dalam ujian.
Adam Barnes
22

Daripada melakukan trik call_command, Anda dapat menjalankan tugas Anda dengan melakukan:

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)
Nate
sumber
10
Mengapa Anda melakukan ini ketika call_command juga menyediakan untuk menangkap stdin, stdout, stderr? Dan ketika dokumentasi menentukan cara yang tepat untuk melakukan ini?
boatcoder
17
Itu pertanyaan yang sangat bagus. Tiga tahun yang lalu mungkin saya akan memiliki jawaban untuk Anda;)
Nate
1
Ditto Nate - ketika jawabannya adalah apa yang saya temukan satu setengah tahun yang lalu - saya hanya membangun di atasnya ...
Danny Staple
2
Posting penggalian, tetapi hari ini ini membantu saya: Saya tidak selalu menggunakan semua aplikasi basis kode saya (tergantung dari situs Django yang digunakan), dan call_commandmembutuhkan aplikasi yang diuji untuk dimuat INSTALLED_APPS. Antara harus memuat aplikasi hanya untuk tujuan pengujian dan menggunakan ini, saya memilih ini.
Mickaël
call_commandmungkin itulah yang harus dicoba oleh kebanyakan orang. Jawaban ini membantu saya menyelesaikan masalah di mana saya perlu memberikan nama tabel unicode ke inspectdbperintah. python / bash menafsirkan argumentasi baris perintah sebagai ascii, dan itu mengebom get_table_descriptionpanggilan jauh di Django.
bigh_29
17

kode berikut:

from django.core.management import call_command
call_command('collectstatic', verbosity=3, interactive=False)
call_command('migrate', 'myapp', verbosity=3, interactive=False)

... sama dengan perintah berikut yang diketik dalam terminal:

$ ./manage.py collectstatic --noinput -v 3
$ ./manage.py migrate myapp --noinput -v 3

Lihat menjalankan perintah manajemen dari django docs .

Artur Barseghyan
sumber
14

The dokumentasi Django pada call_command yang gagal untuk menyebutkan bahwa outharus diarahkan ke sys.stdout. Kode contoh harus dibaca:

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())
Alan Viars
sumber
1

Membangun dari jawaban Nate saya punya ini:

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

Pemakaian:

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

Keuntungan di sini adalah bahwa jika Anda telah menggunakan opsi tambahan dan OptParse, ini akan memilah Anda. Itu tidak cukup sempurna - dan itu tidak memancarkan output - tetapi akan menggunakan database pengujian. Anda kemudian dapat menguji efek basis data.

Saya yakin penggunaan modul tiruan Micheal Foords dan juga pemasangan stdout selama durasi tes akan berarti Anda bisa mendapatkan lebih banyak dari teknik ini juga - menguji output, kondisi keluar dll.

Danny Staple
sumber
4
Mengapa Anda pergi ke semua masalah ini alih-alih hanya menggunakan perintah call_?
boatcoder