Python, Unittest, sys.argv используется для определения глобального var в скрипте. Как правильно провести тестирование?

#python #unit-testing

Вопрос:

Я хочу протестировать сценарий, который выглядит следующим образом:
my_script.py

 import argparse, sys

def my_parser(args=sys.argv[1:]):
  parser = argparse.ArgumentParser(description="my parser")
  parser.add_argument("--my-arg", dest="my_arg", required=True)
  args, _ = parser.parse_known_args(args=args)
  return args

args = my_parser()

def do_something():
  res = 5 1
  return res

def main():
  use_args = args
  res = do_something()
  # whatevere here
 

Когда я пытаюсь запустить unittest для функции do_something() , я всегда получаю ошибку синтаксического анализатора, требующего входных аргументов. Мой тест-это что-то вроде:
my_test.py

 from unittest import TestCase
from my_script import sys, do_something

class MyTest(TestCase):
  def test_do_something(self):
    test_sys_argv = ["dummy", "--my-arg", "dummy_arg"]
    with patch.object(sys, "argv", test_sys_argv):
      out = do_something()
    self.assertEqual(out, 6)
 

То, как я делаю патч, не работает. Я понимаю, что таким образом я исправляю любую ссылку sys.argv на функцию do_something() , в то время как при вызове теста sys.argv для сценария она пуста.
Можно ли решить эту проблему без рефакторинга переменной args внутри функции? Я хотел бы иметь args переменную my_script.py в качестве глобальной переменной скрипта.
Заранее спасибо за любую помощь.

Ответ №1:

Если вы хотите протестировать my_parser , просто введите его вручную в свой тест. если вы хотите протестировать do_something , вам sys.argv это вообще не нужно, так как fn никогда не использует args.

Проблема здесь как раз в том, что линия

 args = my_parser()
 

Запускается всякий раз, когда сценарий импортируется, т. е. перед тестом.* Переместите его в main или в if __name__ == "__main__" блок. Ничто не мешает args быть глобальным var, если вы действительно этого хотите:

 args = None

if __name__ == "__main__":
    args = my_parser()
 

Я сильно подозреваю, что вы будете оценивать аргументы только один раз-во время выполнения, и в этом случае я не совсем понимаю, почему вы просто не используете argparse обычный способ без sys.argv.

Ваша попытка исправить sys.argv не работает, потому что это происходит после импорта, и функция уже получила пустой sys.argv, когда она была впервые определена. (Изменяемые аргументы по умолчанию привязываются, когда функция определена, хотя в данном случае это даже не имеет значения, так как функция вызывается до того, как вы приступите к тестированию.) Возможно, вы захотите определить его следующим образом:

 def my_parser(args = None):
    args = args or sys.argv[1:]
    ...
 

Поскольку sys.argv теперь вызывается внутри функции, она будет оцениваться во время выполнения, и вы можете исправить ее, если вам нужно. Помимо тестирования, я не вижу никаких причин для этого, так как вы обычно не изменяете sys.argv при запуске.

*Вы можете обойти это, импортировав внутри тестовой функции… но все дело в том, что в настоящее время ваша «глобальная переменная» в модуле небезопасна для импорта. Я не совсем уверен, каков здесь процесс выполнения.

P.S. Если вы пытаетесь протестировать приложение командной строки, возможно, вам захочется взглянуть на одну из платформ тестирования для моделирования вызовов с различными аргументами.

Комментарии:

1. Спасибо! Считаете ли вы, что использование argparse обычного способа решило бы проблему во время тестов?

2. @Flexo использует argparse обычным способом, т. е. просто вызывает синтаксический анализатор без добавления sys.argv. вы просто сделаете свой код более обычным = проще для чтения другими. Проблема с тестами на данный момент заключается только в том, что вы пытаетесь проанализировать импорт , что, конечно, не удается.

3. Я вижу, большое спасибо