argparse терпит неудачу при вызове из теста unittest

В файле (скажем, parser.py) у меня есть:

import argparse

def parse_cmdline(cmdline=None):
    parser = argparse.ArgumentParser()
    parser.add_argument('--first-param',help="Does foo.")
    parser.add_argument('--second-param',help="Does bar.")

    if cmdline is not None:
        args = parser.parse_args(cmdline)
    else:
        args = parser.parse_args()

    return vars(args)

if __name__=='__main__':
    print parse_cmdline()

Конечно же, при вызове из командной строки он работает и дает мне почти то, что я ожидаю:

$ ./parser.py --first-param 123 --second-param 456
{'first_param': '123', 'second_param': '456'}

Но затем я хочу unittest его, поэтому я пишу файл test_parser.py:

import unittest
from parser import parse_cmdline

class TestParser(unittest.TestCase):
    def test_parse_cmdline(self):
        parsed = parse_cmdline("--first-param 123 --second-param 456")

        self.assertEqual(parsed['first_param'],'123')
        self.assertEqual(parsed['second_param'],'456')

if __name__ == '__main__':
    unittest.main()

Затем я получаю следующую ошибку:

usage: test_parser.py [-h] [--first-param FIRST_PARAM]
                      [--second-param SECOND_PARAM]
test_parser.py: error: unrecognized arguments: - - f i r s t - p a r a m   1 2 3   - - s e c o n d - p a r a m   4 5 6
E
======================================================================
ERROR: test_parse_cmdline (__main__.TestParser)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "./test_parser.py", line 8, in test_parse_cmdline
    parsed = parse_cmdline("--first-param 123 --second-param 456")
  File "/home/renan/test_argparse/parser.py", line 12, in parse_cmdline
    args = parser.parse_args(cmdline)
  File "/usr/lib/python2.7/argparse.py", line 1691, in parse_args
    self.error(msg % ' '.join(argv))
  File "/usr/lib/python2.7/argparse.py", line 2361, in error
    self.exit(2, _('%s: error: %s\n') % (self.prog, message))
  File "/usr/lib/python2.7/argparse.py", line 2349, in exit
    _sys.exit(status)
SystemExit: 2

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (errors=1)

Как видно, указанная мной командная строка (--first-param 123 --second-param 456) стала - - f i r s t - p a r a m 1 2 3 - - s e c o n d - p a r a m 4 5 6 (каждый символ отделяется пробелом).

Я не понимаю, почему: что я делаю неправильно?


person Renan    schedule 20.08.2013    source источник


Ответы (2)


argparse нужен «вектор аргументов», то есть список отдельных аргументов, а не строка «командной строки».

И просто звонок split ничего не решит. Например, эта командная строка из оболочки:

python script.py --first-param '123 456' --second-param 789

… правильно даст вам 123 456 и 789, но эта строка кода:

parse_cmdline("--first-param '123 456' --second-param 789")

не буду; он даст вам '123 и 789 с дополнительным 456', с которым он не знает, что делать.

На самом деле даже это неправильно:

parse_cmdline("--first-param '123' --second-param 789")

… потому что вы получите '123' вместо 123.


Есть два способа справиться с этим.

Во-первых, если вы знаете, что вы пытаетесь передать, вы можете просто передать это как список, а не строку, и вам не нужно беспокоиться о кропотливом цитировании и разделении деталей:

parse_cmdline(["--first-param", "123 456", "--second-param", "789"])

В качестве альтернативы, если вы точно не знаете, что пытаетесь передать, вы просто знаете, как это выглядит в оболочке, вы можете использовать shlex:

if cmdline is not None:
    args = parser.parse_args(shlex.split(cmdline))

… и теперь Python будет разделять вашу командную строку так же, как стандартная оболочка Unix, получая 123 456 в качестве единственного параметра.

person abarnert    schedule 20.08.2013
comment
Решение на основе shlex меня вполне устраивает. Спасибо! - person Renan; 20.08.2013
comment
Хотя полезно помнить о различиях между split и shlex, он не проверяет сам arpgarse. test_argparse.py часто использует split, но не импортирует shlex. Если пробелы внутри аргументов имеют значение, я бы просто скармливал parse_args тестовый список слов. - person hpaulj; 20.08.2013

Чтобы ответить себе (я понял свою ошибку через несколько минут):

я должен

if cmdline is not None:
    args = parser.parse_args(cmdline.split())
else:
    args = parser.parse_args()

Теперь тест проходит правильно!

person Renan    schedule 20.08.2013
comment
С этим есть серьезная проблема. Попробуйте с parse_cmdline("--first-param '123 456' --second-param 789"). Это не будет делать то же самое, как если бы вы передавали то же самое из реальной оболочки. - person abarnert; 20.08.2013
comment
@abarnert Я вижу; что было бы лучшим решением для этого тогда? - person Renan; 20.08.2013
comment
Это зависит от вашего конкретного варианта использования. Я написал ответ, объясняющий два стандартных решения. - person abarnert; 20.08.2013
comment
Да, в исходном случае parse_args оборачивает тестовую строку в list(), превращая ее в список отдельных символов. Следовательно, пробелы в - - f i .... Комментарий в коде: «убедитесь, что аргументы изменяемы». Очевидно, он больше озабочен получением кортежа, чем строки. Возможно, он должен возражать, если ему дается строка. - person hpaulj; 20.08.2013