2013-09-07 8 views
20

Chciałbym ustawićJak ustawić sys.argv, aby można było przetestować urządzenie?

sys.argv 

więc mogę testów jednostkowych przechodzącą w różnych kombinacjach. Poniższa nie działa:

#!/usr/bin/env python 
import argparse, sys 
def test_parse_args(): 
    global sys.argv 
    sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"] 
    setup = get_setup_file() 
    assert setup == "/home/fenton/project/setup.py" 
def get_setup_file(): 
    parser = argparse.ArgumentParser() 
    parser.add_argument('-f') 
    args = parser.parse_args() 
    return args.file 
if __name__ == '__main__': 
    test_parse_args() 

następnie uruchomić plik:

pscripts % ./test.py                       
    File "./test.py", line 4 
    global sys.argv 
      ^
SyntaxError: invalid syntax 
pscripts % 
+0

Co o nim „nie działa”? –

+3

Nie wywołałeś 'get_setup_file'. Nie potrzebujesz 'global sys.argv'. Potrzebujesz 'import sys'. –

+0

Składnia '" ~/project/setup.py "' nie działa, ponieważ jest to powłoka, która [rozszerza tyldy] (http://www.gnu.org/software/bash/manual/bashref.html#Tilde- Ekspansja), a nie system operacyjny. Tę czynność musisz wykonać samodzielnie, np. 'os.path.join (os.getenv (" HOME ")," project/setup.py ")', aby uzyskać to, co chcesz. –

Odpowiedz

1

To nie działa, ponieważ nie jesteś faktycznie dzwoni get_setup_file. Kod powinien brzmieć:

import argparse 

def test_parse_args(): 
    sys.argv = ["prog", "-f", "/home/fenton/project/setup.py"] 
    setup = get_setup_file() # << You need the parentheses 
    assert setup == "/home/fenton/project/setup.py" 
+1

Musisz także samodzielnie rozwinąć '~', np.z 'os.getenv (" HOME ")', aby ścieżka działała poprawnie. –

+0

cóż, zaktualizowałem te elementy nowym kodem powyżej i nadal ten sam błąd. – ftravers

+0

@AdamRosenfield w rzeczywistości powłoka rozszerzy go dla niego, więc nigdy nie otrzyma '~/...'. Naprawię to dla dokładności. –

6

global eksponuje tylko zmienne globalne wewnątrz modułu, i sys.argv jest w sys, nie modułu. Zamiast używać global sys.argv, użyj import sys.

można uniknąć konieczności zmiany sys.argv w ogóle, choć dość prosta: tylko niech get_setup_file ewentualnie wziąć listę argumentów (zalegających do None) i przekazać, że do parse_args. Gdy zostanie wywołane get_setup_file bez żadnych argumentów, argument ten będzie wynosił None, a parse_args powróci do sys.argv. Gdy zostanie wywołany z listą, zostanie użyty jako argumenty programu.

+0

Świetny pomysł, ale nie wiem jak wymusić "parser.parse_args()", aby wziąć parametr, w przeciwieństwie do ZAWSZE używać sys.argv. Pomysły ??? Wydaje się naprawdę głupie, aby nie móc przyjąć argumentu. – ftravers

+3

@ftravers: Ale [to * robi * bierze parametr] (http://docs.python.org/3.3/library/argparse.html#argparse.ArgumentParser.parse_args). Jeśli spojrzysz na wszystkie przykłady w dokumentacji, prawie * wszystkie * z nich podają 'parse_args' listę. – icktoofay

+0

Okay dzięki, m newbie Pythona, dzięki za pokazanie, gdzie byli doktorzy. Zaktualizował również kod. – ftravers

0

Zwykle masz argumenty polecenia. Musisz je przetestować. Oto jak je przetestować.

  • Załóżmy program może być uruchamiany tak: % myprogram -f setup.py

  • Tworzymy listę naśladować to zachowanie. Zobacz linię (4)

  • Następnie nasza metoda, która analizuje argumenty, pobiera tablicę jako argument domyślnie ustawiony na None. Zobacz linię (7)
  • Następnie w linii (11) przekazujemy to do parse_args, która używa tablicy, jeśli nie jest None. Jeśli jest to None, to domyślnie jest używane sys.argv.
 
    1: #!/usr/bin/env python 
    2: import argparse 
    3: def test_parse_args(): 
    4:  my_argv = ["-f", "setup.py"] 
    5:  setup = get_setup_file(my_argv) 
    6:  assert setup == "setup.py" 
    7: def get_setup_file(argv=None): 
    8:  parser = argparse.ArgumentParser() 
    9:  parser.add_argument('-f') 
    10:  # if argv is 'None' then it will default to looking at 'sys.argv'   
    11:  args = parser.parse_args(argv) 
    12:  return args.f 
    13: if __name__ == '__main__': 
    14:  test_parse_args() 
0

Bardzo dobre pytanie.

Sztuczka polegająca na ustawianiu testów jednostkowych polega na umożliwieniu ich powtarzania. Oznacza to, że musisz wyeliminować zmienne, aby testy były powtarzalne. Na przykład, jeśli testujesz funkcję, która musi działać poprawnie, biorąc pod uwagę bieżącą datę, zmusić ją do pracy dla określonych dat, gdzie wybrana data nie ma znaczenia, ale wybrane daty pasują do typu i zakresu do rzeczywistych.

Tutaj sys.argv będzie listą długości co najmniej jednego. Stwórz więc "fakemain", który zostanie wywołany z listą. Następnie przetestuj pod kątem różnych prawdopodobnych długości list i zawartości. Następnie możesz wywołać swoją fałszywą główną z prawdziwej, przechodzącej przez sys.argv, wiedząc, że fakemain działa lub zmienić część "jeśli chcesz wykonać normalną funkcję w nie-jednostkowych warunkach testowych.

+0

Nie jestem pewien, co masz na myśli przez powtarzalne ... masz na myśli automatyzację/skryptowalność? Jeśli tak, to jest niezbędne. Tym, co definiuje test jednostkowy, jest możliwość odhaczenia go od kontekstu i podania oczekiwanego kontekstu. W takim przypadku nie mogę uruchomić każdego testu ręcznie, pojedynczo przekazując argumenty. Chcę przekazać testowi równoważnik sys.argv, oczekiwany kontekst. – ftravers

2

test_argparse.py urzędowy argparse unittest pliku korzysta z kilku sposobów ustawiania/używania argv:

parser.parse_args(args) 

gdzie args jest lista 'słowa', np ['--foo','test'] lub --foo test'.split().

old_sys_argv = sys.argv 
sys.argv = [old_sys_argv[0]] + args 
try: 
    return parser.parse_args() 
finally: 
    sys.argv = old_sys_argv 

Spycha argumenty na sys.argv.

Właśnie natknąłem się na skrzynkę (używając mutually_exclusive_groups), gdzie ['--foo','test'] powoduje inne zachowanie niż '--foo test'.split(). Jest to subtelny punkt obejmujący id ciągów takich jak test.

+0

Osobiście podoba mi się metoda dostarczania "parse_args()" listy argumentów i po prostu pozostawiając sys.argv z tego całkowicie. wydaje się najczystszym sposobem. – ftravers

20

Zmiana pliku sys.argv w środowisku wykonawczym jest dość kruchym sposobem testowania. Powinieneś użyć funkcji mock 'patch, która może być użyta jako menedżer kontekstów do zastąpienia jednego obiektu (lub atrybutu, metody, funkcji itp.) Innym, w obrębie danego bloku kodu.

W poniższym przykładzie użyto patch(), aby skutecznie "zastąpić" sys.argv z określoną wartością zwracaną (testargs).

from mock import patch 

def test_parse_args(): 
    testargs = ["prog", "-f", "/home/fenton/project/setup.py"] 
    with patch.object(sys, 'argv', testargs): 
     setup = get_setup_file() 
     assert setup == "/home/fenton/project/setup.py" 
+7

Aby zrobić to samo ze standardową biblioteką, składnia byłaby następująca: 'with unittest.mock.patch ('sys.argv'. [Stuff])' – dbaston

+0

W języku Python 3.3+: 'from unittest.mock import patch' –

0

Możesz dołączyć owijkę wokół swojej funkcji, która sporządza sys.argv przed wywołaniem i przywraca je przy wyjeździe:

def run_with_sysargv(func, sys_argv): 
""" prepare the call with given sys_argv and cleanup afterwards. """ 
    def patched_func(*args, **kwargs): 
     old_sys_argv = list(sys.argv) 
     sys.argv = list(sys_argv) 
     try: 
      return func(*args, **kwargs) 
     except Exception, err: 
      sys.argv = old_sys_argv 
      raise err 
    return patched_func 

następnie można po prostu zrobić

def test_parse_args(): 
    _get_setup_file = run_with_sysargv(get_setup_file, 
             ["prog", "-f", "/home/fenton/project/setup.py"]) 
    setup = _get_setup_file() 
    assert setup == "/home/fenton/project/setup.py" 

Ponieważ błędy są przekazywane poprawnie, nie powinny zakłócać zewnętrznych instancji przy użyciu kodu testowego, np. pytest.

0

I to osiągnąć poprzez stworzenie menedżera wykonania, które ustawić args mojego wyboru i usunąć je przy wyjściu:

import sys  


class add_resume_flag(object): 
    def __enter__(self): 
     sys.argv.append('--resume') 

    def __exit__(self, typ, value, traceback): 
     sys.argv = [arg for arg in sys.argv if arg != '--resume'] 

class MyTestClass(unittest.TestCase): 

    def test_something(self): 
     with add_resume_flag(): 
      ... 
Powiązane problemy