【问题标题】:How to test Python classes that depend on argparse?如何测试依赖于 argparse 的 Python 类?
【发布时间】:2017-07-08 22:25:58
【问题描述】:

下面的粘贴包含来自三个独立 Python 文件的相关 sn-ps。第一个是从命令行调用的脚本,它在给定参数的情况下实例化 CIPuller。发生的情况是脚本被调用如下: script.py ci(其他要被 argparse 吞噬的 args)。

第二个是名为Puller 的子类的一部分。第三个是Puller 子类的一部分,称为CIPuller

这非常有效,因为调用了正确的子类,任何使用错误其他参数的用户都可以看到他们给定子类的正确参数,以及来自超类的通用参数。 (虽然我在离线时知道也许我应该为此使用argparse sub-commands。)

我一直在尝试为这些类编写测试。目前,我需要一个ArgumentParser 来实例化类,但在测试中我没有从命令行实例化东西,因此我的ArgumentParser 没用。

我尝试在测试工具中创建一个ArgumentParser 以传递给测试代码中的CIPuller's 构造函数,但是如果我在那里使用add_argument,argparse 在调用add_argument 时抱怨双(重复)参数是可以理解的在CIPuller 构造函数中。

用参数测试这些类的合适设计是什么?

#!/usr/bin/env python                                                             

from ci_puller import CIPuller                                                    
import argparse                                                                   
import sys                                                                        

# Using sys.argv[1] for the argument here, as we don't want to pass that onto     
# the subclasses, which should receive a vanilla ArgumentParser                   
puller_type = sys.argv.pop(1)                                                     

parser = argparse.ArgumentParser(                                                 
    description='Throw data into Elasticsearch.'                                  
)                                                                                 

if puller_type == 'ci':                                                           
    puller = CIPuller(parser, 'single')                                         
else:                                                                             
    raise ValueError("First parameter must be a supported puller. Exiting.")      

puller.run()                                                                      


class Puller(object):                                                             

    def __init__(self, parser, insert_type):                                      
        self.add_arguments(parser)                                                
        self.args = parser.parse_args()                                           

        self.insert_type = insert_type                                            

    def add_arguments(self,parser):
        parser.add_argument(                                                      
            "-d", "--debug",                                                      
            help="print debug info to stdout",                                    
            action="store_true"                                                   
        )                                                                         

        parser.add_argument(                                                      
            "--dontsend",                                                         
            help="don't actually send anything to Elasticsearch",                 
            action="store_true"                                                   
        )                                                                         

        parser.add_argument(                                                      
            "--host",                                                             
            help="override the default host that the data is sent to",            
            action='store',                                                       
            default='kibana.munged.tld'                                     
        )                             

class CIPuller(Puller):                                                           

    def __init__(self, parser, insert_type):                                      
        self.add_arguments(parser)

        self.index_prefix = "code"                                                
        self.doc_type = "cirun"                                                   

        self.build_url = ""                                                       
        self.json_url = ""                                                        
        self.result = []                                                          

        super(CIPuller, self).__init__(parser, insert_type)                       

    def add_arguments(self, parser):                                              
        parser.add_argument(                                                      
            '--buildnumber',                                                      
            help='CI build number',                                               
            action='store',                                                       
            required=True                                                         
        )                                                                         

        parser.add_argument(                                                      
            '--testtype',                                                         
            help='Job type per CI e.g. minitest / feature',                       
            choices=['minitest', 'feature'],                                      
            required=True                                                         
        )                                                                         

        parser.add_argument(                                                      
            '--app',                                                              
            help='App e.g. sapi / stats',                                         
            choices=['sapi', 'stats'],                                            
            required=True                                                         
        )                                                                         

【问题讨论】:

    标签: python argparse pytest


    【解决方案1】:

    argparse 的单元测试很棘手。有一个 test/test_argparse.py 文件作为整个 Python 单元测试的一部分运行。但它有一个复杂的自定义测试工具来处理大多数情况。

    存在三个基本问题,1) 使用测试值调用 parse_args,2) 测试结果 args,3) 测试错误。

    测试生成的args 相对容易。 argparse.Namespace 类具有简单的 __eq__ 方法,因此您可以针对另一个命名空间测试一个命名空间。

    有两种测试输入的方法。一是修改sys.argv。最初sys.argv 包含用于测试人员的字符串。

    self.args = parser.parse_args()
    

    默认测试sys.argv[1:]。因此,如果您更改 sys.argv,您可以测试自定义值。

    但是你也可以给parse_args一个自定义列表。 argparse 文档在其大多数示例中都使用了它。

    self.args = parser.parse_args(argv=myargv)
    

    如果myargNone,它使用sys.argv[1:]。否则,它将使用该自定义列表。

    测试错误需要自定义parse.error 方法(请参阅文档)或将parse_args 包装在可以捕获sys.exit 异常的try/except 块中。

    How do you write tests for the argparse portion of a python module?

    python unittest for argparse

    Argparse unit tests: Suppress the help message

    Unittest with command-line arguments

    Using unittest to test argparse - exit errors

    【讨论】:

    • 为测试修改sys.argv 似乎是最简单的。但是,修改sys.argv 还是修改ArgumentParser 以默认使用您的自定义arvg 是更常见的做法?
    • Puller 提供一个单独的parse 方法,该方法采用传递给parse_args 的可选argv 参数。然后你可以在正常运行模式下使用它,或者使用测试值。 test_argparse 测试这两种方法。
    • 这真的很有帮助,尽管我已经浏览过一些提到的链接,但我没有想到设置 sys.argv 的基本想法。有时我们只见树木不见森林。 :)
    猜你喜欢
    • 2018-06-24
    • 2016-01-20
    • 2018-12-27
    • 2023-03-23
    • 1970-01-01
    • 2011-05-26
    • 1970-01-01
    • 2016-10-08
    • 1970-01-01
    相关资源
    最近更新 更多