有没有用python 压力测试实现的,可以测C/S架构的自动化测试工具?

保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
单击提交则表示您同意developerWorks
的条款和条件。 .
所有提交的信息确保安全。
developerWorks 社区:
我的概要信息
选择语言:
在 Linux 平台上进行大型项目开发过程中,测试人员需要定期(通常是每天),从代码库中更新代码、编译版本、运行全部测试脚本、收集并发布测试结果。这个过程既繁琐又耗时。通常我们希望能在下班后能自动完成这一系列操作,本文将讲述如何利用 Python 脚本轻松实现这一过程。
, 软件工程师,
丁朝杰,IBM中国系统与科技研发中心,System z 固件测试人员,主要从事下一代 System z 虚拟化平台固件相关测试工作。
, IBM i debugger developer, IBM
涂波,有 7 年在 linux/solaris 编程经验。从 2007 年加入 IBM 到现在一直在做 IBM i debugger 项目。目前他主要兴趣集中在工具链领域,包括 compiler, debugger, runtime 等等。
, 软件工程师,
赵亚丽,IBM System z 固件开发人员,主要从事下一代 System z 虚拟化平台固件相关开发工作。
从代码库迁出代码 ---- pexpect 的使用测试人员从代码库(例如 CVS )迁出代码的过程中,需要手动输入访问密码,而 Python 提供了 Pexpect 模块则能够将手动输入密码这一过程自动化。当然 Pexpect 也可以用来和 ssh、ftp、passwd、telnet 等命令行进行自动化交互。这里我们以 CVS 为例展示如何利用 Pexpect 从代码库迁出代码。清单 1. 用 pexpect 迁出代码库代码try:
chkout_cmd = 'cvs co project_code' #从代码库迁出 project_code 的内容
child = pexpect.spawn(chkout_cmd)
child.expect('password:')
child.sendline('your-password') #请替换"your-password"为真实密码
child.interact()
pass #忽略迁出代码中的错误在清单 1 中,我们用命令"cvs co project_code"从代码库中迁出了 project_code 的内容,我们也可以用该命令来更新已经迁出的代码。只需要将命令"cvs update" 传给类 pexpect.spawn()即可,详细的实现请参考代码文件。这里 interact()函数是必须的,用来在交互的方式下控制该子进程。有时代码库中会存在目录不一致行情况,迁出代码会因报错终止,所以需要异常处理(try ... execpt)来忽略该错误。编译代码和运行测试脚本 ---- subprocess 的使用测试人员获取最新的代码之后,就要对源码进行编译,并且运行测试用例。Python 语言提供了多种方法如 os.system()/os.popen()来执行一条命令,这里我们推荐用 subprocess 模块来创建子进程,完成代码编译和运行测试用例。因为 subprocess 支持主进程和子进程的交互,同时也支持主进程和子进程是同步执行还是异步执行。由于本文中的各个功能模块有都先后依赖关系,所以全部采用的是主进程和子进程同步模式执行。编译代码清单 2. 用 subprocess 编译代码build_cmd = 'build_command_for_your_code' #请在这里配置编译命令
build_proc = subprocess.Popen(build_cmd, stdin=None, stdout=None, stderr=None, shell=True)
build_proc.wait() #等待子进程结束
assert (0 == build_proc.returncode)在一些系统中我们编译代码采用的是脚本文件(如 shell 脚本),那么我们仍然可以如下命令来完成代码编译工作。清单 3. 用 subprocess 的 call 函数执行脚本文件
subprocess.call(["code_compile.sh"])运行测试脚本在编译完成代码之后,我们同样可以调用 subprocess.Popen 来创建子进程运行测试用例。如果测试人员的测试用例已经写成了测试例脚本,我们则可以用 subprocess.call()来执行测试例脚本文件,代码实现就不再赘述。有些系统会直接把详细日志输出到屏幕上,那么我们可以用重定向命令"2&&1"把屏幕输出写文件。清单 4. 用重定向命令把输出写文件
ut_cmd = 'Your_unit_test_command
2&&1 & %s' %self.debug_log #debug_log 定义在__init__函数中,用来存储详细日志测试结果存储和发布 ---- XML 解析我们的项目采用敏捷开发,为了更好的反应敏捷开发周期,我们希望存储日志的目录名不但能够指明的具体日期,同时也能反映敏捷(迭代)开发阶段,这样相关人员在查看相应目录中的日志时,能够清楚的明白日志实在在哪个迭代周期的哪一天产生的。本文使用文件 summary 作为运行测试用例后生成的汇总日志,用文件 log.txt 用来存储详细日志。如下图所示,在共享目录 SharedFiles 中存储了一些列迭代周期中的日志。清单 5. 共享目录结构
SharedFiles
├── Sprint10-00
├── log.txt
└── summary
├── Sprint10-15
├── log.txt
└── summary
├── Sprint10-35为了能够让目录名反映敏捷开发周期,我们需要自己定义一个配置文件(txt 或 xml 均可)。由于 Python 已经很好的支持了 XML 解析,并且 XML 文件作为配置也是当前的流行趋势。本文就以 XML 解析为例进行说明。本文使用的 XML 文件名是 Sprint.xml,清单 6 是该 xml 的概要内容清单 6. Sprint.xml 文件结构&sprint-schedule&
&min-sprint&10&/min-sprint&
&max-sprint&20&/max-sprint&
&sprint10&&/sprint10&
&sprint11&&/sprint11&
&sprint19&&/sprint19&
&sprint20&&/sprint20&
&/sprint-schedule&关于 xml 解析 Python 提供了多种方法。本文采用 minidom 对 xml 文件进行解析,清单 7 是相关处理代码。清单 7. xml 解析代码cur_date = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) # 首先获取当前系统日期
xmldoc = minidom.parse(xml_file)
min_num_node = xmldoc.getElementsByTagName('min-sprint')[0]
min_num = int(min_num_node.firstChild.data) #解析出迭代开发周期的起始周期
max_num_node = xmldoc.getElementsByTagName('max-sprint')[0]
max_num = int(max_num_node.firstChild.data) #解析出迭代开发周期的终止周期
cur_num = min_num
#遍历所有迭代周期,取出当前迭代周期的开始时间和当前的系统时间对比,从而确定当前位于哪一个迭代周期。
while cur_num &= max_num :
node_name = 'sprint' + str(cur_num)
cur_node = xmldoc.getElementsByTagName(node_name)[0]
sprint_date = cur_node.firstChild.data
if sprint_date & cur_date[0:7]:
cur_num = cur_num + 1
break这样 cur_num 就指向了当前的迭代开发周期。然后,我们就可以根据当前日期和开发阶段创建对应的日志目录名了,最后把运行结果存储到该目录下,参见清单 8 实现。清单 8. 日志存储代码log_dir = self.share_dir + '/Sprint' + str(cur_num) + '-' + cur_date #share_dir 为共享目录,定义在初始化函数中
os.mkdir(log_dir)
os.system('mv %s %s' %(self.debug_fullname, log_dir)) #debug_fullname,详细日志文件名(含目录),定义在初始化函数中
os.system('mv %s %s' %(self.sum_fullname, log_dir)) #sum_fullname,汇总日志的全路径文件名,定义在初始化函数中关于测试结果的发布,本文并没有把测试结果以自动化的形式发送邮件,而是手动在每个开发周期结束时,群发邮件给相关人员。或者在验证失败后,通知相关的开发人员,这是由于作者所在团队项目代码提交频率不是很高。在更大型的项目中,往往需要增加自动发送邮件的功能,相关实现本文不再赘述。也谈界面设计 ---- getopt 的使用在日常的测试过程中,我们并不是每次都要迁出代码,编译代码,运行测试用例和收集测试结果。这样就需要我们能够有选择的运行部分程序功能,例如只运行测试用例和收集结果。这里我们提供了 4 个运行选泽:
选项 1:迁出代码--&编译版本--&运行测试用例--&收集测试结果
选项 2:更新代码--&编译版本--&运行测试用例--&收集测试结果
选项 3:编译版本--&运行测试用例--&收集测试结果
选项 4:运行测试用例--&收集测试结果当然我们还需要提供帮助信息,以方便不熟悉该脚本实现的人员使用。python 也提供了 getopt 模块让我们轻松实现上述功能。实现代码参见清单 9清单 9. 命令行写解析代码try:
opts, args = getopt.getopt(sys.argv[1:], 'bchu', ['build', 'checkout', 'help', 'update'])
except getopt.error, msg:
self.usage()
sys.exit(2)
build_flag = 0 #构建选项
for o, a in opts:
if o in ('-h', '--help'):
self.usage()
sys.exit()
elif o in ('-c', '--checkout'):
print "执行操作:迁出代码--&编译版本--&运行测试用例--&收集测试结果"
build_flag = 1
elif o in ('-u', '--update'):
print "执行操作:更新代码--&编译版本--&运行测试用例--&收集测试结果"
build_flag = 2
elif o in ('-b', '--build'):
print "执行操作:编译版本--&运行测试用例--&收集测试结果"
build_flag = 3
self.usage()
sys.exit()
if (0 == build_flag) :
if 2 &= len(sys.argv):
self.usage()
sys.exit()
raw_input('\n 按 Enter 键继续。。。(Ctrl+C 退出)\t')
if (1 == build_flag) : #迁出代码,并编译代码
self.checkout_code()
self.build_code()
elif (2 == build_flag) : #更新代码,并编译代码
self.update_code()
self.build_code()
elif (3 == build_flag) : #编译代码
self.build_code()
#运行测试用例并收集运行结果
self.set_python()
self.run_testsuite()
self.store_logs()如果我们在运行的过程中想中断(如利用 Ctrl+C)一键回归测试进程的执行时,有时我们会发现虽然主进程已经被终止,但子进程仍在运行。我们能否在中断主进程的同时也中断子进程呢?答案当然是肯定的,我们可以用信号处理函数捕获信号(如捕获 Ctrl+C 产生的中断信号),然后在显式终止对应的子进程。这里就需要我们在创建子进程的时候,先保存子进程 ID,当然把子进程 ID 保存到初始化函数中,是个不错的选择,清单 10 是相关实现。清单 10. 信号处理代码
# 终止子进程的运行
def handler(self, signum, frame):
if (-1 != self.subproc_id) : #subproc_id 定义在初始化函数中,用来存储当前子进程的 ID
os.killpg(self.subproc_id, signal.SIGINT)
sys.exit(-1)这里我们需要在初始化函数中注册要捕获的信号,并且创建成员变量用来保存子进程的 ID,详细实现请参见清单 11。基于对象的设计 ---- class 的使用最后终于轮到 class 登场了,提到 class 我们就不能不谈构造函数(初始化函数)和析构函数。之前我们多次提到初始化函数,初始化函数允许我们定义一些变量,这些变量在整个类对象的生存周期内均有效。由于本文没有向系统申请资源,就再不定义析构函数了。清单 11. 初始化处理代码
def __init__(self):
signal.signal(signal.SIGINT, self.handler) #注册需要捕获的信号量
self.myafs_dir = os.getenv('myafs')
self.subproc_id = -1 #子进程 ID,用来在终止主进程时也同时终止子进程
self.debug_log = 'log.txt' #存储详细运行日志的文件名
self.debug_fullname = os.getcwd() + os.sep + self.debug_log #全路径文件名(假设产生在该目录下)
self.sum_log = 'summary' #存储汇总日志的文件名
self.sum_fullname = os.getcwd() + os.sep + self.sum_log #全路径文件名(假设产生在当前目录下)
self.share_dir = self.utafs_dir + '/SharedFiles' #共享目录文件名通常我们不需要太关注设计风格,只要 Python 脚本能完成我们的测试要求即可。对于较小的脚本,几条 Python 指令顺序执行即可。为了模块功能复用和可读性,我们通常会把功能模块封装成函数。本文将实现的所有函数都封装到一个类中,使得该脚本更加一体化。清单 12. class 框架结构代码
class COneClickRegTest:
#设定一些经常使用的变量,如当前工作目录,日志名称、存储路径等
def __init__(self):
#设定 python 环境变量,实现参见代码文件
def set_python(self):
#更新代码,实现参见代码文件
def update_code(self):
#迁出代码,实现参见第 2 章代码
def checkout_code(self):
#编译版本,实现参见清单 1 代码
def build_code(self):
#运行测试集,实现参见代码文件
def run_testsuite(self):
#存储运行结果,实现参见清单 7 和清单 8 代码
def store_logs(self):
#信号处理,实现参见清单 10 代码
def handler(self, signum, frame):
#脚本使用说明,实现参见代码文件
def usage(self):
#命令行解析以及执行对应的功能,实现参见清单 9 代码
def main(self):结束语Python 语言是一个易学易用的脚本语言,笔者没有多久的 Python 开发经验,不过其他语言有的功能在 Python 中大都可以找到对应的实现,这也是笔者能够在很短的时间内完成该测试脚本的原因。因此,笔者把该语言和使用该语言完成一键回归测试介绍给大家,希望对大家有所帮助。正像笔者说的其他语言有的功能在 Python 中大都可以找到对应的实现,同样,如果大家对某一种特定的脚本语言或者开发语言特别熟悉,也完全可以采用所熟悉的语言来完成一键回归测试的工作。
下载描述名字大小示例代码3KB
参考资料 是关于 Python 所有内容的起始点,其中包括正式为"A Byte of Python"的简体中文译本,无论您刚接触电脑还是一个有经验的程序员,本教程都有助您学习使用 Python 语言。是一本全面介绍有关 Python 语言和 Python 程序开发专业知识的参考手册。在可以下载到最新的 PyUnit 软件包,以及详细的用户手册。在 寻找为 Linux 开发人员(包括 )准备的更多参考资料,查阅我们。在 developerWorks 上查阅所有 和 。加入 。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
developerWorks: 登录
标有星(*)号的字段是必填字段。
保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件。
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
选择您的昵称
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。
您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
标有星(*)号的字段是必填字段。
(昵称长度在 3 至 31 个字符之间)
单击提交则表示您同意developerWorks 的条款和条件。 .
所有提交的信息确保安全。
IBM PureSystems(TM) 系列解决方案是一个专家集成系统
通过学习路线图系统掌握软件开发技能
软件下载、试用版及云计算
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=LinuxArticleID=972234ArticleTitle=使用 Python 在 Linux 上实现一键回归测试publish-date=手机做server端C/S架构实现(2)
阅读排行 写网络爬虫学python(一) (1664) 在win7下使用MFC中MSComm控件的问题 (1610) python学习——截图工具编写 (1607) 百度2014校园招聘笔试题——连连看判断消
写网络爬虫学python(一)(1664)
在win7下使用MFC中MSComm控件的问题(1610)
python学习——截图工具编写(1607)
百度2014校园招聘笔试题——连连看判断消除算法(1399)
Python邮箱客户端编写之接收邮件操作(1392)
百度质量部测试开发实习生面试总结(技术二面)(969)
百度质量部测试开发实习生面试总结(技术一面)(942)
qq隐身对其可见有效识别方法(已无效)(845)
Python邮件客户端编写之wxPython树形控件(797)
写网络爬虫学python(二)(766)
在win7下使用MFC中MSComm控件的问题(5)
百度质量部测试开发实习生面试总结(技术二面)(2)
百度质量部测试开发实习生面试总结(技术一面)(2)
leetcode——Two Sum 两数之和(AC)(2)
Hulu面试题解答——N位数去除K个数字(解法错误sorry)(2)
python学习——截图工具编写(1)
写网络爬虫学python(一)(1)
二进制查找树转换为双向链表(0)
leetcode——Reverse Integer
反转整数数字(AC)(0)
leetcode——String to Integer (atoi)
字符串转换为整型数(AC)(0)
michel_yg_chen:
简明扼要,赞。补充一下。 pythoncom是要单独安装的。它被保护在pywin32包中。
axiaochong:
我又在网上查了下,主流做法都是先排序,然后再去查找,这样时间复杂度算是低的了,不知道不排序的高效做法...
遍历一次就可以找出,不用排序的?
axiaochong:
呀,的确是错误的算法。谢谢谢谢
n=324,k=1呢能返回正确结果么参考我的博客
axiaochong:
@wt1989217:sorry,现在才看到。去年被拒了~最近面了百度的运维,目前等通知中。。
wt1989217:
您好!我最近也在参加百度测试的实习生招聘,请问您最后应聘成功了百度的测试实习生吗?
是我写的一个网络爬虫~可以大规模提取网页关键字,现在免费试用,欢迎试用,...
axiaochong:
是之前在忙,现在还算是比较闲了,所以希望找实习多锻炼锻炼,嘿嘿
liuxiaobin_bluegiant:
什么学校的,忙实验啊
(责任编辑:赵红霞)
------分隔线----------------------------
到底是使用开发工具构建一个良好的maven结构的Webapp,还是直接...
评委用智能手机Android系统的专用打分端,为每一个现场比赛选手...
cocos2dx 监听android设备按键响应的方法很简单,直接上代码注册一...
Windows Server 2008 与普通 windows 版本最大的不同,在于其文件/目录...
初学nodejs这个系列并不是入门系列。其实我自己还没入门。。入...
YCFHQ-9DWCY-DKV88-T2TMH-G7BHPvs2010中文版破解下载 【摘要】文件名 cn...3674人阅读
项目的 自动化测试中已经使用了基于Python 脚本的框架,自动化过程中最关键的问题就是如何实现桩模块。运用 Python 强大的功能,实现任何桩模块都是可能的,但是是否必须完全使用 Python 实现模块逻辑,成本是一个决定性因素。在桩模块逻辑简单的情况下,使用 Python 模拟模块逻辑不但使自动化测试的结构清晰,也具有更好的灵活性,但是如果桩模块逻辑复杂,实现起来可能要耗费很大的成本,也容易由于桩模块逻辑与实际不符导致测试结果不可信。在这种情况下,如果能够借用 RD 开发的某些代码段 / 库,将会对测试自动化带来很多效益。&
另外,在Python 中调用 C/C++ 代码的方法也可能应用于 C/C++ 库的测试中,这种测试方法的可行性还有待研究。&
以下总结出几种在Python 中调用 C/C++ 代码的方法
使用ctypes 模块调用 C 动态库
从Python2.5 开始, Python 开始提供 ctypes 模块来提供对 C 语言编译的动态库文件的调用。注意,& 这里特指C 的动态库 &,用C++ 编译的动态库 ctypes 虽然能够加载,但调用时的函数名已经由于 C++ 的重载特性被加以修改,难以调用。&使用 ctypes 调用 C 动态库的好处在于不用进行额外的开发,可以直接使用编译好的动态库。 ctypes 提供了完整的 C 类型封装,也支持自定义类型,大大减少在调用过程中的工作量。 ctypes 的使用很简单,只需熟悉 python 封装与 C 中的对应关系即可。以下用一个简单的例子来说明:
from &ctypes& import &* #导入ctypes模块
libc&=&cdll.LoadLibrary("libc.so.6") #加载libc动态库
str &=&c_char_p(' Hello&World! ') #使用char&*在ctypes中的对应封装c_char_p,相当于char*&str=&Hello&World!&
libc.printf(&yell:&%s/n&,& str ) #调用printf函数
ctypes的功能当然远不止这些,有兴趣的同学可以参考这里
使用Python 的扩展( Extending )机制
ctypes很方便地可以调用 C 的动态库,但是对 C++ 编译的动态库,调用起来很困难。这种情况利用 Python 的 Extending 机制就可以解决。 Python 提供了一套完整的框架来使用 C/C++ 编写扩展库,可以很灵活的开发 C++ 扩展模块。这种方法的缺点是工作量比较大,需要为每一个方法编写接口,这里不做详细介绍,可以参考: http://docs.python.org/extending/extending.html#writing-extensions-in-c
那么有什么办法可以高效的调用C++ 动态库呢,答案是 SWIG 。
使用SWIG 生成扩展模块
上面提到了Python 的扩展机制,缺点是工作量比较大,这里介绍一个工具 SWIG 。 SWIG 是一种简化脚本语言与 C/C++ 接口的开发工具,通过包装和编译 C 语言程序来达到与脚本语言通讯目的的工具。它正是基于 Python 的扩展机制,自动生成接口文件,再编译成可以被 Python 调用的动态库扩展模块。
使用SWIG 生成扩展模块分为以下几步:
将需要调用的代码编译成目标文件(.o)
用SWIG 读取编写描述文件 (.i) ,生成接口文件 (.cxx) ;
将接口文件编译为目标文件(.o)
将接口文件的目标文件和原代码段的目标文件一起编译成动态库
以下举例说明:
假设有如下文件
swig_ex.cpp 需要转换成扩展库的原始代码,包含一个int&fact(int) 函数
swig_ex.h& 原始代码的头文件
swig_ex.i SWIG描述文件
swig_ex.i是一个描述文件,有 SWIG 自己的语法,比较简单,内容如下:
%module&swig_ex&
&&&&&&&&#define&SWIG_FILE_WITH_INIT
&&&&&&&&#include&"swig_ex.h"
int&fact(int&n);
再写一个Makefile 来把这些文件编译成动态库 :
all:&swig_ex.o&swig_ex_wrap.o&_swig_ex.so
swig_ex.o:&swig_ex.cpp&swig_ex.h&#编译源文件
&&&&&&&&g++&-fPIC&-c&swig_ex.cpp
swig_ex_wrap.o:&swig_ex.i&swig_ex.o&#根据 SWIG 描述文件 (.i) 生成接口文件 (.cxx) ,再编译之
&&&&&&&&swig&-c++&-python&swig_ex.i
&&&&&&&&g++&-O2&-fPIC&-c&swig_ex_wrap.cxx&-I/home/work/linyi/autoframe/tool/python/include/python2.6/
_swig_ex.so:&swig_ex_wrap.o&#将目标文件打包成动态库
&&&&&&&&g++&-shared&swig_ex.o&swig_ex_wrap.o&-o&_swig_ex.so
.PHONY:&clean
&&&&&&&&rm&-rf&swig_ex_wrap.*&swig_ex.py&_swig_ex.so
编译好以后会有一个so 和 py 文件,写一个 setup.py 把他们安装到 python 目录就可以和其他模块一样被 python 调用了:
Import&swig_ex
swig_ex.fact(10)
参考文档地址:http://www.swig.org/Doc1.3/SWIGDocumentation.html
原始但有效的方法
除了上面这些方法,在Python 中借用 C/C++ 代码最原始有效的方法就是将代码编译成可执行程序,从 Python 里用 Popen 方法来调用获取输出。这种方法简单有效,缺点是不够灵活,有比较大的局限性,不过在很多情况下也已经足够了。
pipe&=&os.popen('./tool&&a&%s&&b&%s'&%&(&hello&,&&world&))
re&=&pipe.read()
以上这些方法基本上已经能满足Python 调用 C/C++ 的需求了,此外还有一些方法,例如使用 Boost.Python ,使用 Pyrex ,这些方法都能提供 Python 与 C/C++ 的交互。
在Python 中引用 C/C++ 模块的方法较多,根据需要从中选择恰当的方法可以减少很多工作量。
在Python 中引用 C/C++ 模块弥补了 Python 脚本测试框架的很多不足,在提高代码复用率的同时,模块的性能也大大提高。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:213309次
积分:2628
积分:2628
排名:第5287名
原创:23篇
转载:65篇
评论:117条
(6)(15)(9)(12)(12)(8)(3)(2)(5)(5)(5)(6)}

我要回帖

更多关于 python 自动化 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信