pytest и argparse: код работает, но тесты не пройдены - PullRequest
0 голосов
/ 15 января 2020

Я хотел бы добавить некоторые параметры командной строки в некоторый код, который я пишу, и тесты не пройдены, когда у меня есть argparse.

Вот урезанная версия базовый класс:

import argparse
import sys


class PreProcessor:

    def parse_args(self, args):
        parser = argparse.ArgumentParser(description='Arguments for PreProcessor scripts.')
        parser.add_argument('-i', '--ignore-pid', help='If the script is already running, it will not re-run. This over-rides that.', action="store_true")
        parser.add_argument('-v', '--verbose', type=int, choices=[0,1,2,3], default=1, help='Be noisy when running [0 is completely silent, 3 is debug-level. defaults to 1].')
        return parser.parse_args()

    def __init__(
        self,
        code,
    ):
        if not code:
            raise ValueError("A code must be defined")
        self.code = code

        # These two lines
        self.args = self.parse_args(sys.argv)
        print(f"type: {type(self.args)}, data: {self.args}")

.... и вот тестовый файл для него:

import pytest
from .example import PreProcessor


def test_base_initialisation():
    foo = PreProcessor(code="foo")
    assert foo.code == "foo"

Я получаю такую ​​ошибку:

platform linux -- Python 3.6.9, pytest-3.3.2, py-1.5.2, pluggy-0.6.0 -- /usr/bin/python3
cachedir: .cache
rootdir: /home/kiz/development/FindingStudySpaces/preprocessors, inifile: pytest.ini
plugins: cov-2.5.1
collected 1 item                                                                                                                                                                                                                                                       
preprocessors/test_example.py::test_base_initialisation FAILED                     [100%]

 generated xml file: /home/kiz/development/FindingStudySpaces/preprocessors/pytest-results.xml 

----------- coverage: platform linux, python 3.6.9-final-0 -----------
Name                             Stmts   Miss  Cover
----------------------------------------------------
preprocessors/__init__.py            0      0   100%
preprocessors/example.py            14      2    86%
preprocessors/preprocessors.py     140    140     0%
----------------------------------------------------
TOTAL                              154    142     8%
Coverage HTML written to dir htmlcov

======================================== FAILURES ========================================
________________________________ test_base_initialisation ________________________________

self = ArgumentParser(prog='pytest-3', usage=None, description='Arguments for PreProcessor scripts.', formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
action = _StoreAction(option_strings=['-v', '--verbose'], dest='verbose', nargs=None, const=None, default=1, type=<class 'int'>...es=[0, 1, 2, 3], help='Be noisy when running [0 is completely silent, 3 is debug-level. defaults to 1].', metavar=None)
arg_string = 'preprocessors/test_example.py'

    def _get_value(self, action, arg_string):
        type_func = self._registry_get('type', action.type, action.type)
        if not callable(type_func):
            msg = _('%r is not callable')
            raise ArgumentError(action, msg % type_func)

        # convert the value to the appropriate type
        try:
>           result = type_func(arg_string)
E           ValueError: invalid literal for int() with base 10: 'preprocessors/test_example.py'
         ... <snip loads of stuff> ...
                 # TypeErrors or ValueErrors also indicate errors
        except (TypeError, ValueError):
            name = getattr(action.type, '__name__', repr(action.type))
            args = {'type': name, 'value': arg_string}
            msg = _('invalid %(type)s value: %(value)r')
>           raise ArgumentError(action, msg % args)
E           argparse.ArgumentError: argument -v/--verbose: invalid int value: 'preprocessors/test_example.py'
         ... <snip loads more stuff> ...

Я попытался передать sys.argv[1:] в методе init - без разницы

Если я закомментирую вызов argparse (ie, строки, где он говорит # These two lines), то я передаю просто отлично ..

Я действительно не хочу добавлять макет / патч к каждому тестовому методу, не помещать какие-либо предложения в живой код, чтобы проверить, был ли вызван def parse_args(self, args) с помощью тестовой процедуры

.... мой google-foo находит несколько обсуждений по тестированию передачи параметров (что хорошо), но я ничего не могу найти о сбое argparse на этом уровне.

Помощь?

Ответы [ 2 ]

0 голосов
/ 17 января 2020

Спасибо обоим ... это то, что на самом деле сработало для меня ...

Подайте __init__, чтобы искать pytest (это действительно должно искать unittest или node тоже, если честно) в sys.argv[0] и передает пустой список функции argparser, если она это делает:

import argparse
import re
import sys

class PreProcessor:

def parse_args(self, args):
    parser = argparse.ArgumentParser(description='Arguments for PreProcessor scripts.')
    parser.add_argument('-i', '--ignore-pid', help='If the script is already running, it will not re-run. This over-rides that.', action="store_true")
    parser.add_argument('-v', '--verbose', type=int, choices=[0,1,2,3], default=1, help='Be noisy when running [0 is completely silent, 3 is debug-level. defaults to 1].')
    return parser.parse_args(args)

def __init__(
    self,
    code,
):
    if not code:
        raise ValueError("A code must be defined")
    self.code = code

    if re.search('pytest', sys.argv[0]):
        self.args = self.parse_args([])
    else:
        self.args = self.parse_args(sys.argv[1:])

Теперь в моих тестах я могу patch объект sys как Нужно:

import pytest
import re
import sys

from unittest.mock import patch

from .example import PreProcessor


def test_base_initialisation():
    foo = PreProcessor(code="foo")
    assert foo.code == "foo"

def test_known_command_line_options(capsys):
    foo = PreProcessor(code="foo")
    test_args = ["fake", "-h"]
    with patch.object(sys, 'argv', test_args):
        with pytest.raises(SystemExit):
            foo.parse_args(sys.argv)
            captured = capsys.readouterr()
            assert re.match(captured, "usage: fake [-h] [-i] [-v {0,1,2,3}]")
            assert re.search(captured, "Arguments for PreProcessor scripts")

    test_args = ["fake", "--help"]
    with patch.object(sys, 'argv', test_args):
        with pytest.raises(SystemExit):
            foo.parse_args(sys.argv)
            captured = capsys.readouterr()
            assert re.match(captured, "usage: fake [-h] [-i] [-v {0,1,2,3}]")
            assert re.search(captured, "Arguments for PreProcessor scripts")

def test_unknown_command_line_options(capsys):
    foo = PreProcessor(code="foo")
    test_args = ["fake", "-a"]
    with patch.object(sys, 'argv', test_args):
        with pytest.raises(SystemExit):
            foo.parse_args(sys.argv)
            captured = capsys.readouterr()
            assert re.match(captured, "usage: fake [-h] [-i] [-v {0,1,2,3}]")
            assert re.search(captured, "unrecognized arguments: -a")

и, для справки, текст справки из argparse гласит:

$> python3 labmon.py -h 
usage: labmon.py [-h] [-i] [-v {0,1,2,3}]

Arguments for PreProcessor scripts.

optional arguments:
  -h, --help            show this help message and exit
  -i, --ignore-pid      If the script is already running, it will not re-run.
                        This over-rides that.
  -v {0,1,2,3}, --verbose {0,1,2,3}
                        Be noisy when running [0 is completely silent, 3 is
                        debug-level. defaults to 1].

и

$> python3 labmon.py -a  
usage: labmon.py [-h] [-i] [-v {0,1,2,3}]
labmon.py: error: unrecognized arguments: -a

... Надеюсь, это поможет другим .

0 голосов
/ 15 января 2020

эта строка:

        return parser.parse_args()

должно быть

        return parser.parse_args(args)

в противном случае, parser.parse_args по умолчанию sys.argv[1:], что, вероятно, не то, что вы хотите

также sys.argv, который передается в это значения из вашего вызова Pytest - вы, вероятно, хотите установить его в то, что имеет смысл для вашей программы

у вас есть ~ два вида варианты здесь

  1. monkeypatch sys.argv:
from unittest import mock

def test():
    with mock.patch.object(sys, 'argv', ['yourprog', 'args', 'here']):
        ...
зависимость вводит ваши аргументы и передает их через вашу программу:
class C:
    def __init__(self, code, args=None):
        ...
        self.parse_args(args)

    def parse_args(self, args=None):
        ...
        return parser.parse_args(args)
def test():
    thing = C(..., args=['args', 'here'])
...