Esecuzione unittest con tipica struttura di directory di test


700

La struttura di directory molto comune anche per un semplice modulo Python sembra essere quella di separare i test unitari nella propria testdirectory:

new_project/
    antigravity/
        antigravity.py
    test/
        test_antigravity.py
    setup.py
    etc.

per esempio vedere questo howto del progetto Python .

La mia domanda è semplicemente Qual è il solito modo di eseguire effettivamente i test? Ho il sospetto che questo sia ovvio per tutti tranne me, ma non puoi semplicemente eseguire python test_antigravity.pydalla directory di test in quanto import antigravitynon funzionerà poiché il modulo non è sul percorso.

So che potrei modificare PYTHONPATH e altri trucchi relativi al percorso di ricerca, ma non posso credere che sia il modo più semplice: va bene se sei lo sviluppatore ma non è realistico aspettarsi che i tuoi utenti utilizzino se vogliono solo controllare che i test siano passando.

L'altra alternativa è solo quella di copiare il file di test nell'altra directory, ma sembra un po 'stupido e manca il punto di averli in una directory separata per cominciare.

Quindi, se avessi appena scaricato la fonte nel mio nuovo progetto, come eseguiresti i test unitari? Preferirei una risposta che mi permetta di dire ai miei utenti: "Per eseguire i test unitari fai X."


5
@EMP La soluzione corretta quando è necessario impostare il percorso di ricerca è ... impostare il percorso di ricerca. Che tipo di soluzione ti aspettavi?
Carl Meyer,

7
@CarlMeyer un'altra soluzione migliore è utilizzare l' unittestinterfaccia della riga di comando come descritto nella mia risposta di seguito, quindi non è necessario aggiungere la directory al percorso.
Pierre,

13
Anch'io. Ho appena iniziato a scrivere i miei primi test unitari per un piccolo progetto Python e ho impiegato diversi giorni a cercare di ragionare sul fatto che non posso eseguire prontamente un test mantenendo i miei sorgenti in una directory src e test in una directory di test, apparentemente con uno qualsiasi dei framework di test esistenti. Alla fine accetterò le cose, troverò un modo; ma questa è stata un'introduzione molto frustrante. (E io sono un veterano di unit test fuori da Python.)
Ates Goral,

Risposte:


656

La migliore soluzione a mio avviso è quella di utilizzare l' unittest interfaccia della riga di comando che aggiungerà la directory in sys.pathmodo che non sia necessario (fatto in TestLoaderclasse).

Ad esempio per una struttura di directory come questa:

new_project
├── antigravity.py
└── test_antigravity.py

Puoi semplicemente eseguire:

$ cd new_project
$ python -m unittest test_antigravity

Per una struttura di directory come la tua:

new_project
├── antigravity
   ├── __init__.py         # make it a package
   └── antigravity.py
└── test
    ├── __init__.py         # also make test a package
    └── test_antigravity.py

E nei moduli di test all'interno del testpacchetto, è possibile importare il antigravitypacchetto e i suoi moduli come al solito:

# import the package
import antigravity

# import the antigravity module
from antigravity import antigravity

# or an object inside the antigravity module
from antigravity.antigravity import my_object

Esecuzione di un singolo modulo di test:

Per eseguire un singolo modulo di test, in questo caso test_antigravity.py:

$ cd new_project
$ python -m unittest test.test_antigravity

Basta fare riferimento al modulo di test nello stesso modo in cui lo si importa.

Esecuzione di un singolo caso di prova o metodo di prova:

Inoltre è possibile eseguire un TestCasemetodo di test singolo o singolo:

$ python -m unittest test.test_antigravity.GravityTestCase
$ python -m unittest test.test_antigravity.GravityTestCase.test_method

Esecuzione di tutti i test:

È inoltre possibile utilizzare il rilevamento test che rileverà ed eseguirà tutti i test per te, devono essere moduli o pacchetti denominati test*.py(possono essere modificati con il -p, --patternflag):

$ cd new_project
$ python -m unittest discover
$ # Also works without discover for Python 3
$ # as suggested by @Burrito in the comments
$ python -m unittest

Questo eseguirà tutti i test*.pymoduli all'interno del testpacchetto.


53
python -m unittest discovertroverà ed eseguirà i test nella testdirectory se sono nominati test*.py. Se hai chiamato la sottodirectory tests, usa python -m unittest discover -s testse se hai chiamato i file di test antigravity_test.py, usa python -m unittest discover -s tests -p '*test.py' I nomi dei file possono usare i trattini bassi ma non i trattini.
Mike3d0g,

10
Questo per me su Python 3 non riesce con l'errore a ImportError: No module named 'test.test_antigravity'causa di un conflitto con il sottomodulo di test della libreria unittest. Forse un esperto può confermare e modificare il nome della sottodirectory di risposta, ad esempio "test" (plurale).
expz

9
Il mio test_antigravity.pygenera ancora un errore di importazione per entrambi import antigravitye from antigravity import antigravity, anche. Ho entrambi i __init_.pyfile e sto chiamando python3 -m unittest discoverdalla new projectdirectory. Cos'altro può essere sbagliato?
imrek,

19
il file test/__init__.pyè cruciale qui, anche se vuoto
Francois

3
@ Mike3d0g non sono sicuro di voler dire che il nome della directory testè speciale ... ma solo per la cronaca, non lo è. : P python -m unittest discoverfunziona con i file di prova nel tests/bene come test/.
Ryan,

49

La soluzione più semplice per i tuoi utenti è fornire uno script eseguibile ( runtests.pyo alcuni di questi) che avvii l'ambiente di test necessario, incluso, se necessario, l'aggiunta sys.pathtemporanea della directory del progetto principale . Questo non richiede agli utenti di impostare variabili di ambiente, qualcosa del genere funziona bene in uno script bootstrap:

import sys, os

sys.path.insert(0, os.path.dirname(__file__))

Quindi le tue istruzioni per gli utenti possono essere semplici come " python runtests.py".

Naturalmente, se il percorso di cui hai davvero bisogno è os.path.dirname(__file__), non è necessario aggiungerlo sys.pathaffatto; Python mette sempre la directory dello script attualmente in esecuzione all'inizio di sys.path, quindi, a seconda della struttura della directory, semplicemente trovare la tua runtests.pynel posto giusto potrebbe essere tutto ciò che è necessario.

Inoltre, il modulo unittest in Python 2.7+ (che è backport come unittest2 per Python 2.6 e precedenti) ora ha incorporato il rilevamento dei test , quindi il naso non è più necessario se si desidera il rilevamento automatico dei test: le istruzioni per l'utente possono essere semplici come python -m unittest discover.


Ho messo alcuni test in una sottocartella come "Major Major". Possono funzionare con Python -m unittest discover, ma come posso scegliere di eseguirne solo uno. Se eseguo python -m unittest tests / testxxxxx, allora fallisce per il problema del percorso. Poiché la modalità di recupero risolve tutto ciò che mi aspetto che ci sia un altro trucco per risolvere il problema del percorso senza la correzione del percorso di codifica manuale che suggerisci nel primo punto
Frederic Bazin,

2
@FredericBazin Non utilizzare il rilevamento se si desidera solo un singolo test o file di test, basta nominare il modulo che si desidera eseguire. Se lo si chiama come percorso punteggiato di un modulo (piuttosto che come percorso di un file), può capire correttamente il percorso di ricerca. Vedi la risposta di Peter per maggiori dettagli.
Carl Meyer,

Questo hack è stato utile in uno scenario in cui ho dovuto eseguire qualcosa di simile python -m pdb tests\test_antigravity.py. All'interno di pdb, ho eseguito ciò sys.path.insert(0, "antigravity")che ha permesso di risolvere l'istruzione import come se stessi eseguendo il modulo.
ixe013,

23

Generalmente creo uno script "run tests" nella directory del progetto (quello comune sia alla directory sorgente testche) che carica la mia suite "All Tests". Questo di solito è un codice boilerplate, quindi posso riutilizzarlo da un progetto all'altro.

run_tests.py:

import unittest
import test.all_tests
testSuite = test.all_tests.create_test_suite()
text_runner = unittest.TextTestRunner().run(testSuite)

test / all_tests.py (da Come eseguo tutti i test di unità Python in una directory? )

import glob
import unittest

def create_test_suite():
    test_file_strings = glob.glob('test/test_*.py')
    module_strings = ['test.'+str[5:len(str)-3] for str in test_file_strings]
    suites = [unittest.defaultTestLoader.loadTestsFromName(name) \
              for name in module_strings]
    testSuite = unittest.TestSuite(suites)
    return testSuite

Con questa configurazione, puoi davvero solo include antigravitynei tuoi moduli di test. Il rovescio della medaglia è che avresti bisogno di più codice di supporto per eseguire un particolare test ... Li eseguo sempre ogni volta.


1
Volevo anche uno run testsscript nella directory del progetto e ho trovato un modo molto più pulito per farlo. Altamente raccomandato.
z33k,

18

Dall'articolo a cui sei collegato:

Crea un file test_modulename.py e inseriscici i test unittest. Poiché i moduli di test si trovano in una directory separata dal codice, potrebbe essere necessario aggiungere la directory principale del modulo a PYTHONPATH per eseguirli:

$ cd /path/to/googlemaps

$ export PYTHONPATH=$PYTHONPATH:/path/to/googlemaps/googlemaps

$ python test/test_googlemaps.py

Infine, esiste un altro framework di unit test per Python (è così importante!), Nose. nose aiuta a semplificare ed estendere il framework unittest incorporato (può, ad esempio, trovare automaticamente il codice di test e configurare PYTHONPATH per te), ma non è incluso con la distribuzione standard di Python.

Forse dovresti guardare il naso come suggerisce?


3
Sì, questo funziona (per me), ma sto davvero chiedendo le istruzioni più semplici che posso dare agli utenti del mio modulo per far loro eseguire i test. La modifica del percorso potrebbe effettivamente essere, ma sto cercando qualcosa di più diretto.
Major Major,

4
Come sarà il tuo percorso Python dopo aver lavorato su un centinaio di progetti? Devo entrare manualmente e ripulire il mio percorso? Se è così, questo è un design odioso!
jeremyjjbrown,

11

Ho avuto lo stesso problema, con una cartella di unit test separata. Dai suggerimenti citati aggiungo il percorso di origine assoluto a sys.path.

Il vantaggio della seguente soluzione è che si può eseguire il file test/test_yourmodule.pysenza prima cambiarlo nella directory test:

import sys, os
testdir = os.path.dirname(__file__)
srcdir = '../antigravity'
sys.path.insert(0, os.path.abspath(os.path.join(testdir, srcdir)))

import antigravity
import unittest

9

se si esegue "python setup.py develop", il pacchetto sarà nel percorso. Ma potresti non volerlo fare perché potresti infettare l'installazione di Python di sistema, motivo per cui esistono strumenti come virtualenv e buildout .


7

Soluzione / Esempio per il modulo unittest di Python

Data la seguente struttura del progetto:

ProjectName
 ├── project_name
 |    ├── models
 |    |    └── thing_1.py
 |    └── __main__.py
 └── test
      ├── models
      |    └── test_thing_1.py
      └── __main__.py

È possibile eseguire il progetto dalla directory principale con python project_name, che chiama ProjectName/project_name/__main__.py.


Per eseguire i test con python test, efficacemente in esecuzione ProjectName/test/__main__.py, è necessario effettuare le seguenti operazioni:

1) Trasforma la tua test/modelsdirectory in un pacchetto aggiungendo un __init__.pyfile. Ciò rende i casi di test all'interno della directory secondaria accessibili dalla testdirectory padre .

# ProjectName/test/models/__init__.py

from .test_thing_1 import Thing1TestCase        

2) Modificare il percorso del sistema in test/__main__.pyper includere la project_namedirectory.

# ProjectName/test/__main__.py

import sys
import unittest

sys.path.append('../project_name')

loader = unittest.TestLoader()
testSuite = loader.discover('test')
testRunner = unittest.TextTestRunner(verbosity=2)
testRunner.run(testSuite)

Ora puoi importare correttamente le cose dai project_nametuoi test.

# ProjectName/test/models/test_thing_1.py    

import unittest
from project_name.models import Thing1  # this doesn't work without 'sys.path.append' per step 2 above

class Thing1TestCase(unittest.TestCase):

    def test_thing_1_init(self):
        thing_id = 'ABC'
        thing1 = Thing1(thing_id)
        self.assertEqual(thing_id, thing.id)

5

Utilizzare setup.py developper rendere la directory di lavoro parte dell'ambiente Python installato, quindi eseguire i test.


Questo mi invalid command 'develop'dà un e questa opzione non è menzionata se chiedo setup.py --help-commands. Deve esserci qualcosa in setup.pysé per farlo funzionare?
Major Major,

Va bene - il problema era che mancava un import setuptoolsmio setup.pyfile. Ma suppongo che ciò dimostra che questo non funzionerà continuamente per i moduli di altre persone.
Maggiore maggiore,

1
Se si dispone di pip , è possibile utilizzarlo per installare il pacchetto in modalità "modificabile" : pip install -e .allo stesso modo si aggiunge il pacchetto all'ambiente Python senza copiare l'origine, consentendo di continuare a modificarlo dove si trova.
Eric Smith,

pip install -e .è esattamente la stessa cosa python setup.py develop, ti consente setup.pydi usare setuptools anche se in realtà non funziona, quindi funziona in entrambi i modi.
Carl Meyer,

5

Se si utilizza VS Code e i test si trovano sullo stesso livello del progetto, l'esecuzione e il debug del codice non funzionano immediatamente. Quello che puoi fare è cambiare il tuo file launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python",
            "type": "python",
            "request": "launch",
            "stopOnEntry": false,
            "pythonPath": "${config:python.pythonPath}",
            "program": "${file}",
            "cwd": "${workspaceRoot}",
            "env": {},
            "envFile": "${workspaceRoot}/.env",
            "debugOptions": [
                "WaitOnAbnormalExit",
                "WaitOnNormalExit",
                "RedirectOutput"
            ]
        }    
    ]
}

La linea chiave qui è envFile

"envFile": "${workspaceRoot}/.env",

Nella radice del progetto aggiungi il file .env

All'interno del tuo file .env aggiungi il percorso alla radice del tuo progetto. Questo verrà aggiunto temporaneamente

PYTHONPATH = C: \ TUO \ PITONE \ PROJECT \ ROOT_DIRECTORY

percorso del progetto e sarai in grado di utilizzare i test delle unità di debug da VS Code


5

Ho notato che se esegui l'interfaccia della riga di comando unittest dalla tua directory "src", le importazioni funzionano correttamente senza modifiche.

python -m unittest discover -s ../test

Se vuoi metterlo in un file batch nella directory del tuo progetto, puoi farlo:

setlocal & cd src & python -m unittest discover -s ../test

5

Ho avuto lo stesso problema da molto tempo. Quello che ho scelto di recente è la seguente struttura di directory:

project_path
├── Makefile
├── src
   ├── script_1.py
   ├── script_2.py
   └── script_3.py
└── tests
    ├── __init__.py
    ├── test_script_1.py
    ├── test_script_2.py
    └── test_script_3.py

e nello __init__.pyscript della cartella test scrivo quanto segue:

import os
import sys
PROJECT_PATH = os.getcwd()
SOURCE_PATH = os.path.join(
    PROJECT_PATH,"src"
)
sys.path.append(SOURCE_PATH)

Molto importante per la condivisione del progetto è il Makefile, perché impone l'esecuzione corretta degli script. Ecco il comando che ho inserito nel Makefile:

run_tests:
    python -m unittest discover .

Il Makefile è importante non solo per il comando che esegue ma anche per la sua provenienza . Se eseguissi il cd nei test e lo facessi python -m unittest discover ., non funzionerebbe perché lo script init in unit_tests chiama os.getcwd (), che indicherebbe quindi il percorso assoluto errato (che verrebbe aggiunto a sys.path e ti mancherebbe la tua cartella di origine). Gli script verrebbero eseguiti poiché discovery trova tutti i test, ma non funzionerebbero correttamente. Quindi il Makefile è lì per evitare di dover ricordare questo problema.

Mi piace molto questo approccio perché non devo toccare la mia cartella src, i miei test unitari o le mie variabili di ambiente e tutto funziona senza intoppi.

Fammi sapere se vi piace.

Spero che aiuti,


4

Di seguito è la mia struttura del progetto:

ProjectFolder:
 - project:
     - __init__.py
     - item.py
 - tests:
     - test_item.py

Ho trovato meglio importare nel metodo setUp ():

import unittest
import sys    

class ItemTest(unittest.TestCase):

    def setUp(self):
        sys.path.insert(0, "../project")
        from project import item
        # further setup using this import

    def test_item_props(self):
        # do my assertions

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

4

Qual è il solito modo di eseguire effettivamente i test

Uso Python 3.6.2

cd new_project

pytest test/test_antigravity.py

Per installare pytest :sudo pip install pytest

Non ho impostato alcuna variabile di percorso e le mie importazioni non falliscono con la stessa struttura del progetto "test".

Ho commentato questa roba: in if __name__ == '__main__'questo modo:

test_antigravity.py

import antigravity

class TestAntigravity(unittest.TestCase):

    def test_something(self):

        # ... test stuff here


# if __name__ == '__main__':
# 
#     if __package__ is None:
# 
#         import something
#         sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
#         from .. import antigravity
# 
#     else:
# 
#         from .. import antigravity
# 
#     unittest.main()

4

È possibile utilizzare il wrapper che esegue selezionati o tutti i test.

Per esempio:

./run_tests antigravity/*.py

o per eseguire tutti i test in modo ricorsivo utilizzare globbing ( tests/**/*.py) (abilita da shopt -s globstar).

Il wrapper può sostanzialmente usare argparseper analizzare gli argomenti come:

parser = argparse.ArgumentParser()
parser.add_argument('files', nargs='*')

Quindi caricare tutti i test:

for filename in args.files:
    exec(open(filename).read())

quindi aggiungili nella tua suite di test (usando inspect):

alltests = unittest.TestSuite()
for name, obj in inspect.getmembers(sys.modules[__name__]):
    if inspect.isclass(obj) and name.startswith("FooTest"):
        alltests.addTest(unittest.makeSuite(obj))

ed eseguili:

result = unittest.TextTestRunner(verbosity=2).run(alltests)

Controlla questo esempio per maggiori dettagli.

Vedi anche: Come eseguire tutti i test unit Python in una directory?


4

Python 3+

Aggiunta a @Pierre

Utilizzando la unitteststruttura di directory in questo modo:

new_project
├── antigravity
   ├── __init__.py         # make it a package
   └── antigravity.py
└── test
    ├── __init__.py         # also make test a package
    └── test_antigravity.py

Per eseguire il modulo di test test_antigravity.py:

$ cd new_project
$ python -m unittest test.test_antigravity

O un singolo TestCase

$ python -m unittest test.test_antigravity.GravityTestCase

Obbligatorio non dimenticare il __init__.pyanche se vuoto altrimenti non funzionerà.


2

Non puoi importare dalla directory principale senza un po 'di voodoo. Ecco ancora un altro modo che funziona con almeno Python 3.6.

Innanzitutto, disporre di un file test / context.py con il seguente contenuto:

import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

Quindi avere la seguente importazione nel file test / test_antigravity.py:

import unittest
try:
    import context
except ModuleNotFoundError:
    import test.context    
import antigravity

Si noti che il motivo di questa clausola try-tranne è quello

  • import test.context ha esito negativo quando viene eseguito con "python test_antigravity.py" e
  • il contesto di importazione ha esito negativo quando viene eseguito con "python -m unittest" dalla directory new_project.

Con questo trucco entrambi funzionano.

Ora puoi eseguire tutti i file di test nella directory di test con:

$ pwd
/projects/new_project
$ python -m unittest

oppure esegui un singolo file di test con:

$ cd test
$ python test_antigravity

Ok, non è molto più bello che avere il contenuto di context.py in test_antigravity.py, ma forse un po '. Suggerimenti sono ben accetti


2

Se nella directory di test sono presenti più directory, è necessario aggiungere un __init__.pyfile a ciascuna directory .

/home/johndoe/snakeoil
└── test
    ├── __init__.py        
    └── frontend
        └── __init__.py
        └── test_foo.py
    └── backend
        └── __init__.py
        └── test_bar.py

Quindi, per eseguire tutti i test contemporaneamente, eseguire:

python -m unittest discover -s /home/johndoe/snakeoil/test -t /home/johndoe/snakeoil

Fonte: python -m unittest -h

  -s START, --start-directory START
                        Directory to start discovery ('.' default)
  -t TOP, --top-level-directory TOP
                        Top level directory of project (defaults to start
                        directory)

1

Questo script BASH eseguirà la directory di test unittest di Python da qualsiasi parte del file system, indipendentemente dalla directory di lavoro in cui ti trovi.

Questo è utile quando ti trovi nella directory di lavoro ./srco ./examplehai bisogno di un rapido test unitario:

#!/bin/bash

this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

Non è necessario che un test/__init__.pyfile carichi il pacchetto / sovraccarico di memoria durante la produzione.


1

In questo modo ti consentirà di eseguire gli script di test ovunque tu voglia senza fare confusione con le variabili di sistema dalla riga di comando.

Ciò aggiunge la cartella principale del progetto al percorso python, con la posizione trovata relativa allo script stesso, non relativa alla directory di lavoro corrente.

import sys, os

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

Aggiungilo all'inizio di tutti gli script di test. Ciò aggiungerà la cartella principale del progetto al percorso di sistema, quindi qualsiasi modulo importato da lì funzionerà ora. E non importa da dove esegui i test.

È ovviamente possibile modificare il file project_path_hack in modo che corrisponda al percorso della cartella principale del progetto.


0

Se stai cercando una soluzione solo per la riga di comando:

Basato sulla seguente struttura di directory (generalizzata con una directory di origine dedicata):

new_project/
    src/
        antigravity.py
    test/
        test_antigravity.py

Windows : (in new_project)

$ set PYTHONPATH=%PYTHONPATH%;%cd%\src
$ python -m unittest discover -s test

Vedi questa domanda se vuoi usarlo in un batch for-loop.

Linux : (in new_project)

$ export PYTHONPATH=$PYTHONPATH:$(pwd)/src  [I think - please edit this answer if you are a Linux user and you know this]
$ python -m unittest discover -s test

Con questo approccio, è anche possibile aggiungere più directory a PYTHONPATH, se necessario.


0

Dovresti davvero usare lo strumento pip.

Utilizzare pip install -e .per installare il pacchetto in modalità di sviluppo. Questa è una buona pratica, raccomandata da pytest (consultare la documentazione relativa alle buone pratiche , dove è possibile trovare anche due layout di progetto da seguire).


Perché votare questa risposta? Ho letto la risposta accettata e anche se non è male, pytestè molto meglio eseguire i test, a causa dell'output della console che ottieni, a colori, con informazioni sullo stack stack e informazioni dettagliate sull'errore di asserzione.
aliopi,
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.