आप अजगर मॉड्यूल के अर्गपर्स भाग के लिए परीक्षण कैसे लिखते हैं? [बन्द है]


162

मेरे पास एक पायथन मॉड्यूल है जो argparse लाइब्रेरी का उपयोग करता है। मैं कोड आधार के उस भाग के लिए परीक्षण कैसे लिखूं?


argparse एक कमांड लाइन इंटरफ़ेस है। कमांड लाइन के माध्यम से आवेदन आमंत्रित करने के लिए अपने परीक्षण लिखें।
होमर 6

आपका प्रश्न यह समझना मुश्किल बनाता है कि आप क्या परीक्षण करना चाहते हैं। मुझे संदेह है कि यह अंततः है, उदाहरण के लिए "जब मैं कमांड लाइन तर्क एक्स, वाई, जेड का उपयोग करता हूं तो फ़ंक्शन foo()कहा जाता है"। का मजाक sys.argvयदि ऐसा है जवाब है। क्लि-टेस्ट-हेल्पर्स पायथन पैकेज पर एक नज़र डालें । यह भी देखें stackoverflow.com/a/58594599/202834
Peterino

जवाबों:


214

आपको अपना कोड रिफलेक्टर करना चाहिए और पार्सिंग को एक फंक्शन में ले जाना चाहिए:

def parse_args(args):
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser.parse_args(args)

फिर अपने mainफंक्शन में आपको बस इसे कॉल करना चाहिए:

parser = parse_args(sys.argv[1:])

(जहां इसका पहला तत्व sys.argvस्क्रिप्ट नाम का प्रतिनिधित्व करता है, उसे CLI ऑपरेशन के दौरान अतिरिक्त स्विच के रूप में नहीं भेजने के लिए हटा दिया जाता है।)

अपने परीक्षणों में, आप इसके बाद जो भी तर्क देना चाहते हैं, उसके साथ आप पार्सर फ़ंक्शन को कॉल कर सकते हैं:

def test_parser(self):
    parser = parse_args(['-l', '-m'])
    self.assertTrue(parser.long)
    # ...and so on.

इस तरह आपको कभी भी सिर्फ पार्सर का परीक्षण करने के लिए अपने आवेदन के कोड को निष्पादित नहीं करना पड़ेगा।

यदि आपको अपने आवेदन में बाद में अपने पार्सर में विकल्प बदलने और / या जोड़ने की आवश्यकता है, तो एक कारखाना विधि बनाएं:

def create_parser():
    parser = argparse.ArgumentParser(...)
    parser.add_argument...
    # ...Create your parser as you like...
    return parser

आप चाहें तो बाद में इसमें फेरबदल कर सकते हैं, और एक परीक्षण जैसा दिख सकता है:

class ParserTest(unittest.TestCase):
    def setUp(self):
        self.parser = create_parser()

    def test_something(self):
        parsed = self.parser.parse_args(['--something', 'test'])
        self.assertEqual(parsed.something, 'test')

4
आपके उत्तर के लिए धन्यवाद। जब एक निश्चित तर्क पारित नहीं होता है तो हम त्रुटियों के लिए परीक्षण कैसे करते हैं?
प्रतिक खडलोया

3
@PratikKhadloya यदि तर्क की आवश्यकता है और यह पारित नहीं हुआ है, तो argparse एक अपवाद बढ़ाएगा।
विक्टर केर्केज

2
@PratikKhadloya हाँ, संदेश दुर्भाग्य से वास्तव में मददगार नहीं है :( यह सिर्फ है 2... argparseयह बहुत टेस्ट फ्रेंडली नहीं है क्योंकि यह सीधे प्रिंट करता है sys.stderr...
विक्टर केर्केज़

1
@ViktorKerkez आप किसी विशिष्ट संदेश की जांच करने के लिए sock.stderr का उपयोग करने में सक्षम हो सकते हैं, या तो mock.assert_called_with या mock_calls की जांच करके, अधिक विवरण के लिए docs.pythy.org/3/library/unittest.mock.html देखें । यह भी देखें stackoverflow.com/questions/6271947/... stdin मजाक का एक उदाहरण के लिए। (stderr समान होना चाहिए)
BryCoBat

1
@PratikKhadloya से निपटने / परीक्षण त्रुटियों stackoverflow.com/a/55234595/1240268 के
एंडी हेडन

25

"argparse part" थोड़ा अस्पष्ट है इसलिए यह उत्तर एक भाग पर केंद्रित है: parse_argsविधि। यह वह विधि है जो आपकी कमांड लाइन के साथ इंटरैक्ट करती है और सभी पारित मूल्यों को प्राप्त करती है। मूल रूप से, आप क्या parse_argsरिटर्न मॉक कर सकते हैं ताकि कमांड लाइन से वास्तव में मान प्राप्त करने की आवश्यकता न हो। mock पैकेज अजगर संस्करण 2.6-3.2 के लिए पिप के माध्यम से स्थापित किया जा सकता। यह unittest.mockसंस्करण 3.3 के बाद से मानक पुस्तकालय का हिस्सा है ।

import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(kwarg1=value, kwarg2=value))
def test_command(mock_args):
    pass

आपको अपनी सभी कमांड विधि के args को शामिल करना होगा, Namespace भले ही वे उत्तीर्ण न हों। उन लोगों के मूल्य दे None। ( डॉक्स देखें ) यह शैली उन मामलों के लिए जल्दी से परीक्षण करने के लिए उपयोगी है जहां प्रत्येक विधि तर्क के लिए विभिन्न मान पारित किए जाते हैं। यदि आप Namespaceअपने परीक्षणों में कुल गैर-निर्भरता के लिए खुद का मजाक बनाने का विकल्प चुनते हैं , तो सुनिश्चित करें कि यह वास्तविक Namespaceवर्ग के समान व्यवहार करता है ।

नीचे अर्गपर्स लाइब्रेरी से पहली स्निपेट का उपयोग करके एक उदाहरण दिया गया है।

# test_mock_argparse.py
import argparse
try:
    from unittest import mock  # python 3.3+
except ImportError:
    import mock  # python 2.6-3.2


def main():
    parser = argparse.ArgumentParser(description='Process some integers.')
    parser.add_argument('integers', metavar='N', type=int, nargs='+',
                        help='an integer for the accumulator')
    parser.add_argument('--sum', dest='accumulate', action='store_const',
                        const=sum, default=max,
                        help='sum the integers (default: find the max)')

    args = parser.parse_args()
    print(args)  # NOTE: this is how you would check what the kwargs are if you're unsure
    return args.accumulate(args.integers)


@mock.patch('argparse.ArgumentParser.parse_args',
            return_value=argparse.Namespace(accumulate=sum, integers=[1,2,3]))
def test_command(mock_args):
    res = main()
    assert res == 6, "1 + 2 + 3 = 6"


if __name__ == "__main__":
    print(main())

लेकिन अब आपका unittest कोड भी argparseऔर उसकी Namespaceकक्षा पर निर्भर करता है । आपको मजाक करना चाहिए Namespace
1717

1
@ डंके की चोट पर माक्र्समास्टर ने माफी मांगी। मैंने अपने उत्तर को स्पष्टीकरण और संभावित उपयोगों के साथ अद्यतन किया। मैं यहां भी सीख रहा हूं, यदि आप चाहें तो क्या आप (या कोई और) ऐसे मामले प्रदान कर सकते हैं, जहां रिटर्न वैल्यू का मजाक उड़ाना फायदेमंद है? (या कम से कम मामलों में जहां पर नहीं दिया गया मान मजाक हानिकारक है)
munsu

1
from unittest import mockअब सही आयात विधि है - अच्छी तरह से कम से कम python3 के लिए
माइकल हॉल

1
@MichaelHall धन्यवाद मैंने स्निपेट अपडेट किया और प्रासंगिक जानकारी जोड़ी।
मुनसु

1
Namespaceयहाँ कक्षा का उपयोग ठीक वही है जो मैं देख रहा था। परीक्षण अभी भी निर्भर होने के बावजूद argparse, यह argparseपरीक्षण के तहत कोड द्वारा विशेष कार्यान्वयन पर भरोसा नहीं करता है , जो मेरी इकाई परीक्षणों के लिए महत्वपूर्ण है। इसके अलावा, इसका उपयोग करना आसान हैpytest की parametrize()विधि जल्दी से एक टेम्प्लेट की गई नकली है जिसमें शामिल हैं के साथ विभिन्न तर्क संयोजनों का परीक्षण करने के return_value=argparse.Namespace(accumulate=accumulate, integers=integers)
एसीटोन

17

अपने बनाओ main()समारोह ले argvबल्कि दे यह तुलना में एक तर्क के रूप से पढ़ने sys.argvके रूप में यह डिफ़ॉल्ट रूप से होगा :

# mymodule.py
import argparse
import sys


def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('-a')
    process(**vars(parser.parse_args(args)))
    return 0


def process(a=None):
    pass

if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))

फिर आप सामान्य रूप से परीक्षण कर सकते हैं।

import mock

from mymodule import main


@mock.patch('mymodule.process')
def test_main(process):
    main([])
    process.assert_call_once_with(a=None)


@mock.patch('foo.process')
def test_main_a(process):
    main(['-a', '1'])
    process.assert_call_once_with(a='1')

9
  1. का उपयोग करके अपनी arg सूची पॉपुलेट करें sys.argv.append() और फिर कॉल करें parse() , परिणाम जांचें और दोहराएं।
  2. अपने झंडे के साथ एक बैच / बैश फ़ाइल से कॉल करें और एक डंप args झंडा।
  3. अपने सभी तर्क पार्सिंग को एक अलग फ़ाइल में और if __name__ == "__main__":कॉल पार्स में डालें और परिणामों का मूल्यांकन करें / फिर एक बैच / डीएएस फ़ाइल से इसका परीक्षण करें।

9

मैं मूल सेवारत स्क्रिप्ट को संशोधित नहीं करना चाहता था, इसलिए मैंने केवल इस sys.argvभाग में भाग लेने का मजाक उड़ाया ।

from unittest.mock import patch

with patch('argparse._sys.argv', ['python', 'serve.py']):
    ...  # your test code here

यह तब टूटता है जब एर्गपर्स कार्यान्वयन में बदलाव होता है लेकिन त्वरित परीक्षण स्क्रिप्ट के लिए पर्याप्त होता है। परीक्षण लिपियों में विशिष्टता की तुलना में संवेदनशीलता बहुत अधिक महत्वपूर्ण है।


6

एक पार्सर परीक्षण का एक सरल तरीका है:

parser = ...
parser.add_argument('-a',type=int)
...
argv = '-a 1 foo'.split()  # or ['-a','1','foo']
args = parser.parse_args(argv)
assert(args.a == 1)
...

एक और तरीका संशोधित करने के लिए है sys.argv , और कॉल करना हैargs = parser.parse_args()

वहाँ परीक्षण के उदाहरण के बहुत सारे हैं argparseमेंlib/test/test_argparse.py


5

parse_argsएक SystemExitऔर प्रिंट करने के लिए stderr फेंकता है , आप इन दोनों को पकड़ सकते हैं:

import contextlib
import io
import sys

@contextlib.contextmanager
def captured_output():
    new_out, new_err = io.StringIO(), io.StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

def validate_args(args):
    with captured_output() as (out, err):
        try:
            parser.parse_args(args)
            return True
        except SystemExit as e:
            return False

आप stderr (उपयोग करके) का निरीक्षण करते हैं err.seek(0); err.read() लेकिन आमतौर पर यह कि ग्रैन्युलैरिटी की आवश्यकता नहीं है)।

अब आप इसका उपयोग कर सकते हैं assertTrueया जो भी आपको पसंद हो:

assertTrue(validate_args(["-l", "-m"]))

वैकल्पिक रूप से आप एक अलग त्रुटि (इसके बजाय SystemExit) को पकड़ना और हटाना चाहते हैं :

def validate_args(args):
    with captured_output() as (out, err):
        try:
            return parser.parse_args(args)
        except SystemExit as e:
            err.seek(0)
            raise argparse.ArgumentError(err.read())

2

जब argparse.ArgumentParser.parse_argsएक फ़ंक्शन से परिणाम गुजरते हैं , तो मैं कभी-कभी namedtupleपरीक्षण के लिए मॉक तर्कों का उपयोग करता हूं ।

import unittest
from collections import namedtuple
from my_module import main

class TestMyModule(TestCase):

    args_tuple = namedtuple('args', 'arg1 arg2 arg3 arg4')

    def test_arg1(self):
        args = TestMyModule.args_tuple("age > 85", None, None, None)
        res = main(args)
        assert res == ["55289-0524", "00591-3496"], 'arg1 failed'

    def test_arg2(self):
        args = TestMyModule.args_tuple(None, [42, 69], None, None)
        res = main(args)
        assert res == [], 'arg2 failed'

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

0

सीएलआई (कमांड लाइन इंटरफ़ेस) के परीक्षण के लिए, और कमांड आउटपुट नहीं मैंने ऐसा कुछ किया

import pytest
from argparse import ArgumentParser, _StoreAction

ap = ArgumentParser(prog="cli")
ap.add_argument("cmd", choices=("spam", "ham"))
ap.add_argument("-a", "--arg", type=str, nargs="?", default=None, const=None)
...

def test_parser():
    assert isinstance(ap, ArgumentParser)
    assert isinstance(ap, list)
    args = {_.dest: _ for _ in ap._actions if isinstance(_, _StoreAction)}
    
    assert args.keys() == {"cmd", "arg"}
    assert args["cmd"] == ("spam", "ham")
    assert args["arg"].type == str
    assert args["arg"].nargs == "?"
    ...
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.