在软件活动中,我们需要对测试用例进行管理,如果只用excel,不用管理工具系统的管理,那么将出现以下一些问题: 案例文件分散,测试进度不透明; 需求变更导致的测试计划/测试用例变更,未能及时通知相关测试人员; 版本管理困难,很难追踪版本的变化; 缺陷管理与测试用例管理脱节,不便于缺陷密度的分析; 产品需求、测试计划、测试用例未能建立关联,不便于测试过程管理; 缺乏相关的测试分析报告数据,不便于暴露测试风险;
市面上的测试管理工具比较多,常见的有jira, Bugzilla, 禅道,TestLink等。
如果有经费,可以用jira, 这个可以完美的跟confluncen里的文档和jira里面的bug链接起来。
免费的可以用testlink.
但是免费的东西,还是有缺陷的:
case不能批量执行,一个个都要手动去标记,关键是点一次要刷新整个页面,等待时间特长。
系统经常打不开,或者响应时间过长,效率特低。
写case不是很方便,更新和维护也不方便。
用例导入只支持xml格式。
既然这么多问题,咋不更新或者用其它系统?我也想换哈,可是很多事情不是我们说了算。 既然不能换,那么我们就想办法曲线救国吧。
下文就是用python来解析xml, 用它来生成excel来执行,并将结果批量更新。这样就简单脱离了这个系统,从而提高效率。
我们先从case里面导出xml. 大概是这样的,我们要获得此次要执行的全部case的id.
Python 有非常多的工具来处理 XML。我们常常不知道用哪个更好。
xml.dom.*
模块 - 是 W3C DOM API 的实现。如果你有处理 DOM API 的需要,那么这个模块适合你。
xml.sax.*
模块 - 是 SAX API 的实现。这个模块牺牲了便捷性来换取速度和内存占用。SAX 是一个基于事件的 API,这就意味着它可以“在空中”(on the fly)处理庞大数量的的文档,不用完全加载进内存。
xml.parser.expat
- 是一个直接的,低级一点的基于 C 的 expat
的语法分析器(见注释2)。 expat
接口基于事件反馈,有点像 SAX 但又不太像,因为它的接口并不是完全规范于 expat
库的。
最后,我们来看看 xml.etree.ElementTree
(以下简称 ET)。它提供了轻量级的 Python 式的 API ,它由一个 C 实现来提供。相对于 DOM 来说,ET 快了很多,有很多令人愉悦的 API 可以使用。相对于 SAX 来说,ET 也有 ET.iterparse
提供了 “在空中” 的处理方式,没有必要加载整个文档到内存。
我的建议 是尽可能的使用 ET 来处理 XML ,学好ET就可以了,其它的可以不用学。
首先读入XML,有两种途径,从文件读入和从字符串读入。 从文件读入:
import xml.etree.ElementTree as ET
tree = ET.parse('sample.xml')
root = tree.getroot()
从字符串读入:
root = ET.fromstring(sample_as_string)
查看一个Element的Tag和Attribute的方法,Tag是一个字符串,而Attribute得到的是一个字典。
另外,还可以使用
Element.get(AttributeName)
来代替Element.attrib[AttributeName]来访问。
查看孩子节点: root.attrib返回的是一个空字典,如果看root的孩子,可以得到非空的attrib字典。
for child in root:
print child.tag, child.attrib
print root[0].tag
print root[0][0].tag
下标访问的方法虽然简单,但是在未知XML具体结构的时候并不适用,通过Tag名称访问的方法更具有普适性。这里用到Element类的几个函数,分别是
Element.iter()
Element.findall()
Element.find()
这两个函数使用的场景有所差异: Element.iter()用来寻找所有符合要求的Tag,注意,这里查找的范围是所有孩子和孩子的孩子 and so on。如果查看所有的某个tag,可以使用下面的代码:
for neighbor in root.iter('tag'):
print neighbor.text
Element.findall()只查找直接的孩子,返回所有符合要求的Tag的Element,而Element.find()只返回符合要求的第一个Element。
我们可以直接用Element.text来得到这个Element的值。
xml.etree.ElementTree可以通过支持的有限的XPath表达式来定位元素。 ElementTree支持的语法如下:
tag 查找所有具有指定名称tag的子元素。例如:country表示所有名为country的元素,country/rank表示所有名为country的元素下名为rank的元素。
* 查找所有元素。如:*/rank表示所有名为rank的孙子元素。
. 选择当前元素。在xpath表达式开头使用,表示相对路径。
// 选择当前元素下所有级别的所有子元素。xpath不能以“//”开头。
.. 选择父元素。如果视图达到起始元素的祖先,则返回None(或空列表)。起始元素为调用find(或findall)的元素。
[@attrib] 选择具有指定属性attrib的所有子元素。
[@attrib='value'] 选择指定属性attrib具有指定值value的元素,该值不能包含引号。
[tag] 选择所有具有名为tag的子元素的元素。
[.='text'] Python3.7+,选择元素(或其子元素)完整文本内容为指定的值text的元素。
[tag='text'] 选择元素(或其子元素)名为tag,完整文本内容为指定的值text的元素。
[position] 选择位于给定位置的所有元素,position可以是以1为起始的整数、表达式last()或相对于最后一个位置的位置(如:last()-1)
方括号表达式前面必须有标签名、星号或者其他方括号表达式。position前必须有一个标签名。
OK,我们来看官方的例子
<?xml version="1.0"?>
<data>
<country name="Liechtenstein">
<rank>1</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
<country name="Singapore">
<rank>4</rank>
<year>2011</year>
<gdppc>59900</gdppc>
<neighbor name="Malaysia" direction="N"/>
</country>
<country name="Panama">
<rank>68</rank>
<year>2011</year>
<gdppc>13600</gdppc>
<neighbor name="Costa Rica" direction="W"/>
<neighbor name="Colombia" direction="E"/>
</country>
</data>
XPath表达式用来在XML中定位Element,下面给一个例子来说明:
import xml.etree.ElementTree as ET
root = ET.fromstring(countrydata)
# Top-level elements
root.findall(".")
# All 'neighbor' grand-children of 'country' children of the top-level
# elements
root.findall("./country/neighbor")
# Nodes with name='Singapore' that have a 'year' child
root.findall(".//year/..[@name='Singapore']")
# 'year' nodes that are children of nodes with name='Singapore'
root.findall(".//*[@name='Singapore']/year")
# All 'neighbor' nodes that are the second child of their parent
root.findall(".//neighbor[2]")
好的,再来实践一下我们testlink导出的xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!-- TestLink - www.testlink.org - xml to allow results import -->
<results>
<testproject name="Labs" prefix="ET" />
<testplan name="OAp" />
<build name="2.0" />
<platform name="Android" />
<testcase external_id="ET-81835">
<result>X</result>
<notes>test link rocks </notes>
<tester>put login here</tester>
<!-- if not present now() will be used -->
<timestamp>YYYY-MM-DD HH:MM:SS</timestamp>
<bug_id>put your bug id here</bug_id>
</testcase>
<testcase external_id="ET-81836">
<result>X</result>
<notes>test link rocks </notes>
<tester>put login here</tester>
<!-- if not present now() will be used -->
<timestamp>YYYY-MM-DD HH:MM:SS</timestamp>
<bug_id>put your bug id here</bug_id>
<steps>
<step>
<step_number>1</step_number>
<result>p</result>
<notes>your step exec notes</notes>
</step>
</steps>
</testcase>
代码敲起来
import xml.etree.ElementTree as ET
tree = ET.parse('/Users/anderson/Documents/OneApp_Android.xml')
root = tree.getroot() #获得root节点, 根节点应该是result
print(root)
print(root[5].attrib['external_id']) #下标访问第六个子节点,是testcase, 通过attrib可以获取caseid
# print(root[5][0].text) #下标访问第六个子节点下的第一个孙子节点,, 通过attrib可以获取text,应该是X
# print(root[6].attrib['external_id'])
# print(root[6][0].text)
for t in root.iter():
print(t.tag,t.attrib)
# for t in root:
# print(t.attrib)
# print(t.tag)
# for t in root:
# print(t.get("external_id"))
# for t in root.iter("testcase"):
# print(t.attrib)
# for t in root.iter("testcase"):
# print(t.attrib['external_id'])
# for t in root.findall("testcase"):
# print(t.attrib['external_id'])
# for t in root.find("testcase"):
# print(t.attrib)
# for elem in tree.iterfind('testcase'):
# print(elem.attrib)
# for elem in tree.iterfind('testcase/result'):
# print(elem.text)
count = 0
for elem in tree.iter(tag='testcase'):
if elem[0].text == 'X':
count += 1
print(count)
敲完这些,基本就掌握了。是不是很简单?
前面已经介绍了如何获取一个Element的对象,以及查看它的Tag、Attribute、值和它的孩子。下面介绍如何修改一个Element并对XML文件进行保存
修改Element可以直接访问Element.text。 修改Element的Attribute,也可以用来新增Attribute:
Element.set('AttributeName','AttributeValue')
新增孩子节点:
Element.append(childElement)
删除孩子节点:
Element.remove(childElement)
保存XML 我们从文件解析的时候,我们用了一个ElementTree的对象tree,在完成修改之后,还用tree来保存XML文件。
tree.write('output.xml')
ElementTree提供了两个静态函数(直接用类名访问,这里我们用的是ET)可以很方便的构建一个XML,如:
root = ET.Element('data')
country = ET.SubElement(root,'country', {'name':'Liechtenstein'})
rank = ET.SubElement(country,'rank')
rank.text = '1'
year = ET.SubElement(country,'year')
year.text = '2008'
ET.dump(root)
就可以得到
<data><country name="Liechtenstein"><rank>1</rank><year>2008</year></country></data>
掌握了这些知识后,我们有两种方法来操作了,第一种方法是手工操作,导出导入xml. 第二种方法是调用testlink API。 我现在的方法是结合这两种, 先将xml导出来,获取到caseid, 然后调用API,获取到case的具体信息,导出到EXCEL, 然后在EXCEL中执行,最后将excel里面的信息,更新到xml中,导入到testlink。 先需要安装API
pip install TestLink-API-Python-client
将导出的xml的case id搜集起来:
import xml.etree.ElementTree as ET
import numpy as np
import pandas as pd
case_list = []
tree = ET.parse("/Users/Anderson/Downloads/OneApp_Android.xml")
root = tree.getroot()
for t in root.findall("testcase"):
# print(t.attrib["external_id"])
case_list.append(t.attrib["external_id"])
有了这个列表,得到case信息,得到excel, 先要获得一个key, 如图:
import testlink
def get_case_detail(id):
# 连接test link
url = "host/testlink/lib/api/xmlrpc/v1/xmlrpc.php"
key = "xxx"
tlc = testlink.TestlinkAPIClient(url, key)
test_case = tlc.getTestCase(None, testcaseexternalid=id)
return id, test_case[0]["name"],test_case[0]["summary"],test_case[0]["steps"]
将url的host换成自己的,key替换掉,就可以拿到case的信息了。 api的具体参数请看官网。 然后我们就可以得到一个excel了,利用pandas,简单方便,比直接操作excel简单很多。
get_case = (get_case_detail(x) for x in case_list)
case_pd = pd.DataFrame(get_case, columns=["id","name","summary","steps"])
case_pd["result"] =""
with pd.ExcelWriter('output.xlsx') as writer:
case_pd.to_excel(writer, sheet_name='Sheet_name_1')
然后就可以在Excel里面执行。 执行完成后,就可以将excel里的结果,更新到xml文件中。
def put_result():
df = pd.read_excel('/Users/anderson/Downloads/output.xlsx')
data = df[["id","result"]]
print(data)
for ix, row in data.iterrows() : t in root.iterfind("testcase/external_id"):
if row['id'])== t.attrib:
t.set('result', row['result'])
t.set('tester', 'xxx')
t.set('timestamp', time.now())
tree.save("result.xml")
好了,我们可以手动将xml文件导入到testlink,就完成了我们这次实践之旅。
在case比较多的情况下,这种曲线救国的策略,效率会提升很多。
更多精彩,请关注微信公众号:python爱好部落