2016-02-08 80 views
10

foo è un progetto Python con annidamento di directory profonde, tra cui ~ 30 unittest file in varie sottodirectory. All'interno foo s' setup.py, ho added a custom "test" command correre internamentePassando argomenti (per argparse) con scoprimento unittest

python -m unittest discover foo '*test.py' 

Si noti che questo utilizza la modalità unittest's discovery.


Poiché alcuni test sono estremamente lenti, ho deciso di recente che i test dovrebbero avere "livelli". La risposta a this question ha spiegato molto bene come ottenere unittest e argparse per funzionare bene l'uno con l'altro. Così ora, posso eseguire un file unittest individuale, dire foo/bar/_bar_test.py, con

python foo/bar/_bar_test.py --level=3 

e solo livello 3 test vengono eseguiti.

Il problema è che io non riesco a capire come passare la bandierina su ordinazione (in questo caso "--level = 3" utilizzando scoprire tutto ciò che provo non riesce, ad esempio:.

$ python -m unittest discover --level=3 foo '*test.py' 
Usage: python -m unittest discover [options] 

python -m unittest discover: error: no such option: --level 

$ python -m --level=3 unittest discover foo '*test.py' 
/usr/bin/python: No module named --level=3 

Come può passo --level=3 ai singoli Unittests? Se possibile, vorrei evitare di dividere diversi test a livello di file diversi.

Bounty Modifica

La pre-taglie (fine) soluzione suggerisce di utilizzare il sistema enviro variabili Questo non è male, ma sto cercando qualcosa di più pulito.

Modifica del test di corridore più file (ad esempio, python -m unittest scoprire foo '* test.py') a qualcos'altro va bene, a patto che:

  1. Esso consente la generazione di un unico rapporto per multiple-file unittests.
  2. Può in qualche modo supportare più livelli di test (utilizzando la tecnica nella domanda o utilizzando un altro meccanismo diverso).

risposta

2

Questo non passa args utilizzando unittest scoprire, ma è quello che si compie stanno cercando di fare.

Questo è leveltest.py. Metterlo da qualche parte nel percorso di ricerca del modulo (forse directory corrente o site-packages):

import argparse 
import sys 
import unittest 

# this part copied from unittest.__main__.py 
if sys.argv[0].endswith("__main__.py"): 
    import os.path 
    # We change sys.argv[0] to make help message more useful 
    # use executable without path, unquoted 
    # (it's just a hint anyway) 
    # (if you have spaces in your executable you get what you deserve!) 
    executable = os.path.basename(sys.executable) 
    sys.argv[0] = executable + " -m leveltest" 
    del os 

def _id(obj): 
    return obj 

# decorator that assigns test levels to test cases (classes and methods) 
def level(testlevel): 
    if unittest.level < testlevel: 
     return unittest.skip("test level too low.") 
    return _id 

def parse_args(): 
    parser = argparse.ArgumentParser() 
    parser.add_argument('--level', type=int, default=3) 
    ns, args = parser.parse_known_args(namespace=unittest) 
    return ns, sys.argv[:1] + args 

if __name__ == "__main__": 
    ns, remaining_args = parse_args() 

    # this invokes unittest when leveltest invoked with -m flag like: 
    # python -m leveltest --level=2 discover --verbose 
    unittest.main(module=None, argv=remaining_args) 

Ecco come lo si utilizza in un file di esempio testproject.py:

import unittest 
import leveltest 

# This is needed before any uses of the @leveltest.level() decorator 
# to parse the "--level" command argument and set the test level when 
# this test file is run directly with -m 
if __name__ == "__main__": 
    ns, remaining_args = leveltest.parse_args() 

@leveltest.level(2) 
class TestStringMethods(unittest.TestCase): 

    @leveltest.level(5) 
    def test_upper(self): 
     self.assertEqual('foo'.upper(), 'FOO') 

    @leveltest.level(3) 
    def test_isupper(self): 
     self.assertTrue('FOO'.isupper()) 
     self.assertFalse('Foo'.isupper()) 

    @leveltest.level(4) 
    def test_split(self): 
     s = 'hello world' 
     self.assertEqual(s.split(), ['hello', 'world']) 
     # check that s.split fails when the separator is not a string 
     with self.assertRaises(TypeError): 
      s.split(2) 

if __name__ == '__main__': 
    # this invokes unittest when this file is executed with -m 
    unittest.main(argv=remaining_args) 

È possibile quindi eseguire test eseguendo testproject.py direttamente, come:

~roottwo\projects> python testproject.py --level 2 -v 
test_isupper (__main__.TestStringMethods) ... skipped 'test level too low.' 
test_split (__main__.TestStringMethods) ... skipped 'test level too low.' 
test_upper (__main__.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.000s 

OK (skipped=3) 

~roottwo\projects> python testproject.py --level 3 -v 
test_isupper (__main__.TestStringMethods) ... ok 
test_split (__main__.TestStringMethods) ... skipped 'test level too low.' 
test_upper (__main__.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=2) 

~roottwo\projects> python testproject.py --level 4 -v 
test_isupper (__main__.TestStringMethods) ... ok 
test_split (__main__.TestStringMethods) ... ok 
test_upper (__main__.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=1) 

~roottwo\projects> python testproject.py --level 5 -v 
test_isupper (__main__.TestStringMethods) ... ok 
test_split (__main__.TestStringMethods) ... ok 
test_upper (__main__.TestStringMethods) ... ok 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK 

utilizzando unittest scoperta come questa:

~roottwo\projects> python -m leveltest --level 2 -v 
test_isupper (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_split (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.003s 

OK (skipped=3) 

~roottwo\projects> python -m leveltest --level 3 discover -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=2) 

~roottwo\projects> python -m leveltest --level 4 -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... ok 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK (skipped=1) 

~roottwo\projects> python -m leveltest discover --level 5 -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... ok 
test_upper (testproject.TestStringMethods) ... ok 

---------------------------------------------------------------------- 
Ran 3 tests in 0.001s 

OK 

o specificando casi di test da eseguire, come:

~roottwo\projects>python -m leveltest --level 3 testproject -v 
test_isupper (testproject.TestStringMethods) ... ok 
test_split (testproject.TestStringMethods) ... skipped 'test level too low.' 
test_upper (testproject.TestStringMethods) ... skipped 'test level too low.' 

---------------------------------------------------------------------- 
Ran 3 tests in 0.002s 

OK (skipped=2) 
+0

Quindi, grazie per la tua risposta, ma non sono riuscito a capire se questo consentisse qualcosa come la capacità di scoprire tutti i file in una directory, quindi generare un unico rapporto per tutti loro. –

+0

Usa 'unittest' per eseguire tutti i test. Quindi, sì, fornisce gli stessi report di "unittest". Gli esempi nella mia risposta usano il flag -v (verbose) per unittest per fornire dettagli su tutto il test, compresi quelli che sono stati saltati perché il livello di test era troppo basso. – RootTwo

+0

Ah, vedo - interessante. Grazie per la tua risposta - la guarderà ancora un po '. Apprezzato! –

6

Non è possibile passare argomenti durante l'utilizzo di Discover. DiscoveringTestLoader classe da Discover, rimuove tutti i file non corrispondenti (elimina usando '* test.py --level = 3') e passa di file solo i nomi in unittest.TextTestRunner

Probabilmente unica opzione fino ad ora sta usando le variabili d'ambiente

LEVEL=3 python -m unittest discoverfoo '*test.py' 
+0

variabili d'ambiente sono un'idea interessante. Grazie. Sto ancora sperando in qualcosa che non li coinvolge. –

2

Il problema è che l'analizzatore di argomento più semplice non capisce questa sintassi. È quindi necessario rimuovere i parametri prima che venga invocato l'unittest.

Un modo semplice per farlo è quello di creare un modulo wrapper (ad esempio my_unittest.py) che cerca i parametri aggiuntivi, li rimuove da sys.argv e quindi richiama la voce principale in unittest.

Ora per il bel po '... Il codice per quel wrapper è praticamente lo stesso del codice che già usi per il caso del singolo file! Hai solo bisogno di metterlo in un file separato.

EDIT: Aggiunto codice di esempio riportato di seguito, come richiesto ...

In primo luogo, il nuovo file per eseguire telescopi del VLT (my_unittest.py):

import sys 
import unittest 
from parser import wrapper 

if __name__ == '__main__': 
    wrapper.parse_args() 
    unittest.main(module=None, argv=sys.argv) 

Ora parser.py, che aveva di essere in un file separato per evitare di essere nel modulo __main__ per il riferimento globale per lavorare:

import sys 
import argparse 
import unittest 

class UnitTestParser(object): 

    def __init__(self): 
     self.args = None 

    def parse_args(self): 
     # Parse optional extra arguments 
     parser = argparse.ArgumentParser() 
     parser.add_argument('--level', type=int, default=0) 
     ns, args = parser.parse_known_args() 
     self.args = vars(ns) 

     # Now set the sys.argv to the unittest_args (leaving sys.argv[0] alone) 
     sys.argv[1:] = args 

wrapper = UnitTestParser() 

E infine un campione banco di prova (project_test.py) per testare che i parametri vengono analizzati correttamente:

import unittest 
from parser import wrapper 

class TestMyProject(unittest.TestCase): 

    def test_len(self): 
     self.assertEqual(len(wrapper.args), 1) 

    def test_level3(self): 
     self.assertEqual(wrapper.args['level'], 3) 

E ora la prova:

$ python -m my_unittest discover --level 3 . '*test.py' 
.. 
---------------------------------------------------------------------- 
Ran 2 tests in 0.000s 

OK 
+0

OK, questo è un buon punto. In un certo senso, in effetti, lo traducevo in "scrivi il tuo pacchetto unittest" costruito su "unittest" (non vorrei fare affidamento su alcuni locali 'my_unittest.py'). Comunque, una bella idea. Grazie! –

+0

@AmiTavory Non deve essere un pacchetto separato. Puoi metterlo con le tue unittests e semplicemente consegnare quel file python nello stesso pacchetto/directory di test/qualunque cosa tu faccia per consegnare i tuoi UT. –

+0

Se spunti il ​​contenuto di 'my_unittest', sarò felice di accettare la tua risposta (e assegnarti la taglia). –

Problemi correlati