Come posso eseguire tutti i test unit Python in una directory?


315

Ho una directory che contiene i miei test di unità Python. Ogni modulo di unit test ha la forma test _ *. Py . Sto tentando di creare un file chiamato all_test.py che, hai indovinato, eseguirà tutti i file nel modulo di test di cui sopra e restituirà il risultato. Finora ho provato due metodi; entrambi hanno fallito. Mostrerò i due metodi e spero che qualcuno là fuori sappia come farlo correttamente.

Per il mio primo coraggioso tentativo, ho pensato "Se solo importassi tutti i miei moduli di test nel file e poi chiamassi questo unittest.main()doodle, funzionerà, giusto?" Bene, ho scoperto che mi sbagliavo.

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

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

Questo non ha funzionato, il risultato che ho ottenuto è stato:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Per il mio secondo tentativo, però, ok, forse proverò a fare tutto questo test in modo più "manuale". Quindi ho tentato di farlo di seguito:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

Anche questo non ha funzionato, ma sembra così vicino!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Mi sembra di avere una suite di qualche tipo e posso eseguire il risultato. Sono un po 'preoccupato per il fatto che dice che ho solo run=1, sembra che dovrebbe essere run=2, ma è un progresso. Ma come posso passare e visualizzare il risultato sul main? O come faccio praticamente a farlo funzionare in modo da poter semplicemente eseguire questo file e, nel fare ciò, eseguire tutti i test unitari in questa directory?


1
Passa alla risposta di Travis se stai usando Python 2.7+
Rocky l'

hai mai provato a eseguire i test da un oggetto di istanza di test?
Pinocchio,

Vedi questa risposta per una soluzione con una struttura di file di esempio.
Derek Soike,

Risposte:


477

Con Python 2.7 e versioni successive non è necessario scrivere nuovo codice o utilizzare strumenti di terze parti per farlo; l'esecuzione del test ricorsivo tramite la riga di comando è integrata. Inserisci un __init__.pynella tua directory di test e:

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

Puoi leggere di più nella documentazione unittest di Python 2.7 o Python 3.x.


11
i problemi includono: ImportError: la directory di avvio non è importabile:
zinking

6
Almeno con Python 2.7.8 su Linux né l'invocazione da riga di comando mi dà ricorsione. Il mio progetto ha diversi sottoprogetti i cui test unitari vivono nelle rispettive directory "unit_tests / <subproject> / python /". Se specifico un tale percorso, vengono eseguiti i test unitari per quel sottoprogetto, ma con solo "unit_tests" come argomento della directory test non vengono trovati test (invece di tutti i test per tutti i sottoprogetti, come speravo). Qualche suggerimento?
user686249

6
Informazioni sulla ricorsione: il primo comando senza <test_directory> predefinito è "." e recluta i sottomoduli . Cioè, tutte le directory dei test che desideri vengano scoperte devono avere un init .py. In tal caso, verranno trovati dal comando discover. Ho appena provato, ha funzionato.
Emil Stenström,

Questo ha funzionato per me. Ho una cartella di test con quattro file, eseguo questo dal mio terminale Linux, roba fantastica.
JasTonAChair il

5
Grazie! Perché questa non è la risposta accettata? Dal mio punto di vista, la risposta migliore è sempre quella che non richiede dipendenze esterne ...
Jonathan Benn,

108

Potresti usare un test runner che lo farebbe per te. il naso è molto buono per esempio. Quando eseguito, troverà i test nell'albero corrente ed eseguirli.

aggiornato:

Ecco un po 'di codice dai miei giorni pre-naso. Probabilmente non vuoi l'elenco esplicito dei nomi dei moduli, ma forse il resto ti sarà utile.

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)

2
Il vantaggio di questo approccio rispetto all'importazione esplicita di tutti i moduli di test in un modulo test_all.py e alla chiamata di unittest.main () è possibile dichiarare facoltativamente una suite di test in alcuni moduli e non in altri?
Corey Porter,

1
Ho provato il naso e funziona perfettamente. È stato facile da installare ed eseguire nel mio progetto. Sono stato anche in grado di automatizzarlo con poche righe di script, in esecuzione all'interno di un virtualenv. +1 per il naso!
Jesse Webb,

Non sempre fattibile: a volte l'importazione della struttura del progetto può far confondere il naso se tenta di eseguire le importazioni sui moduli.
Chiffa,

4
Si noti che nose è stato "in modalità di manutenzione negli ultimi anni" ed è attualmente consigliato l'uso di nose2 , pytest o semplicemente unittest / unittest2 per nuovi progetti.
Kurt Peek,

hai mai provato a eseguire i test da un oggetto di istanza di test?
Pinocchio,

96

In python 3, se stai usando unittest.TestCase:

  • Devi avere un file vuoto (o altrimenti) __init__.pynella tua testdirectory ( deve essere nominato test/)
  • I tuoi file di test all'interno test/corrispondono al modello test_*.py. Possono trovarsi all'interno di una sottodirectory test/e questi sottodirectory possono essere nominati come qualsiasi cosa.

Quindi, puoi eseguire tutti i test con:

python -m unittest

Fatto! Una soluzione inferiore a 100 linee. Spero che un altro principiante in pitone risparmi tempo trovandolo.


3
Si noti che per impostazione predefinita cerca solo i test nei nomi dei file che iniziano con "test"
Shawabawa,

3
Esatto, la domanda originale si riferiva al fatto che "Ogni modulo di test unitario è nel formato test _ *. Py.", Quindi questa risposta è una risposta diretta. Ora ho aggiornato la risposta per essere più esplicito
tmck-code

1
Grazie, quello che mi mancava per usare la risposta di Travis Bear.
Jeremy Cochoy,

65

Questo è ora possibile direttamente da unittest: unittest.TestLoader.discover .

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)

3
Ho provato anche questo metodo, ho test di coppia, ma funziona perfettamente. Eccellente!!! Ma sono curioso di avere solo 4 test. Insieme eseguono 0,032 secondi, ma quando uso questo metodo per eseguirli tutti, ottengo risultati .... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OKPerché? La differenza, da dove viene?
simkus,

Sto riscontrando problemi nell'esecuzione di un file simile a questo dalla riga di comando. Come dovrebbe essere invocato?
Dustin Michels,

python file.py
massacro

1
Ha funzionato perfettamente! Basta impostarlo nel test / dir e quindi impostare start_id = "./". IMHO, questa risposta è ora (Python 3.7) il modo accettato!
jjwdesign,

È possibile modificare l'ultima riga in ´res = runner.run (suite); sys.exit (0 se res.wasSuccessful () else 1) ´ se si desidera un codice di uscita corretto
Sadap,

32

Bene, studiando un po 'il codice sopra (in particolare usando TextTestRunnere defaultTestLoader), sono stato in grado di avvicinarmi abbastanza. Alla fine ho corretto il mio codice semplicemente passando tutte le suite di test a un costruttore di suite singole, anziché aggiungendole "manualmente", il che ha risolto i miei altri problemi. Quindi ecco la mia soluzione.

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

Sì, probabilmente è più semplice usare il naso piuttosto che farlo, ma questo è un altro punto.


bene, funziona bene per la directory corrente, come invocare il sub-direttamente?
Larry Cai,

Larry, vedi la nuova risposta ( stackoverflow.com/a/24562019/104143 ) per la scoperta di test ricorsivi
Peter Kofler,

hai mai provato a eseguire i test da un oggetto di istanza di test?
Pinocchio,

25

Se vuoi eseguire tutti i test da varie classi di test case e sei felice di specificarli esplicitamente, puoi farlo in questo modo:

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __name__ == "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

    runner = TextTestRunner(verbosity=2)
    runner.run(suite)

dov'è il uclidmio progetto TestSymbolse TestPatternssono sottoclassi di TestCase.


Dai documenti unittest.TestLoader : "Normalmente, non è necessario creare un'istanza di questa classe; il modulo unittest fornisce un'istanza che può essere condivisa come unittest.defaultTestLoader." Inoltre, poiché TestSuiteaccetta un iterabile come argomento, è possibile creare detto iterabile in un ciclo per evitare la ripetizione loader.loadTestsFromTestCase.
Two-Bit Alchemist

@ Two-Bit Alchemist il tuo secondo punto in particolare è bello. Modificherei il codice per includerlo ma non posso verificarlo. (La prima mod lo farebbe sembrare troppo simile a Java per i miei gusti .. anche se mi rendo conto di essere irrazionale (fregateli con i loro nomi delle variabili dei casi di cammello)).
Demented Hedgehog,

Questa è la mia preferita, molto pulita. Sono stato in grado di impacchettarlo e renderlo un argomento nella mia normale riga di comando.
Marco II

15

Ho usato il discovermetodo e un sovraccarico di load_testsper ottenere questo risultato in un numero (minimo, credo) di righe di codice:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

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

Esecuzione su cinque qualcosa del genere

Ran 27 tests in 0.187s
OK

questo è disponibile solo per python2.7, suppongo
Larry Cai

@larrycai Forse, di solito sono su Python 3, a volte su Python 2.7. La domanda non era legata a una versione specifica.
RDS

Sono su Python 3.4 e scopri restituisce una suite, rendendo il loop ridondante.
Dunes,

Per il futuro di Larry: "Molte nuove funzionalità sono state aggiunte a unittest in Python 2.7, incluso il rilevamento dei test. Unittest2 consente di utilizzare queste funzionalità con le versioni precedenti di Python."
Two-Bit Alchemist

8

Ho provato vari approcci ma tutti sembrano imperfetti o devo truccare un po 'di codice, è fastidioso. Ma c'è un modo conveniente sotto Linux, che è semplicemente quello di trovare ogni test attraverso un certo schema e poi invocarli uno per uno.

find . -name 'Test*py' -exec python '{}' \;

e, soprattutto, funziona sicuramente.


7

Nel caso di una libreria o un'applicazione in pacchetto , non si desidera farlo. setuptools lo farà per te .

Per utilizzare questo comando, i test del progetto devono essere racchiusi in una unittestsuite di test da una funzione, una classe o un metodo TestCase o un modulo o pacchetto contenente TestCaseclassi. Se la suite denominata è un modulo e il modulo ha una additional_tests()funzione, viene chiamato e il risultato (che deve essere un unittest.TestSuite) viene aggiunto ai test da eseguire. Se la suite denominata è un pacchetto, eventuali sottomoduli e pacchetti secondari vengono aggiunti ricorsivamente alla suite di test generale .

Ditelo dove si trova il pacchetto di test di root, come:

setup(
    # ...
    test_suite = 'somepkg.test'
)

E corri python setup.py test.

Il rilevamento basato su file può essere problematico in Python 3, a meno che non si evitino le importazioni relative nella suite di test, poiché discoverutilizza l'importazione di file. Anche se supporta facoltativo top_level_dir, ma ho avuto infiniti errori di ricorsione. Quindi una soluzione semplice per un codice non impacchettato è quella di inserire quanto segue nel __init__.pypacchetto di test (vedere protocollo load_tests ).

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite

Bella risposta, e può essere utilizzata per automatizzare i test prima di implementarli! Grazie
Arthur Clerc-Gherardi il

4

Uso PyDev / LiClipse e non ho davvero capito come eseguire tutti i test contemporaneamente dalla GUI. (modifica: fai clic con il pulsante destro del mouse sulla cartella del test principale e scegliRun as -> Python unit-test

Questa è la mia soluzione attuale:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

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

Ho inserito questo codice in un modulo chiamato allnella mia directory di test. Se eseguo questo modulo come unittest da LiClipse, vengono eseguiti tutti i test. Se chiedo solo di ripetere test specifici o falliti, vengono eseguiti solo quei test. Non interferisce nemmeno con il mio test runner da riga di comando (nosetests) - viene ignorato.

Potrebbe essere necessario modificare gli argomenti in discoverbase all'impostazione del progetto.


I nomi di tutti i file di test e i metodi di test devono iniziare con "test_". Altrimenti il ​​comando "Esegui come -> Test unit Python" non li troverà.
Stefan,

2

Sulla base della risposta di Stephen Cagle ho aggiunto il supporto per i moduli di test nidificati.

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

Il codice cerca tutte le sottodirectory di .per i *Tests.pyfile che vengono poi caricati. Si aspetta che ciascuno *Tests.pycontenga una singola classe *Tests(unittest.TestCase)che viene caricata a sua volta ed eseguita una dopo l'altra.

Funziona con l'annidamento profondo arbitrario di directory / moduli, ma __init__.pyalmeno ogni directory tra deve contenere un file vuoto . Ciò consente al test di caricare i moduli nidificati sostituendo le barre (o barre rovesciate) con punti (vedi replace_slash_by_dot).


2

Questa è una vecchia domanda, ma ciò che ha funzionato per me ora (nel 2019) è:

python -m unittest *_test.py

Tutti i miei file di test si trovano nella stessa cartella dei file di origine e terminano con _test.



1

Questo script BASH eseguirà la directory di test unittest di Python da OVUNQUE nel file system, indipendentemente dalla directory di lavoro in cui ti trovi: la sua directory di lavoro si trova sempre dove testsi trova quella directory.

TUTTE LE PROVE, $ PWD indipendente

il modulo Python unittest è sensibile alla directory corrente, a meno che non si indichi dove (utilizzando l' discover -sopzione).

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

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

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

PROVE SELEZIONATE, $ PWD indipendente

Ho chiamato questo file di utilità: runone.pye lo uso in questo modo:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

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


-3

Ecco il mio approccio creando un wrapper per eseguire test dalla riga di comando:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

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

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

Per semplicità, scusa i miei standard di codifica non PEP8 .

Quindi puoi creare la classe BaseTest per componenti comuni per tutti i tuoi test, quindi ognuno dei tuoi test sarebbe semplicemente simile a:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

Per eseguire, è sufficiente specificare i test come parte degli argomenti della riga di comando, ad esempio:

./run_tests.py -h http://example.com/ tests/**/*.py

2
la maggior parte di questa risposta non ha nulla a che fare con il rilevamento dei test (ad es. registrazione, ecc.). Stack Overflow è per rispondere alle domande, non mostrare codice non correlato.
Corey Goldberg,
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.