2016-02-08 33 views
10

foo to projekt Pythona z głębokim zagnieżdżaniem katalogów, w tym ~ 30 unittest plików w różnych podkatalogach. W ciągu foo „s setup.py, mam added a custom "test" command wewnętrznie działaPodawanie argumentów (dla argparse) z unittest discover

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

pamiętać, że ta wykorzystuje tryb unittest's discovery.


Ponieważ niektóre testy są bardzo powolne, ostatnio zdecydowałem, że testy powinny mieć "poziomy". Odpowiedź na this question wyjaśniła bardzo dobrze, jak uzyskać unittest i argparse, aby dobrze współpracować ze sobą. Więc teraz mogę uruchomić indywidualne unittest plik, powiedzmy foo/bar/_bar_test.py z

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

i tylko poziom-3 testy są prowadzone.

Problem polega na tym, że nie mogę dowiedzieć się, jak przekazać niestandardowe flagi (w tym przypadku „--level = 3” Wykorzystanie odkryć wszystko staram się nie powiedzie, np.

$ 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 

Jak można mijam --level=3 do indywidualnych unittests? Jeśli to możliwe, chciałbym uniknąć dzielenia testy inny szczebla do różnych plików.

Bounty Edycja rozwiązanie

wstępnie bounty (fine) sugeruje zastosowanie systemu ENVIRO Zmienne n. To nie jest złe, ale szukam czegoś czystszego.

Zmiana testy biegacz wielokrotnego pliku (tj python -m unittest odkryć foo „* test.py”) na coś innego jest w porządku, tak długo, jak:

  1. Umożliwia generowanie raportu dla pojedynczego wielokrotne archiwizowanie plików.
  2. Może w pewien sposób obsługiwać wiele poziomów testowych (przy użyciu techniki w pytaniu lub przy użyciu innego mechanizmu).

Odpowiedz

2

nie przechodzą args użyciu unittest odkryć, ale to, czego dokonuje próbują to zrobić.

To jest leveltest.py. Umieścić go gdzieś w ścieżce wyszukiwania modułów (może bieżącym katalogu lub 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) 

Oto jak go używać w pliku przykład 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) 

Następnie można uruchomić testy uruchamiając testproject.py bezpośrednio, jak:

~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 

korzystając unittest odkrycie tak:

~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 

Albo poprzez określenie przypadków testowych do uruchomienia, jak:

~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

Więc, dziękuję za odpowiedź, ale nie mogłem się zorientować, czy pozwala to na coś takiego, jak "odkryj" możliwość przejrzenia wszystkich plików w katalogu, a następnie wygeneruje pojedynczy raport dla wszystkich z nich. –

+0

Używa 'unittest' do wykonania wszystkich testów. Tak, zapewnia on te same raporty co 'unittest'. Przykłady w mojej odpowiedzi wykorzystują flagę -v (verbose), aby anulować, aby podać szczegóły dotyczące wszystkich testów, w tym te, które zostały pominięte, ponieważ poziom testu był zbyt niski. – RootTwo

+0

Ah, widzę - interesujące. Dzięki za odpowiedź - przyjrzymy się jej jeszcze raz. Doceniony! –

6

Nie ma sposobu przekazywania argumentów podczas używania funkcji odkrywania. DiscoveringTestLoader klasa z Discover, usuwa wszystkie pliki niedopasowane (eliminuje za pomocą „* test.py --level = 3”) i przechodzi złożyć tylko imiona w unittest.TextTestRunner

Prawdopodobnie jedyną opcją do tej pory używa zmiennych środowiskowych

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

zmienne środowiskowe są ciekawym pomysłem. Dzięki. Wciąż mam nadzieję na coś, co ich nie dotyczy. –

2

Problem polega na tym, że analizator składni unittest po prostu nie rozumie tej składni. Dlatego konieczne jest usunięcie parametrów przed wywołaniem unittest.

Prostym sposobem na zrobienie tego jest utworzenie modułu opakowania (np. My_unittest.py), który wyszukuje dodatkowe parametry, usuwa je z pliku sys.argv, a następnie wywołuje główny wpis w unittest.

Teraz dla dobrego bitu ... Kod tego opakowania jest w zasadzie taki sam, jak kod, którego już używasz w przypadku pojedynczego pliku! Wystarczy umieścić go w oddzielnym pliku.

EDIT: Dodano przykładowy kod poniżej na żądanie ...

Po pierwsze, nowy plik do uruchomienia terminali UT (my_unittest.py):

import sys 
import unittest 
from parser import wrapper 

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

Teraz parser.py, który miał być w osobnym pliku, aby uniknąć w module __main__ dla globalnego odniesienia do pracy:

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() 

i wreszcie próbki przypadek testowy (project_test.py), aby sprawdzić, czy parametry są przetwarzane prawidłowo:

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) 

A teraz dowód:

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

OK 
+0

OK, to dobra uwaga. Z nieco mniejszym skutkiem, przetłumaczyłbym to na "napisanie własnego pakietu unittest" zbudowanego na 'unittest' (nie chciałbym polegać na jakimś lokalnym' my_unittest.py'). Mimo to świetny pomysł. Dzięki! –

+0

@AmiTavory Nie musi to być oddzielny bagaż. Możesz umieścić go za pomocą unittests i po prostu dostarczyć ten plik Pythona w tym samym pakiecie/katalogu testowym/cokolwiek robisz, aby dostarczyć twoje UT. –

+0

Jeśli uzupełnisz zawartość 'my_unittest', z przyjemnością przyjmuję twoją odpowiedź (i nagrodzę cię nagrodą). –