Come posso chiamare un comando Django manage.py personalizzato direttamente da un driver di prova?


174

Voglio scrivere un test unitario per un comando manage.py Django che esegue un'operazione di back-end su una tabella di database. Come invoco il comando di gestione direttamente dal codice?

Non voglio eseguire il comando sulla shell del sistema operativo da tests.py perché non riesco a utilizzare l'ambiente di test impostato utilizzando il comando manage.py (database di test, posta in uscita fittizia di test, ecc ...)

Risposte:


312

Il modo migliore per testare queste cose - estrarre le funzionalità necessarie dal comando stesso alla funzione o classe autonoma. Aiuta a sottrarre "roba di esecuzione dei comandi" e scrivere test senza requisiti aggiuntivi.

Ma se per qualche motivo non riesci a disaccoppiare il comando del modulo logico puoi chiamarlo da qualsiasi codice usando il metodo call_command in questo modo:

from django.core.management import call_command

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

19
+1 per mettere la logica testabile da qualche altra parte (metodo del modello? Metodo manager? Funzione standalone?) In modo da non avere alcun problema con il macchinario call_command. Inoltre semplifica il riutilizzo della funzionalità.
Carl Meyer,

36
Anche se estrai la logica questa funzione è ancora utile per testare il tuo comportamento specifico del comando, come gli argomenti richiesti, e per assicurarti che chiami la tua funzione di libreria che fa il vero lavoro.
Igor Sobreira,

Il paragrafo di apertura si applica a qualsiasi situazione al contorno. Sposta il tuo codice logico biz fuori dal codice che è costretto a interfacciarsi con qualcosa, come un utente. Tuttavia, se si scrive la riga di codice, potrebbe esserci un bug, quindi i test dovrebbero effettivamente superare qualsiasi limite.
Phlip,

Penso che questo sia ancora utile per qualcosa del genere call_command('check'), per assicurarsi che i controlli di sistema stiano passando, in un test.
Adam Barnes,

22

Anziché eseguire il trucco call_command, è possibile eseguire l'attività eseguendo:

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
Perché dovresti farlo quando call_command prevede anche l'acquisizione di stdin, stdout, stderr? E quando la documentazione specifica il modo giusto per farlo?
Boatcoder

17
Questa è un'ottima domanda. Tre anni fa forse avrei avuto una risposta per te;)
Nate,

1
Idem Nate - quando la sua risposta è stata quella che ho trovato un anno e mezzo fa - mi sono semplicemente basato su ...
Danny Staple,

2
Post scavo, ma oggi questo mi ha aiutato: non utilizzo sempre tutte le applicazioni della mia base di codice (a seconda del sito Django utilizzato) e ho call_commandbisogno di caricare l'applicazione testata INSTALLED_APPS. Tra il dover caricare l'app solo a scopo di test e l'utilizzo di questo, ho scelto questo.
Mickaël,

call_commandè probabilmente quello che la maggior parte delle persone dovrebbe provare prima. Questa risposta mi ha aiutato a risolvere un problema in cui dovevo passare i nomi di tabella unicode al inspectdbcomando. python / bash interpretavano gli argomenti della riga di comando come ascii, e questo stava bombardando la get_table_descriptionchiamata nel profondo di Django.
bigh_29,

17

il seguente codice:

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

... è uguale ai seguenti comandi digitati nel terminale:

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

Vedi l' esecuzione dei comandi di gestione dai documenti di django .


14

La documentazione di Django sul comando call_ non riesce a menzionare che outdeve essere reindirizzata sys.stdout. Il codice di esempio dovrebbe essere:

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

Basandomi sulla risposta di Nate ho questo:

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

Uso:

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

Il vantaggio qui è che se hai utilizzato opzioni aggiuntive e OptParse, questo risolverà il problema per te. Non è del tutto perfetto - e non convoglia ancora gli output - ma utilizzerà il database di test. È quindi possibile verificare gli effetti del database.

Sono sicuro che l'uso del modulo simulato di Micheal Foords e anche il ricablaggio dello stdout per la durata di un test significherebbe che potresti ottenere qualcosa in più anche da questa tecnica - testare l'output, le condizioni di uscita ecc.


4
Perché dovresti andare a tutti questi problemi invece di usare solo call_command?
Boatcoder
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.