Dove vanno i test unitari di Python?


490

Se stai scrivendo una libreria o un'app, dove vanno i file dei test unitari?

È bello separare i file di test dal codice dell'app principale, ma è scomodo metterli in una sottodirectory "test" all'interno della directory radice dell'app, perché rende più difficile importare i moduli che testerai.

C'è una buona pratica qui?


4
Esistono molte risposte accettabili a questa domanda e ciò non la rende una domanda per un sito di domande / risposte. Le persone devono capire che non tutte le domande molto utili possono essere poste su SO. I formatori scelgono per questo e dobbiamo seguire le loro regole.
Wouter J

121
Le persone hanno anche bisogno di capire che quando le pagine SO finiscono come un successo per una ricerca su Google, forse è meglio seguire semplicemente il flusso piuttosto che attenersi alla dottrina ufficiale SO.
Christopher Barber,

6
Le persone di @ChristopherBarber devono anche capire che le decisioni e la chiusura aggressiva sono ciò che ha finito per guadagnare una così terribile reputazione come sito che preferirei non andare a fare domande a meno che davvero, davvero non abbia opzioni migliori.
Stefano Borini,

Risposte:


200

Per un file module.py, il test unitario dovrebbe normalmente essere chiamato test_module.py, seguendo le convenzioni di denominazione Pythonic.

Ci sono molti posti comunemente accettati da mettere test_module.py:

  1. Nella stessa directory di module.py.
  2. In ../tests/test_module.py(allo stesso livello della directory del codice).
  3. In tests/test_module.py(un livello nella directory del codice).

Preferisco il numero 1 per la sua semplicità nel trovare i test e importarli. Qualunque sia il sistema di compilazione che stai utilizzando può essere facilmente configurato per eseguire i file a partire da test_. In realtà, il modello predefinito unittestutilizzato per il rilevamento dei test ètest*.py .


12
Il protocollo load_tests cerca i file denominati test * .py per impostazione predefinita. Inoltre, questo risultato superiore di Google e questa documentazione unittest utilizzano entrambi il formato test_module.py.
dfarrell07,

6
L'uso di foo_test.py può salvare una o due sequenze di tasti quando si utilizza il completamento della scheda poiché non si dispone di un mucchio di file che iniziano con 'test_'.
ginepro

11
@juniper, seguendo i tuoi pensieri module_test apparirà nel completamento automatico quando stai scrivendo. Questo può essere confuso o fastidioso.
Medeiros,

11
Durante la distribuzione del codice, non vogliamo distribuire i test nelle nostre sedi di produzione. Quindi, averli in una directory './tests/test_blah.py' è facile da strappare quando facciamo le distribuzioni. Inoltre, alcuni test accettano file di dati di esempio e averli in una directory di test è fondamentale per non distribuire dati di test.
Kevin J. Rice,

1
@ KevinJ.Rice Non dovresti testare che il codice funzioni nelle tue sedi di produzione?
endolith,

67

Solo 1 file di prova

Se sono presenti solo 1 file di test, si consiglia di inserirlo in una directory di livello superiore:

module/
    lib/
        __init__.py
        module.py
    test.py

Eseguire il test in CLI

python test.py

Molti file di test

Se ha molti file di prova, inseriscilo in una testscartella:

module/
    lib/
        __init__.py
        module.py
    tests/
        test_module.py
        test_module_function.py
# test_module.py

import unittest
from lib import module

class TestModule(unittest.TestCase):
    def test_module(self):
        pass

if __name__ == '__main__':
    unittest.main()

Eseguire il test in CLI

# In top-level /module/ folder
python -m tests.test_module
python -m tests.test_module_function

Uso unittest discovery

unittest discovery troverà tutti i test nella cartella del pacchetto.

Creare una __init__.pyin tests/cartella

module/
    lib/
        __init__.py
        module.py
    tests/
        __init__.py
        test_module.py
        test_module_function.py

Eseguire il test in CLI

# In top-level /module/ folder

# -s, --start-directory (default current directory)
# -p, --pattern (default test*.py)

python -m unittest discover

Riferimento

Quadro di unit test


51

Una pratica comune è quella di mettere la directory dei test nella stessa directory principale del modulo / pacchetto. Quindi se il tuo modulo è stato chiamato foo.py il layout della tua directory sarebbe simile a:

parent_dir/
  foo.py
  tests/

Ovviamente non esiste un modo per farlo. È inoltre possibile creare una sottodirectory test e importare il modulo utilizzando l'importazione assoluta .

Ovunque metti i test, ti consiglio di usare il naso per eseguirli. Il naso cerca nei tuoi elenchi per i test. In questo modo, puoi mettere i test ovunque abbiano più senso organizzativo.


16
Mi piacerebbe farlo, ma non riesco a farlo funzionare. Per eseguire i test, mi trovo in parent_dir e digito: "python tests \ foo_test.py", e in foo_test.py: "da ..foo importa questo, quello, l'altro" Che non riesce con: "ValueError: Attempted importazione relativa in non pacchetto "Sia parent_dir che i test hanno un init .py in essi, quindi non sono sicuro del perché non siano pacchetti. Ho il sospetto che sia perché lo script di livello superiore che esegui dalla riga di comando non può essere considerato (parte di) un pacchetto, anche se si trova in una directory con un init .py. Quindi, come posso eseguire i test? Sto lavorando su Windows oggi, benedici le mie calze di cotone.
Jonathan Hartley,

4
Il modo migliore - ho scoperto - di eseguire test unitari è installare la tua libreria / programma, quindi eseguire unit test con nose. Consiglierei virtualenv e virtualenvwrapper per renderlo molto più semplice.
Cristian,

@Tartley - è necessario un file init .py nella directory 'test' per far funzionare le importazioni di assoluzione. Ho questo metodo che funziona con il naso, quindi non sono sicuro del motivo per cui hai problemi.
cmcginty,

4
Grazie Casey - ma ho un file .py di init in tutte le directory pertinenti. Non so cosa faccio di sbagliato, ma ho questo problema su tutti i miei progetti Python e non riesco a capire perché nessun altro lo sappia. Oh cara.
Jonathan Hartley,

8
Una soluzione al mio problema, con Python2.6 o più recente, per eseguire i test dalla radice del progetto usando: python -m project.package.tests.module_tests (invece di python project / package / tests / module_tests.py) . Ciò mette la directory del modulo di test sul percorso, quindi i test possono quindi eseguire un'importazione relativa nella directory principale per ottenere il modulo sotto test.
Jonathan Hartley,

32

Avevamo la stessa domanda quando scrivevamo Pythoscope ( https://pypi.org/project/pythoscope/ ), che genera test unitari per i programmi Python. Abbiamo interrogato le persone sui test nell'elenco Python prima di scegliere una directory, c'erano molte opinioni diverse. Alla fine abbiamo scelto di mettere una directory "test" nella stessa directory del codice sorgente. In quella directory generiamo un file di prova per ciascun modulo nella directory principale.


2
Eccezionale! Ho appena eseguito Pythoscope (ottimo logo), su un codice legacy ed è stato fantastico. Best practice istantanee e puoi convincere altri sviluppatori a compilare gli stub di test ora non riusciti. Ora per collegarlo al bambù? :)
Will

27

Tendo anche a mettere i miei test unitari nel file stesso, come osserva Jeremy Cantrell, anche se tendo a non mettere la funzione test nel corpo principale, ma piuttosto a mettere tutto in un

if __name__ == '__main__':
   do tests...

bloccare. Questo finisce per aggiungere documentazione al file come 'codice di esempio' per come usare il file python che stai testando.

Dovrei aggiungere, tendo a scrivere moduli / classi molto stretti. Se i tuoi moduli richiedono un numero molto elevato di test, puoi inserirli in un altro, ma anche in questo caso aggiungerei ancora:

if __name__ == '__main__':
   import tests.thisModule
   tests.thisModule.runtests

Questo consente a chiunque legga il tuo codice sorgente di sapere dove cercare il codice di prova.


"come Jeremy Cantrell sopra nota" dove?
endolith

Mi piace questo modo di lavorare. Il più semplice degli editor può essere configurato per eseguire il file con un tasto di scelta rapida, quindi quando si visualizza il codice, è possibile eseguire i test in un istante. Sfortunatamente in lingue diverse da Python questo può sembrare orribile, quindi se sei in un negozio C ++ o Java i tuoi colleghi potrebbero disapprovarlo. Inoltre non funziona bene con gli strumenti di copertura del codice.
Keeely,

19

Ogni tanto mi ritrovo a controllare l'argomento del posizionamento del test, e ogni volta la maggioranza raccomanda una struttura di cartelle separata accanto al codice della libreria, ma trovo che ogni volta gli argomenti sono gli stessi e non sono così convincenti. Finisco per mettere i miei moduli di prova da qualche parte accanto ai moduli principali.

Il motivo principale per questo è: refactoring .

Quando muovo le cose voglio che i moduli di test si muovano con il codice; è facile perdere i test se si trovano in un albero separato. Siamo onesti, prima o poi finirai con una struttura di cartelle totalmente diversa, come django , pallone e molti altri. Va bene se non ti interessa.

La domanda principale che dovresti porti è questa:

Sto scrivendo:

  • a) biblioteca riutilizzabile o
  • b) costruire un progetto piuttosto che raggruppare insieme alcuni moduli semi-separati?

Se una:

Una cartella separata e lo sforzo extra per mantenere la sua struttura potrebbero essere più adatti. Nessuno si lamenterà del fatto che i test vengano distribuiti alla produzione .

Ma è altrettanto facile escludere i test dalla distribuzione quando sono mescolati con le cartelle principali; inseriscilo in setup.py :

find_packages("src", exclude=["*.tests", "*.tests.*", "tests.*", "tests"]) 

Se b:

Potresti desiderare - come ognuno di noi fa - di scrivere librerie riutilizzabili, ma il più delle volte la loro vita è legata alla vita del progetto. La capacità di mantenere facilmente il tuo progetto dovrebbe essere una priorità.

Quindi se hai fatto un buon lavoro e il tuo modulo è adatto per un altro progetto, probabilmente verrà copiato - non biforcuto o trasformato in una libreria separata - in questo nuovo progetto e spostando i test che si trovano accanto ad esso nella stessa struttura di cartelle è facile rispetto ai test di pesca in un pasticcio che era diventata una cartella di test separata. (Potresti sostenere che non dovrebbe essere un disastro in primo luogo, ma cerchiamo di essere realistici qui).

Quindi la scelta è ancora tua, ma direi che con i test confusi si ottengono le stesse cose che con una cartella separata, ma con meno sforzi per mantenere le cose in ordine.


1
qual è il problema con la distribuzione dei test alla produzione, in realtà? nella mia esperienza, è molto utile: consente agli utenti di eseguire effettivamente test sul loro ambiente ... quando ricevi segnalazioni di bug, puoi chiedere all'utente di eseguire la suite di test per assicurarsi che tutto sia a posto e inviare hot patch per il test suite direttamente ... inoltre, non è perché hai messo i tuoi test in module.tests che finirà in produzione, a meno che tu non abbia fatto qualcosa di sbagliato nel tuo file di installazione ...
anarcat

2
Come ho scritto nella mia risposta, di solito non separo i test. Ma. Mettere i test nel pacchetto di distribuzione può portare all'esecuzione di roba che normalmente non si vorrebbe in ambiente di produzione (ad es. Un codice a livello di modulo). Dipende ovviamente da come vengono scritti i test, ma per essere al sicuro li si lascia fuori, nessuno eseguirà qualcosa di dannoso per errore. Non sono contrario a includere i test nelle distribuzioni, ma capisco che, come regola generale, è più sicuro. E metterli in una cartella separata rende super facile non includerli in dist.
Janusz Skonieczny,

15

Uso una tests/directory e quindi importare i principali moduli dell'applicazione usando le relative importazioni. Quindi in MyApp / tests / foo.py potrebbero esserci:

from .. import foo

per importare il MyApp.foomodulo.


5
"ValueError: Tentativo di importazione relativa in non pacchetto"
cprn

12

Non credo che esista una "migliore pratica" consolidata.

Ho inserito i miei test in un'altra directory al di fuori del codice dell'app. Aggiungo quindi la directory principale dell'app a sys.path (che consente di importare i moduli da qualsiasi luogo) nel mio script Runner di test (che fa anche altre cose) prima di eseguire tutti i test. In questo modo non devo mai rimuovere la directory dei test dal codice principale quando lo rilascio, risparmiando tempo e fatica, anche se una quantità così esigua.


3
Aggiungo quindi la directory dell'app principale a sys.path Il problema è se i tuoi test contengono alcuni moduli helper (come il server web di test) e devi importare tali moduli helper dai test appropriati.
Piotr Dobrogost,

Questo è quello che sembra per me:os.sys.path.append(os.dirname('..'))
Matt M.

11

Dalla mia esperienza nello sviluppo di framework di test in Python, suggerirei di mettere i test unit di Python in una directory separata. Mantenere una struttura di directory simmetrica. Ciò sarebbe utile per impacchettare solo le librerie principali e non impacchettare i test unitari. Di seguito è implementato attraverso un diagramma schematico.

                              <Main Package>
                               /          \
                              /            \
                            lib           tests
                            /                \
             [module1.py, module2.py,  [ut_module1.py, ut_module2.py,
              module3.py  module4.py,   ut_module3.py, ut_module.py]
              __init__.py]

In questo modo quando impacchettate queste librerie usando un rpm, potete semplicemente impacchettare i moduli della libreria principale (solo). Questo aiuta la manutenibilità in particolare in un ambiente agile.


1
Mi sono reso conto che un potenziale vantaggio di questo approccio è che i test possono essere sviluppati (e forse anche controllati dalla versione) come un'applicazione indipendente. Ovviamente, a ogni vantaggio c'è uno svantaggio: mantenere la simmetria sta fondamentalmente facendo "contabilità a doppia entrata" e rende il refactoring più di un lavoro ingrato.
Jasha,

Domanda delicata di follow-up: perché l'ambiente agile è particolarmente adatto a questo approccio? (cioè che dire del flusso di lavoro agile rende così vantaggiosa una struttura di directory simmetrica?)
Jasha

11

Ti consiglio di controllare alcuni dei principali progetti Python su GitHub e ottenere alcune idee.

Quando il tuo codice diventa più grande e aggiungi più librerie, è meglio creare una cartella di test nella stessa directory in cui hai setup.py e rispecchiare la struttura della directory del progetto per ogni tipo di test (unittest, integrazione, ...)

Ad esempio se hai una struttura di directory come:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
setup.py

Dopo aver aggiunto la cartella test avrai una struttura di directory come:

myPackage/
    myapp/
       moduleA/
          __init__.py
          module_A.py
       moduleB/
          __init__.py
          module_B.py
test/
   unit/
      myapp/
         moduleA/
            module_A_test.py
         moduleB/
            module_B_test.py
   integration/
          myapp/
             moduleA/
                module_A_test.py
             moduleB/
                module_B_test.py
setup.py

Molti pacchetti Python scritti correttamente usano la stessa struttura. Un ottimo esempio è il pacchetto Boto. Controlla https://github.com/boto/boto


1
Si consiglia di utilizzare "test" anziché "test", poiché "test" è un modulo build in Python. docs.python.org/2/library/test.html
brodul

Non sempre .. per esempio matplotlibha sotto matplotlib/lib/matplotlib/tests( github.com/matplotlib/matplotlib/tree/... ), sklearnha sotto scikitelearn/sklearn/tests( github.com/scikit-learn/scikit-learn/tree/master/sklearn/tests )
alpha_989

7

Come lo faccio ...

Struttura delle cartelle:

project/
    src/
        code.py
    tests/
    setup.py

Setup.py punta a src / come il percorso contenente i moduli dei miei progetti, quindi eseguo:

setup.py develop

Il che aggiunge il mio progetto in pacchetti del sito, indicando la mia copia di lavoro. Per eseguire i miei test utilizzo:

setup.py tests

Utilizzando qualsiasi test runner che ho configurato.


Sembra che tu abbia scavalcato il termine "modulo". La maggior parte dei programmatori Python probabilmente penserebbe che il modulo sia il file che hai chiamato code.py. Avrebbe più senso chiamare la directory di livello superiore "progetto".
blokeley,

4

Preferisco la directory dei test di livello superiore. Ciò significa che le importazioni diventano un po 'più difficili. Per questo ho due soluzioni:

  1. Usa setuptools. Poi si può passare test_suite='tests.runalltests.suite'in setup(), e possibile eseguire i test semplicemente:python setup.py test
  2. Impostare PYTHONPATH durante l'esecuzione dei test: PYTHONPATH=. python tests/runalltests.py

Ecco come questa roba è supportata dal codice in M2Crypto:

Se preferisci eseguire test con nosetest, potresti dover fare qualcosa di leggermente diverso.


3

Noi usiamo

app/src/code.py
app/testing/code_test.py 
app/docs/..

In ogni file di test inseriamo ../src/in sys.path. Non è la soluzione più bella ma funziona. Penso che sarebbe fantastico se qualcuno venisse fuori con qualcosa come Maven in Java che ti dà convenzioni standard che funzionano, indipendentemente dal progetto su cui lavori.


1

Se i test sono semplici, inseriscili semplicemente nel docstring: la maggior parte dei framework di test per Python sarà in grado di utilizzarli:

>>> import module
>>> module.method('test')
'testresult'

Per altri test più coinvolti, li metterei dentro ../tests/test_module.pyo dentro tests/test_module.py.


1

In C #, ho generalmente separato i test in un assieme separato.

In Python - finora - ho teso a scrivere doctest, in cui il test si trova nel docstring di una funzione, oppure a metterli nel if __name__ == "__main__"blocco nella parte inferiore del modulo.


0

Quando scrivo un pacchetto chiamato "foo", inserirò i test unitari in un pacchetto separato "foo_test". Moduli e pacchetti secondari avranno quindi lo stesso nome del modulo pacchetto SUT. Ad esempio i test per un modulo foo.xy si trovano in foo_test.xy I file __init__.py di ciascun pacchetto di test contengono quindi una suite AllTest che include tutte le suite di test del pacchetto. setuptools fornisce un modo conveniente per specificare il pacchetto di test principale, in modo che dopo "python setup.py development" puoi semplicemente usare "python setup.py test" o "python setup.py test -s foo_test.x.SomeTestSuite" per solo una suite specifica.


0

Ho messo i miei test nella stessa directory del codice in test (CUT); per foo.pyi test sarà in foo_ut.pyo simile. (Ottimizzo il processo di individuazione del test per trovarli.)

Questo mette i test proprio accanto al codice in un elenco di directory, rendendo ovvio che i test sono lì e rende l'apertura dei test il più semplice possibile quando si trovano in un file separato. (Per gli editor della riga di comando vim foo*e quando si utilizza un browser di file system grafico, fare clic sul file CUT e quindi sul file di test immediatamente adiacente.)

Come altri hanno sottolineato , ciò semplifica anche il refactoring e l'estrazione del codice da utilizzare altrove qualora fosse necessario.

Non mi piace l'idea di mettere i test in un albero di directory completamente diverso; perché rendere più difficile del necessario per gli sviluppatori aprire i test quando aprono il file con il CUT? Non è che la stragrande maggioranza degli sviluppatori sia così entusiasta di scrivere o modificare i test che ignorerà qualsiasi barriera nel farlo, invece di usare la barriera come scusa. (Al contrario, nella mia esperienza; anche quando lo rendi il più semplice possibile, conosco molti sviluppatori che non possono preoccuparsi di scrivere test.)


-2

Di recente ho iniziato a programmare in Python, quindi non ho ancora avuto la possibilità di scoprire le migliori pratiche. Ma ho scritto un modulo che va e trova tutti i test e li esegue.

Quindi ho:

app /
 appfile.py
test/
 appfileTest.py

Dovrò vedere come va mentre avanza verso progetti più grandi.


4
Ciao, CamelCase è un po 'strano nel mondo dei pitoni.
Stuart Axon,
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.