Як я можу викликати користувальницьку команду Django Manag.py безпосередньо з тестового драйвера?


174

Я хочу написати одиничний тест для команди Django Manag.py, яка виконує операцію з резервного копіювання на таблиці бази даних. Як я можу викликати команду управління безпосередньо з коду?

Я не хочу виконувати команду в оболонці Операційної системи від testing.py, оскільки я не можу використовувати тестову середу, створену за допомогою тесту Manag.py (тестова база даних, тестова скринька електронної пошти і т.д. ...)

Відповіді:


312

Найкращий спосіб перевірити такі речі - витягнути необхідну функціональність з самої команди до самостійної функції або класу. Це допомагає абстрагуватися від "матеріалу виконання команд" і написати тест без додаткових вимог.

Але якщо ви з якихось причин не можете від'єднати команду логічної форми, ви можете зателефонувати їй з будь-якого коду, використовуючи метод call_command на зразок цього:

from django.core.management import call_command

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

19
+1 - помістити логіку тестування десь в іншому місці (модельний метод? Метод менеджера? Автономна функція?), Тому вам взагалі не потрібно возитися з механізмом call_command. Також полегшує повторне використання функціональних можливостей.
Карл Мейєр

36
Навіть якщо ви виймаєте логіку, ця функція все-таки корисна для перевірки конкретної поведінки вашої команди, як, наприклад, необхідних аргументів, і щоб переконатися, що вона викликає функцію вашої бібліотеки, яка справді працює.
Ігор Собрейра

Вступний параграф застосовується до будь-якої прикордонної ситуації. Перемістіть власний логічний код бізнес із коду, який обмежений для інтерфейсу з чимось, наприклад, користувачем. Однак, якщо ви пишете рядок коду, він може мати помилку, тому тести дійсно повинні доходити за будь-який кордон.
Фліп

Я думаю, що це все ще корисно для чогось подібного call_command('check'), щоб переконатися, що перевірка системи проходить, в тесті.
Адам Барнс

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? І коли в документації вказано правильний спосіб зробити це?
boatcoder

17
Це надзвичайно гарне питання. Три роки тому, можливо, я мав би відповідь за вас;)
Нейт

1
Дітто Нейт - коли його відповідь була тим, що я знайшов рік-півтора тому - я просто будував на ньому ...
Danny Staple

2
Після копання, але сьогодні це допомогло мені: я не завжди використовую всі програми моєї кодової бази (залежно від використовуваного сайту Django), і мені call_commandпотрібно завантажити тестований додаток INSTALLED_APPS. Між тим, що потрібно завантажувати додаток лише для тестування та використовувати це, я вибрав це.
Мікаел

call_commandце, мабуть, те, що більшість людей повинні спробувати спочатку. Ця відповідь допомогла мені вирішити проблему, коли мені потрібно було передати імена таблиць unicode inspectdbкоманді. python / bash інтерпретували аргументи командного рядка як ascii, і це бомбардувало get_table_descriptionвиклик глибоко в django.
bigh_29

17

наступний код:

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

... дорівнює наступним командам, набраним у терміналі:

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

Див. Запуск команд управління з документів django .


14

Документація Django в call_command не згадує, до якої 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, а також повторне з'єднання прокладки протягом тривалості тесту означало б, що ви також можете вийти ще з цієї техніки - протестуйте вихід, умови виходу тощо.


4
Чому ви б пішли на всю цю проблему замість того, щоб просто використовувати call_command?
boatcoder
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.