Domanda

Ho una directory che contiene i miei test di unità Python. Ogni modulo unit test è del Test forma _ *. Py . Sto tentando di fare un file chiamato all_test.py che la volontà, avete indovinato, eseguire tutti i file in forma di prova di cui sopra e restituire il risultato. Ho provato due metodi finora; entrambi hanno fallito. Mostrerò i due metodi, e spero che qualcuno là fuori sa come fare in realtà questo correttamente.

Per il mio primo tentativo coraggioso, ho pensato: "Se ho appena importare tutti i miei moduli di test nel file, e quindi chiamare questa unittest.main() doodad, funzionerà, no?" Beh, viene fuori 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 ho ottenuto è stato:

$ python all_test.py 

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

OK

Per il mio secondo tentativo, ho pensato, ok, forse cercherò di fare tutta questa cosa di test in modo più "manuale". Così ho tentato di farlo qui 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 si dice che ho solo run=1, sembra che dovrebbe essere run=2, ma è il progresso. Ma come faccio a passare e visualizzare il risultato principale? O come faccio fondamentalmente farlo funzionare così posso solo eseguire questo file, e così facendo, eseguire tutti i test di unità in questa directory?

È stato utile?

Soluzione

È possibile utilizzare un test di corridore che avrebbe fatto questo per voi. naso è molto buono per esempio. Quando viene eseguito, si troverà test nella struttura attuale ed eseguirli.

Aggiornamento:

Ecco un po 'di codice da miei giorni pre-naso. Probabilmente non si desidera che l'elenco esplicito di nomi di moduli, ma forse il resto sarà utile a voi.

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)

Altri suggerimenti

Con Python 2.7 e superiori non devi scrivere nuovo codice o utilizzare strumenti di terze parti per fare questo; esecuzione dei test ricorsivo tramite la linea di comando è incorporato.

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

Si può leggere di più nel python 2.7 o python 3.x unittest documentazione.

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

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

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

In Python 3, se si sta utilizzando unittest.TestCase:

  • È necessario disporre di un file __init__.py vuoto (o meno) nella directory test ( deve essere nominato test/)
  • I tuoi file di test all'interno test/ corrispondono al test_*.py modello. Possono essere all'interno di una sottodirectory test/, e quelle sottodirectory possono essere nominato come nulla.

Quindi, è possibile eseguire tutti i test con:

python -m unittest

Fatto! Una soluzione meno di 100 linee. Speriamo che un altro principiante python consente di risparmiare tempo trovando questo.

Bene studiando il codice di cui sopra un po '(in particolare utilizzando TextTestRunner e defaultTestLoader), sono stato in grado di ottenere abbastanza vicino. Alla fine ho fissato il mio codice da anche solo di passaggio tutte le suite di test ad un unico costruttore di suite, piuttosto che aggiungere loro "manualmente", che ha fissato i miei altri problemi. Così qui è 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)

Si, è probabilmente più facile usare solo il naso di fare questo, ma che è oltre il punto.

Se si desidera eseguire tutti i test provenienti da varie classi di test case e sei felice di specificare esplicitamente allora si può fare 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)

dove uclid è il mio progetto e TestSymbols e TestPatterns sono sottoclassi di TestCase.

ho usato il metodo discover e un sovraccarico di load_tests per ottenere questo risultato in una (minima, credo) numero di linee 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 come

Ran 27 tests in 0.187s
OK

Ho provato vari approcci, ma tutti sembrano viziata o devo trucco del codice, che è fastidioso. Ma c'è un modo comoda sotto linux, che è semplicemente quello di trovare ogni prova attraverso determinato modello e quindi richiamare uno per uno.

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

e, soprattutto, funziona sicuramente.

In caso di confezionato libreria o applicazione, non si vuole farlo. setuptools lo farà per voi .

  

Per utilizzare questo comando, i test del progetto devono essere avvolti in una suite di test unittest da una funzione, una classe TestCase o un metodo, o un modulo o pacchetto contenente classi TestCase. Se la suite di nome è un modulo, e il modulo ha una funzione additional_tests(), si chiama e il risultato (che deve essere un unittest.TestSuite) si aggiunge alle prove da eseguire. Se la suite di nome è un pacchetto, eventuali sottomoduli e pacchetti secondari sono ricorsivamente aggiunti alla suite di test globale .

Basta dire che dove il vostro pacchetto di prova principale è, come:

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

E run python setup.py test.

scoperta basata su file può essere problematico in Python 3, a meno che non si evitano le importazioni relative nella vostra suite di test, perché discover utilizza l'importazione di file. Anche se sostiene top_level_dir facoltativa, ma ho avuto alcuni errori ricorsione infinita. Quindi, una soluzione semplice per un codice non-confezionato è quello di mettere quanto segue in __init__.py del vostro pacchetto di prova (vedi load_tests protocollo ).

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

Io uso pydev / LiClipse e non ho davvero capito come eseguire tutti i test in una volta dalla GUI. (Edit: si fa clic destro sulla cartella di prova principale e scegli Run as -> Python unit-test

Questa è la mia soluzione corrente:

import unittest

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

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

Ho messo questo codice in un modulo chiamato all nella mia directory di prova. Se faccio funzionare questo modulo come unittest da LiClipse poi tutti i test vengono eseguiti. Se chiedo di ripetere test specifici o falliti, allora solo i test vengono eseguiti. Non interferisce con la mia prova di comando corridore o (nosetests) -. È ignorato

Potrebbe essere necessario modificare gli argomenti da discover in base alla configurazione del progetto.

In base alla risposta di Stephen Cagle ho aggiunto il supporto per moduli di test nidificate.

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 file *Tests.py che vengono poi caricati. Essa richiede che ciascun *Tests.py di contenere un singolo *Tests(unittest.TestCase) classe che è caricato a sua volta ed eseguito uno dopo l'altro.

Questo funziona con profonda nidificazione arbitraria della directory / moduli, ma ogni directory in mezzo deve contenere un file __init__.py vuoto almeno. Questo permette il test per caricare i moduli nidificati sostituendo barre (o barre) da punti (vedi replace_slash_by_dot).

A causa scoperta di prova sembra essere un soggetto completo, v'è un quadro di riferimento dedicato a testare la scoperta:

Più leggere qui: https://wiki.python.org/moin/PythonTestingToolsTaxonomy

Questo script BASH eseguirà il pitone unittest directory test da qualsiasi parte del file system, non importa quale directory di lavoro ti trovi in:. La sua directory di lavoro sia sempre dove si trova quella directory test

tutte le prove, indipendentemente $ PWD

unittest Python modulo è sensibile alla directory corrente, a meno che non gli si dice dove (usando l'opzione discover -s).

Questa funzione è utile in caso di dimora nella directory di lavoro ./src o ./example e avete bisogno di una prova di unità nel complesso rapida:

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

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

test selezionati, indipendentemente $ PWD

nomino questo file utility: runone.py e usarlo 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 c'è bisogno di un file test/__init__.py gravare il pacchetto / memoria overhead durante la produzione.

Qui è il mio approccio con la creazione di 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 ragioni di semplicità, vi prego di scusare i miei standard non PEP8 di codifica .

Quindi è possibile creare classe BaseTest per i componenti comuni per tutti i test, in modo che ogni del test sarebbe semplicemente apparire come:

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, per esempio:.

./run_tests.py -h http://example.com/ tests/**/*.py
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top