如何测试依赖于argparse的Python类?

How to test Python classes that depend on argparse?

以下粘贴包含来自三个单独的Python文件的相关代码段。第一个是从命令行调用的脚本,它在给定某些参数的情况下实例化CIPuller。会发生什么是脚本被调用类似于:
script.py ci(其他args被argparse吞噬)。

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

这非常有效,因为调用了正确的子类,并且使用错误的其他args的任何用户都可以看到给定子类的正确args,以及来自超类的泛型参数。 (虽然我知道离线,也许我应该使用argparse子命令。)

我试图为这些类编写测试。目前,我需要一个ArgumentParser来实例化类,但在测试中我没有从命令行实例化事物,因此我的ArgumentParser是无用的。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/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                                                        
        )

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

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

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

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

1
self.args = parser.parse_args()

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

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

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

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

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

你如何编写python模块的argparse部分的测试?

用于argparse的python unittest

Argparse单元测试:取消帮助消息

使用命令行参数进行单元测试

使用unittest测试argparse - 退出错误