Qual è il modo migliore / più pitonioso di prendere in giro una funzione privata?

1

Considera un modulo con una funzione "pubblica" simile a questa:

def func(arg):
    val = _generate_something(arg)
    _do_something(val)

Come puoi vedere questa è una 'funzione del vuoto'. La sua logica di base è in _generate_something .

Voglio scrivere test unitari per questa funzione. Posso pensare a diversi approcci per farlo. Mi piacerebbe sapere qual è il migliore, sia in termini di pratiche di programmazione generale che di best practice in Python.

Essenzialmente questa domanda riguarda il modo Pythonico di prendere in giro una funzione privata (denaro che si sta rammendo? Iniezione della funzione? Iniezione di un oggetto? Ecc.)

Opzione A

Nel test monkeypatch _do_something (che è una funzione 'privata' del modulo), così posso verificare che riceva un buon valore.

Opzione B

Inietti _do_something come argomento a func , in questo modo:

def func(arg, do_something=None):
    do_something = do_something or _do_something  # in production use default
    val = _generate_something(arg)
    do_something(val)

Poi nel mio do_something mock posso controllare di aver ricevuto il valore corretto.

Opzione C

Avere func essere un metodo di una classe, e avere quella classe prendere do_something come argomento a __init__ :

class Something(object):
    def __init__(self, do_something=None):
        self._do_something = do_something or _production_default

    def func(self, arg):
        val = self._generate_something(arg)
        self.do_something(val)

    # ... omitted for brevity ...

Opzione D

Mi piace l'opzione B o C, ma do_something è iniettato come un oggetto 'pieno', non una funzione nuda. Ad esempio:

def func(arg, something_doer=None):
    something_doer = something_doer or _something_doer # in production use default
    val = _generate_something(arg)
    something_doer.do_something(val)

Come ho detto, mi piacerebbe sapere quale sia l'opzione migliore, anche per quanto riguarda "Pythonicness". Ad esempio, è 'accettabile' in Python per deridere le funzioni nude (piuttosto che gli oggetti che le contengono)?

    
posta Aviv Cohn 08.11.2016 - 01:31
fonte

2 risposte

2

Consiglierei di usare l'approccio più semplice e più breve, che è in Python probabilmente l'opzione A, patch per scimmia . Tutto il resto rende il codice più complicato, senza alcun vantaggio sostanziale. L'unica cosa che cambierei qui è rendere questa soluzione più esplicita (vedi sotto).

Convertire il tuo modulo in una classe (opzione C) dovrebbe essere motivato dal fatto che hai bisogno di una classe invece di un modulo, non dal fatto che vuoi prendere in giro una singola funzione. Questa opzione è preferibile in linguaggi come Java o C #, dove non c'è distinzione tra un "modulo" e una "classe" in un modo paragonabile a Python, e dove una funzione come func probabilmente "vivrebbe" all'interno di un classe in prima persona.

Sostituire il metodo do_something con un metodo con un metodo (opzione D) non dovrebbe essere fatto per lo stesso motivo - usa un oggetto invece di una funzione autonoma quando una funzione autonoma è "non abbastanza", per lo scopo puro di derisione non porta alcun vantaggio.

Quindi questo ti lascia con l'opzione B o A. Tuttavia, non vorrei inquinare l'elenco dei parametri di func allo scopo di deridere, né inserisco codice di simulazione in una funzione func solo allo scopo di deridere se c'è un'alternativa più semplice. Meglio mantenere l'API "ufficiale" e il codice funzionale quanto più pulito possibile. Immagina cosa succede se vuoi prendere in giro più metodi "privati" invece di uno solo, e se vuoi testare 5 metodi pubblici del modulo - l'API conterrà i parametri del 90% usati solo per i test, il che riduce la leggibilità di una grandezza .

Se hai dubbi sull'approccio di patch delle scimmie perché cambia qualcosa di privato, puoi aggiungere una funzione di patch "pubblica" (o "interna") al modulo, che consente di eseguire la manipolazione attraverso una funzione "ufficiale" :

def setup_mock_for_do_something(do_something):
    _do_something = do_something

Ciò rende più esplicita la possibilità di "patching scimmia" in questo modulo, evitando simultaneamente modifiche strutturali non necessarie all'intero codice.

Quindi perché è tutto più "Pythonic"? Guardando "Lo Zen di Python" , vediamo qui i seguenti principi applicati

  • Esplicito è meglio di implicito.
  • Semplice è meglio che complesso.
  • Contabilità.
  • Se l'implementazione è facile da spiegare, potrebbe essere una buona idea.
risposta data 08.11.2016 - 05:39
fonte
1

Nota: OP ha modificato e modificato la domanda in modo significativo, quindi questa risposta è un po 'obsoleta, ma per ora è una cronologia. La risposta originale segue:

Supponendo che _generate_something() e _do_something() funzionino, e siano correttamente testati individualmente , può essere fatto un buon argomento per non disturbare il test func() . IMO, è ben oltre il punto di diminuzione dei rendimenti nei test unitari. YMMV. (Nota: se ci fossero molte linee di codice tra le due linee nell'esempio, allora un test ha senso)

Ad esempio, le tue opzioni BD, come le capisco, sono solo un modo complicato per testare i risultati di _generate_something() , che potresti solo testare direttamente. Ora, forse hanno senso in il design complessivo del tuo programma, ma aggiungere quella quantità di complessità solo per i test unitari è un errore.

    
risposta data 08.11.2016 - 02:45
fonte

Leggi altre domande sui tag