从代码库迁出代码 —- pexpect 的使用

测试人员从代码库(例如 CVS )迁出代码的过程中,需要手动输入访问密码,而 Python 提供了 Pexpect 模块则能够将手动输入密码这一过程自动化。当然 Pexpect 也可以用来和 ssh、ftp、passwd、telnet 等命令行进行自动化交互。这里我们以 CVS 为例展示如何利用 Pexpect 从代码库迁出代码。

清单 1. 用 pexpect 迁出代码库代码

 
 
1
2
3
4
5
6
7
8
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()
except:
        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 编译代码

 
 
1
2
3
4
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 函数执行脚本文件

 
 
1
subprocess.call(["code_compile.sh"])

运行测试脚本

在编译完成代码之后,我们同样可以调用 subprocess.Popen 来创建子进程运行测试用例。如果测试人员的测试用例已经写成了测试例脚本,我们则可以用 subprocess.call()来执行测试例脚本文件,代码实现就不再赘述。有些系统会直接把详细日志输出到屏幕上,那么我们可以用重定向命令”2>&1″把屏幕输出写文件。

清单 4. 用重定向命令把输出写文件

 
 
1
ut_cmd = 'Your_unit_test_command  2>&1 > %s' %self.debug_log #debug_log 定义在__init__函数中,用来存储详细日志

测试结果存储和发布 —- XML 解析

我们的项目采用敏捷开发,为了更好的反应敏捷开发周期,我们希望存储日志的目录名不但能够指明的具体日期,同时也能反映敏捷(迭代)开发阶段,这样相关人员在查看相应目录中的日志时,能够清楚的明白日志实在在哪个迭代周期的哪一天产生的。本文使用文件 summary 作为运行测试用例后生成的汇总日志,用文件 log.txt 用来存储详细日志。如下图所示,在共享目录 SharedFiles 中存储了一些列迭代周期中的日志。

清单 5. 共享目录结构

 
 
1
2
3
4
5
6
7
8
SharedFiles
├── Sprint10-20130823121500
│   ├── log.txt
│   └── summary
├── Sprint10-20130826152715
│   ├── log.txt
│   └── summary
├── Sprint10-20130828165235

为了能够让目录名反映敏捷开发周期,我们需要自己定义一个配置文件(txt 或 xml 均可)。由于 Python 已经很好的支持了 XML 解析,并且 XML 文件作为配置也是当前的流行趋势。本文就以 XML 解析为例进行说明。本文使用的 XML 文件名是 Sprint.xml,清单 6 是该 xml 的概要内容

清单 6. Sprint.xml 文件结构

 
 
1
2
3
4
5
6
7
8
9
<sprint-schedule>
<min-sprint>10</min-sprint>
<max-sprint>20</max-sprint>
<sprint10>20130814</sprint10>
     <sprint11>20130828</sprint11>
… …
<sprint19>20131218</sprint19>
<sprint20>20140101</sprint20>
    </sprint-schedule>

关于 xml 解析 Python 提供了多种方法。本文采用 minidom 对 xml 文件进行解析,清单 7 是相关处理代码。

清单 7. xml 解析代码

 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
else:
            break

这样 cur_num 就指向了当前的迭代开发周期。然后,我们就可以根据当前日期和开发阶段创建对应的日志目录名了,最后把运行结果存储到该目录下,参见清单 8 实现。

清单 8. 日志存储代码

 
 
1
2
3
4
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. 命令行写解析代码

 
 
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
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
            break
        elif o in ('-u', '--update'):
            print "执行操作:更新代码-->编译版本-->运行测试用例-->收集测试结果"
            build_flag = 2
            break
        elif o in ('-b', '--build'):
            print "执行操作:编译版本-->运行测试用例-->收集测试结果"
            build_flag = 3
            break
        else:
            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. 信号处理代码

 
 
1
2
3
4
5
# 终止子进程的运行
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. 初始化处理代码
 
 
1
2
3
4
5
6
7
8
9
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 框架结构代码

 
 
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
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 在 Linux 上实现一键回归测试的更多相关文章

  1. Nginx+uWSGI+Django+Python在Linux上的部署

    搞了一整天,终于以发现自己访问网络的端口是错误的结束了. 首先要安装Nginx,uWSGI,Django,Python,这些都可以再网上查到. 安装好后可以用 whereis 命令查看是否安装好了各种 ...

  2. python脚本linux上后台执行

    1.脚本后加& 加了&以后可以使脚本在后台运行,这样的话你就可以继续工作了.但是有一个问题就是你关闭终端连接后,脚本会停止运行 python3 run.py >/dev/null ...

  3. python在linux上的GUI无法弹出界面

    在进行python写GUI程序的时候,使用Tkinter,发现无法执行程序,报错如下: X connection to localhost:10.0 broken(explicit kill or s ...

  4. 利用python监测linux上的服务(简单实现服务宕掉自动发送邮件)

    python 这里用到了四个python 模块 : import time (时间模块) import re (正则模块) import socket (监测端口模块)import yagmail ( ...

  5. 使用Python获取Linux系统的各种信息

    哪个Python版本? 当我提及Python,所指的就是CPython 2(准确的是2.7).我会显式提醒那些相同的代码在CPython 3 (3.3)上是不工作的,以及提供一份解释不同之处的备选代码 ...

  6. 使用 Python 获取 Linux 系统信息

    探索platform模块 platform模块在标准库中,它有很多运行我们获得众多系统信息的函数.让我们运行Python解释器来探索它们中的一些函数,那就从platform.uname()函数开始吧: ...

  7. 【转】 使用 Python 获取 Linux 系统信息

    在本文中,我们将会探索使用Python编程语言工具来检索Linux系统各种信息.走你. 哪个Python版本? 当我提及Python,所指的就是CPython 2(准确的是2.7).我会显式提醒那些相 ...

  8. 在linux上安装python, jupyter, 虚拟环境(virtualenv)以及 虚拟环境管理之virtualenvwraper

    一, 安装python31.下载python3源码 wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz2.解压缩源码包,去 ...

  9. Python基于Python实现批量上传文件或目录到不同的Linux服务器

    基于Python实现批量上传文件或目录到不同的Linux服务器   by:授客 QQ:1033553122 实现功能 1 测试环境 1 使用方法 1 1. 编辑配置文件conf/rootpath_fo ...

随机推荐

  1. Java基础-SSM之mybatis一对一关联

    Java基础-SSM之mybatis一对一关联 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.准备测试环境(创建数据库表)  1>.创建husbands和wifes表并建 ...

  2. Java基础-进制转换

    Java基础-进制转换 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Java 程序中常用的进制 1>.十进制,由“0123456789” 这10个数字组成,逢十进一: ...

  3. vim编辑器基本操作介绍

    vim编辑器基本操作介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 可能很多小伙伴都听说过vi编辑器或是vim编辑器.它们是Unix和Linux世界最流行的编辑器之一,他们的特 ...

  4. Redis记录-Redis介绍

    Redis是一个开源,高级的键值存储和一个适用的解决方案,用于构建高性能,可扩展的Web应用程序. Redis有三个主要特点,使它优越于其它键值数据存储系统 - Redis将其数据库完全保存在内存中, ...

  5. Spring RedisTemplate操作-通道操作(10)

    @Autowired @Resource(name = "redisTemplate") private RedisTemplate<String, String> r ...

  6. Your Prediction Gets As Good As Your Data

    Your Prediction Gets As Good As Your Data May 5, 2015 by Kazem In the past, we have seen software en ...

  7. JMS学习(三)ActiveMQ Message Persistence

    1,JMS规范支持两种类型的消息传递:persistent and non-persistent.ActiveMQ在支持这两种类型的传递方式时,还支持消息的恢复.中间状态的消息(message are ...

  8. 使用data:uri上传图片

    上传图片的方式有两种,一种是使用传统的html控件的方式,设置form属性为multipart/form-data.这种方式兼容ie6,ie7.另一种方式是使用data:uri,将base64编码从浏 ...

  9. [机器学习&数据挖掘]朴素贝叶斯数学原理

    1.准备: (1)先验概率:根据以往经验和分析得到的概率,也就是通常的概率,在全概率公式中表现是“由因求果”的果 (2)后验概率:指在得到“结果”的信息后重新修正的概率,通常为条件概率(但条件概率不全 ...

  10. python的__get__、__set__、__delete__(1)

    内容:    描述符引导        摘要        定义和介绍        描述符协议        调用描述符        样例        Properties        函数和 ...