Подтвердить что ты не робот

Как установить 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 %  
4b9b3361

Ответ 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():
            ...