Domanda

Ho un file di doctestable abbastanza semplice:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest
    doctest.testmod(verbose=True)

che funziona come previsto quando viene eseguito direttamente attraverso python.

Tuttavia, in ipython, ottengo

1 items had no tests:
    __main__
0 tests in 1 items.
0 passed and 0 failed.
Test passed.

Poiché questo è parte di un progetto Django e avrà bisogno di accedere a tutte le variabili appropriate e tali che manage.py imposta, posso anche eseguire attraverso un comando modificato, che utilizza code.InteractiveConsole, uno risultato delle quali è __name__ viene impostato su '__console__'.

Con il codice di cui sopra, ottengo lo stesso risultato con ipython. Ho provato a cambiare l'ultima riga a questo:

 this = __import__(__name__)
 doctest.testmod(this, verbose=True)

ed ottengo un ImportError su __console__, che ha un senso, immagino. Ciò non ha alcun effetto su entrambi Python o ipython.

Quindi, mi piacerebbe essere in grado di eseguire doctests con successo attraverso tutti e tre questi metodi, in particolare l'InteractiveConsole uno, dal momento che mi aspetto di essere bisogno di magia pony Django abbastanza presto.

Solo per chiarezza, questo è quello che mi aspetto:

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
È stato utile?

Soluzione

I seguenti lavori:

$ ipython
...
In [1]: %run file.py

Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.

In [2]: 

Non ho idea del perché ipython file.py non funziona. Ma quanto sopra è almeno una soluzione.

Modifica

Ho trovato il motivo per cui non funziona. E 'molto semplice:

  • Se non si specifica il modulo di testare in doctest.testmod(), si presuppone che si desidera testare il modulo __main__.
  • Quando IPython esegue il file passato ad esso sulla riga di comando, il modulo è __main__ __main__ di IPython, non il modulo. Così doctest cerca di eseguire doctests nello script ingresso di IPython.

I seguenti lavori, ma si sente un po 'strano:

if __name__ == '__main__':
    import doctest
    import the_current_module
    doctest.testmod(the_current_module)

Quindi, in pratica il modulo importazioni stesso (che è il "si sente un po 'strano" parte). Ma funziona. Qualcosa non mi piace circa. questo approccio è che ogni modulo deve includere il proprio nome nella sorgente.

EDIT 2:

Il seguente script, ipython_doctest, rende ipython si comportano nel modo desiderato:

#! /usr/bin/env bash

echo "__IP.magic_run(\"$1\")" > __ipython_run.py
ipython __ipython_run.py

Lo script crea uno script python che eseguirà %run argname in IPython.

Esempio:

$ ./ipython_doctest file.py
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5 (r25:51908, Mar  7 2008, 03:27:42) 
Type "copyright", "credits" or "license" for more information.

IPython 0.9.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object'. ?object also works, ?? prints more.

In [1]:

Altri suggerimenti

Il problema principale è che ipython gioca brutti scherzi strani con __main__ (tramite il proprio modulo di FakeModule) in modo che, per il momento in doctest è introspezione che "modulo presunto" attraverso il suo __dict__, Foo è non ci -. così doctest non ricorsivamente it

Ecco una soluzione:

class Foo():
    """
    >>> 3+2
    5
    """

if __name__ in ("__main__", "__console__"):
    import doctest, inspect, sys
    m = sys.modules['__main__']
    m.__test__ = dict((n,v) for (n,v) in globals().items()
                            if inspect.isclass(v))
    doctest.testmod(verbose=True)

Ciò produce, come richiesto:

$ ipython dot.py 
Trying:
    3+2
Expecting:
    5
ok
1 items had no tests:
    __main__
1 items passed all tests:
   1 tests in __main__.__test__.Foo
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
Python 2.5.1 (r251:54863, Feb  6 2009, 19:02:12) 
  [[ snip snip ]]
In [1]: 

Basta impostare __test__ globale non funziona, ancora una volta perché l'impostazione come un globale di quello che stai pensando di come __main__ in realtà non metterlo nella __dict__ dell'oggetto reale che viene recuperato da m = sys.modules['__main__'], e il secondo è esattamente la doctest espressione sta utilizzando internamente (in realtà usa sys.modules.get, ma la precauzione non è necessario qui dal momento che sappiamo che esiste __main__ in sys.modules ... non è solo l'oggetto che si aspetta che sia -!)

Inoltre, proprio l'impostazione m.__test__ = globals() direttamente non funziona neanche, per un motivo diverso: controlli doctest che i valori in __test__ sono stringhe, funzioni, classi o moduli, e senza alcuna selezione, non si può garantire che globals() soddisferà tale condizione ( in realtà non lo farà). Qui sto selezionando solo le classi, se si vuole anche funzioni o roba del genere è possibile utilizzare un or nella clausola if nel genexp all'interno della chiamata dict.

Non so esattamente come si sta eseguendo un Django guscio che è in grado di eseguire lo script (come credo python manage.py shell non accetta argomenti, si deve fare qualcosa di diverso, e non riesco a immaginare che cosa esattamente! -), ma un approccio simile dovrebbe aiutare (se il guscio di Django sta usando ipython, l'impostazione predefinita quando disponibile, o Python pianura): in modo appropriato l'impostazione __test__ nell'oggetto si ottiene come sys.modules['__main__'] (o __console__, se è quello che stai passando poi a doctest.testmod, credo) dovrebbe funzionare, in quanto imita ciò che doctest saranno poi facendo internamente per individuare le corde di prova.

E, per concludere, una riflessione filosofica sul design, l'architettura, la semplicità, la trasparenza, e la "magia nera" ...:

Tutto questo sforzo è fondamentalmente ciò che è necessario per sconfiggere la "magia nera" che ipython (e forse Django, anche se può essere semplicemente delegando che parte a ipython) sta facendo a vostro nome per la vostra "convenienza" ... qualsiasi ora in cui due quadri (o più ;-) stanno facendo in modo indipendente ciascuna il proprio marchio di magia nera, l'interoperabilità può improvvisamente richiedere uno sforzo notevole e diventare tutt'altro che conveniente; -).

Non sto dicendo che avrebbe potuto essere fornita la stessa comodità (da uno o più dei ipython, Django e / o doctests) senza di magia nera, l'introspezione, moduli falsi, e così via ; i progettisti e manutentori di ciascuno di questi quadri sono ingegneri eccellenti, e mi aspetto che hanno fatto il loro dovere a fondo, e stanno effettuando solo la quantità minima di magia nera che è indispensabile per fornire la quantità di convenienza per l'utente hanno deciso che avevano bisogno. Tuttavia, anche in una situazione del genere, "magia nera" si trasforma improvvisamente da un sogno di convenienza per un incubo di debug non appena si vuole fare qualcosa di ancora marginalmente al di fuori ciò che l'autore del quadro aveva concepito.

OK, forse in questo caso non proprio un incubo, ma io non notare che questa domanda è stato aperto un po 'e anche con il richiamo della generosità non ha ottenuto ancora molte risposte - anche se ora hanno due risposte a scegliere, il mio utilizzando la funzione speciale __test__ di doctest, @ Codeape di utilizzare la funzione __IP.magic_run peculiare di IronPython. Io preferisco la mia, perché non si basa su nulla interno o privi di documenti - __test__ È una fe documentataNatura della doctest, mentre __IP, con quei due incombente sottolineature importanti, urlare "interni profondi, non toccare" a me; -) ... se si rompe in occasione dell'uscita punto successivo non sarei affatto sorpreso. Eppure, questione di gusto -. Che risposta può forse essere considerato più "comoda"

Ma, questo è esattamente il mio punto: la convenienza può venire ad un prezzo enorme in termini di rinuncia semplicità, trasparenza, e / o elusione delle caratteristiche interne / non documentate / instabili; così, come una lezione per tutti noi, la meno magia nera & c abbiamo possono farla franca (anche a costo di rinunciare a un epsilon di convenienza qua e là), il più felice saremo tutti nel lungo periodo (e il più felice faremo altri sviluppatori che hanno bisogno di sfruttare le nostre attuali sforzi in futuro).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top