পাইথন মডিউলটির আরগপারস অংশের জন্য আপনি কীভাবে পরীক্ষা লিখবেন? [বন্ধ]


162

আমার কাছে পাইথন মডিউল রয়েছে যা আরগপার্স লাইব্রেরি ব্যবহার করে। কোড বেসের সেই বিভাগটির জন্য আমি কীভাবে পরীক্ষাগুলি লিখব?


আরগপারস হ'ল একটি কমান্ড লাইন ইন্টারফেস। কমান্ড লাইনের মাধ্যমে অ্যাপ্লিকেশন প্রার্থনা করতে আপনার পরীক্ষা লিখুন।
হোমার 6

আপনার প্রশ্নটি আপনি কী পরীক্ষা করতে চান তা বুঝতে অসুবিধা হয় । আমি সন্দেহ করি এটি শেষ পর্যন্ত, যেমন "যখন আমি কমান্ড লাইন টি X, Y, Z ব্যবহার করি তখন ফাংশন foo()বলা হয়"। ঠাট্টা-বিদ্রূপ করা sys.argvযদি উত্তর হয় তবে তা যদি হয়। কটাক্ষপাত CLI-পরীক্ষা সাহায্যকারী পাইথন প্যাকেজ। আরও দেখুন 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স্ক্রিপ্টের নাম উপস্থাপন করে এটি সিএলআই অপারেশন চলাকালীন অতিরিক্ত সুইচ হিসাবে না প্রেরণে সরানো হয়েছে।)

আপনার পরীক্ষাগুলিতে, আপনি তারপরে যা পরীক্ষা করতে চান তার যে তালিকা যুক্তি দিয়ে পার্সার ফাংশনটি কল করতে পারেন:

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
@ প্রতীকখাদলোয়া যদি যুক্তি প্রয়োজন হয় এবং তা পাস না হয় তবে আরগপারস ব্যতিক্রম করবে।
ভিক্টর কেরকেজ

2
@ প্রতীকখাদলোয়া হ্যাঁ, বার্তাটি দুর্ভাগ্যক্রমে সত্যিই সহায়ক নয় :( এটি কেবল 2... argparseখুব টেস্ট বান্ধব নয়, কারণ এটি সরাসরি প্রিন্ট করে sys.stderr...
ভিক্টর কেরাকেজ

1
@ViktorKerkez আপনি একটি নির্দিষ্ট বার্তাটির জন্য চেক করতে পারেন mock.assert_called_with বা mock_calls পরীক্ষা দ্বারা উপহাস sys.stderr করতে সক্ষম হতে পারে, দেখুন docs.python.org/3/library/unittest.mock.html আরো বিস্তারিত জন্য। স্ট্যাডিনকে উপহাস করার উদাহরণ হিসাবে স্ট্যাকওভারফ্লো. com/ জিজ্ঞাসা / 62২7১194747/২ দেখুন (
স্টাডার

1
@PratikKhadloya পরিচালনা করার জন্য আমার উত্তর দেখুন / পরীক্ষার ত্রুটি stackoverflow.com/a/55234595/1240268
অ্যান্ডি হেডেন

25

"আরগপার্স অংশ" কিছুটা অস্পষ্ট তাই এই উত্তরটি একটি অংশকে কেন্দ্র করে: parse_argsপদ্ধতি। এটি সেই পদ্ধতি যা আপনার কমান্ড লাইনের সাথে ইন্টারঅ্যাক্ট করে এবং সমস্ত পাস করা মান পায়। মূলত, আপনি কী parse_argsপ্রত্যাবর্তন করে তা উপহাস করতে পারেন যাতে কমান্ড লাইন থেকে আসলে মান পাওয়া দরকার না get mock প্যাকেজ পাইথন সংস্করণ 2.6-3.2 জন্য পিপ মাধ্যমে ইনস্টল করা যাবে। unittest.mockসংস্করণ ৩.৩ থেকে এটি স্ট্যান্ডার্ড লাইব্রেরির অংশ ।

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

আপনার সমস্ত কমান্ড পদ্ধতির আরোগুলি 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())

তবে এখন আপনার ইউনিটেটেস্ট কোড argparseও তার Namespaceশ্রেণীর উপর নির্ভর করে । আপনি উপহাস করা উচিত Namespace
imrek

1
টুইটার আমি ব্যাখ্যা এবং সম্ভাব্য ব্যবহার সহ আমার উত্তর আপডেট করেছি। আমি এখানেও শিখছি তাই আপনি যদি চান তবে, আপনি (বা অন্য কেউ) এমন কোনও মামলা প্রদান করতে পারেন যেখানে প্রত্যাবর্তন মূল্যের উপহাস করা উপকারী? (বা অন্তত ক্ষেত্রে যেখানে এ না ফেরত মান উপহাস ক্ষতিকর হয়)
munsu

1
from unittest import mockএখন সঠিক আমদানি পদ্ধতি - কমপক্ষে পাইথন 3 এর জন্য ভাল
মাইকেল হল

1
@ মিশেলহাল ধন্যবাদ আমি স্নিপেট আপডেট করেছি এবং প্রাসঙ্গিক তথ্য যুক্ত করেছি।
মুনসু

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. আপনার আরগের তালিকাটি ব্যবহার করে sys.argv.append()কল করুন parse(), ফলাফলগুলি দেখুন এবং পুনরাবৃত্তি করুন।
  2. আপনার পতাকা এবং ডাম্প আরগস পতাকা সহ একটি ব্যাচ / ব্যাশ ফাইল থেকে কল করুন।
  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, আপনি এই দুটিই ধরতে পারেন:

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

আপনি স্টাডার পরিদর্শন করেন (ব্যবহার করে 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

CLI (কমান্ড লাইন ইন্টারফেস) পরীক্ষা করার জন্য, এবং কমান্ড আউটপুট নয় আমি এরকম কিছু করেছি

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 == "?"
    ...
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.