Как установить sys.argv, чтобы я мог unit test его?
Я хотел бы установить
sys.argv
поэтому я могу unit test проходить в разных комбинациях. Не работает следующее:
#!/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()
Затем запустите файл:
pscripts % ./test.py
File "./test.py", line 4
global sys.argv
^
SyntaxError: invalid syntax
pscripts %
Ответы
Ответ 1
Изменение sys.argv во время выполнения - довольно хрупкий способ тестирования. Вам следует использовать функциональность mockpatch, которую можно использовать в качестве диспетчера контекста для замены одного объекта (или атрибута, метода, функции и т.д.) Другим в данном блоке кода.
В следующем примере используется patch()
для эффективной "замены" sys.argv
указанным возвращаемым значением (testargs
).
try:
# python 3.4+ should use builtin unittest.mock not mock package
from unittest.mock import patch
except ImportError:
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"
Ответ 2
global
предоставляет только глобальные переменные в вашем модуле, а sys.argv
находится в sys
, а не в вашем модуле. Вместо global sys.argv
используйте import sys
.
Вы можете вообще не изменять sys.argv
, но достаточно просто: просто get_setup_file
необязательно возьмите список аргументов (по умолчанию None
) и перейдите к parse_args
. Когда get_setup_file
вызывается без аргументов, этот аргумент будет None
, а parse_args
вернется к sys.argv
. Когда он вызывается со списком, он будет использоваться в качестве аргументов программы.
Ответ 3
test_argparse.py
, официальный файл argparse
unittest, использует несколько способов установки/использования argv
:
parser.parse_args(args)
где args
- это список "слов", например, ['--foo','test']
или --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
Это выдвигает аргументы на sys.argv
.
Я только что натолкнулся на случай (использующий mutually_exclusive_groups
), где ['--foo','test']
производит поведение, отличное от '--foo test'.split()
. Это тонкий момент, включающий в себя id
таких строк, как test
.
Ответ 4
Это не работает, потому что вы на самом деле не вызываете get_setup_file
. Ваш код должен читать:
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"
Ответ 5
Обычно у вас есть аргументы команды. Тебе нужно их протестировать. Вот как unit test их.
-
Предположим, что программа может быть запущена как: % myprogram -f setup.py
-
Мы создаем список для имитации этого поведения. См. Строку (4)
- Затем наш метод, который анализирует args, принимает массив как аргумент, который по умолчанию равен
None
. См. Строку (7)
- Затем в строке (11) мы передаем это в
parse_args
, который использует массив, если он не None
. Если это None
, то по умолчанию используется 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()
Ответ 6
Очень хороший вопрос.
Трюк для настройки модульных тестов - все это делает их повторяемыми. Это означает, что вам нужно устранить переменные, чтобы тесты повторялись. Например, если вы тестируете функцию, которая должна выполняться правильно с учетом текущей даты, а затем принудительно работать для определенных дат, когда выбранная дата не имеет значения, но выбранные даты соответствуют типу и диапазону действительным.
Здесь sys.argv будет списком длины как минимум одного. Поэтому создайте "fakemain", который вызывается со списком. Затем проверьте различные вероятные длины списков и содержимое. Затем вы можете вызвать свою фальшивую основную часть из реальной, передавая sys.argv, зная, что fakemain работает, или измените часть "if name...", чтобы выполнить обычную функцию при не-модульном тестировании условия.
Ответ 7
Вы можете прикрепить обертку вокруг своей функции, которая подготавливает sys.argv перед вызовом и восстанавливает его при выходе:
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
Тогда вы можете просто сделать
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"
Поскольку ошибки передаются правильно, они не должны вмешиваться в внешние экземпляры, используя тестовый код, например pytest
.
Ответ 8
Я достиг этого, создав диспетчер выполнения, который установил бы аргументы по моему выбору и удалил их при выходе:
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():
...