案例故事: Testlink是我們常用的用例管理工具,很多公司其實都在用, Testlink 支持制定測試計劃,支持多人線上同時管理維護/執行測試用例,自動生成測試報告等。 我個人也非常非常不推薦Excel線下管理測試用例, 但是官方提供的Testlink版本,是不支持Excel導入的,只能進行Xm ...
案例故事: Testlink是我們常用的用例管理工具,很多公司其實都在用,
Testlink 支持制定測試計劃,支持多人線上同時管理維護/執行測試用例,自動生成測試報告等。
我個人也非常非常不推薦Excel線下管理測試用例,
但是官方提供的Testlink版本,是不支持Excel導入的,只能進行Xml導入。
沒這個Excel導入功能,確實有點可惜了,
但是秉著Python是萬能的,只是將Excel轉Xml本也不是什麼難事。
準備階段
- 操作Excel的模塊,一直首選openpyxl, 直接pip install openpyxl即可
- 操作Xml的模塊,我建議首選ElementTree, 但是需要修改其源代碼,原因如下3,4步。
- 根據Testlink官方提供的文檔,如果單獨一條用例,其xml如下,看著暈乎,別慫,就是乾!
<?xml version='1.0' encoding='utf-8'?>
<testcases>
<testcase internalid="1" name="VTS測試——arm64-v8a VtsFastbootVerification">
<externalid />
<version>
<![CDATA[1]]>
</version>
<summary>
<![CDATA[VTS測試——arm64-v8a VtsFastbootVerification]]>
</summary>
<preconditions>
<![CDATA[Android設備已經通過USB連接上Ubuntu系統]]>
</preconditions>
<execution_type>
<![CDATA[2]]>
</execution_type>
<importance>
<![CDATA[1]]>
</importance>
<status>
<![CDATA[7]]>
</status>
<steps>
<step>
<step_number>
<![CDATA[1]]>
</step_number>
<actions>
<![CDATA[Step1: 運行vts-tradefed進入vts控制台]]>
</actions>
<expectedresults>
<![CDATA[Result1: 成功進入vts控制台]]>
</expectedresults>
<execution_type>
<![CDATA[2]]>
</execution_type>
</step>
<step>
<step_number>
<![CDATA[2]]>
</step_number>
<actions>
<![CDATA[Step2: 執行run vts -m VtsFastbootVerification]]>
</actions>
<expectedresults>
<![CDATA[Result2: 運行結果Pass,vts測試通過]]>
</expectedresults>
<execution_type>
<![CDATA[2]]>
</execution_type>
</step>
</steps>
<keywords>
<keyword name="Android 9">
<notes>
<![CDATA[]]>
</notes>
</keyword>
</keywords>
</testcase>
</testcases>
4.由以上Xml可以看到,有一個很奇怪的![CDATA[**]],
被這個標記所包含的內容將表示為純文本,不應被XML解析器進行解析,
但是這種這個標記,Python自帶的ElementTree無法正常生成,
於是我多番研究後,修改了ElementTree的源代碼,增加了901,902行,
可以到本素材案例處下載就是etree這個文件夾,
本案例import的etree可以理解成第三方的模塊,非官方的ElementTree模塊。
5. 我們需要設定測試用例Excel模板如下:
6. 我們本次需要考慮實現批量轉換多個Excel, 輸入是Excel_Input,
運行excel2xml.py後輸出是XML_Output, 其文件目錄如下:
Python面向對象類形式
由於本案例涉及的代碼有些許難度,且相對較長,
直接以面向對象類的形式來進行建模及程式設計。
建模:先設想有這麼一個空白的世界,這個世界需要哪些類型的事物(名詞)。
我們需要兩個類,一個是ExcelReader類用於讀取Excel獲取Cases數據,
一個是XMLWriter類,用於將以上獲取的Excel數據寫入Xml文件里去。
# coding=utf-8
import os
import re
import shutil
from openpyxl import load_workbook
from etree.ElementTree import ElementTree, Element, SubElement
SUMMARY_COL = 1 # 用例標題
PRECONDITION_COL = 2 # 預置條件
EXECUTETYPE_COL = 3 # 用例的執行方式,手動和自動兩種
IMPORTANCE_COL = 4 # 用例的優先順序
STEPS_COL = 5 # 用例的執行步驟
EXCEPTED_RESULT_COL = 6 # 用例的預期結果
KEYWORD_COL = 7 # 用例所屬的項目
class ExcelReader(object):
"""Read Excel file and get all cases contents"""
def __init__(self, excel_file):
self.excel_file = excel_file
def __parse_steps(self, steps_str):
"""需要解析切割Step1:XXXXStep2:XXXXStep3:XXXXX並獲取各執行步驟Step的具體文本"""
steps_str = steps_str + " "
new_steps_list = []
number_list = []
steps_count = 1
for i in range(1, 20):
if ("Step%s" % i in steps_str):
steps_count = i
else:
break
for x in range(1, steps_count + 1):
number_list.append(int(steps_str.find("Step%s" % x)))
number_list.append(-1)
for j in range(0, len(number_list) - 1):
new_steps_list.append(steps_str[number_list[j]:number_list[j + 1]])
return new_steps_list
def __parse_results(self, result_str):
"""需要解析切割Result1:XXXXResult2:XXXXResult3:XXXXX並獲取各預期結果Result的具體文本"""
result_str = result_str + " "
new_result_list = []
number_list = []
steps_count = 1
for i in range(1, 20):
if ("Result%s" % i in result_str):
steps_count = i
else:
break
for x in range(1, steps_count + 1):
number_list.append(int(result_str.find("Result%s" % x)))
number_list.append(-1)
for j in range(0, len(number_list) - 1):
new_result_list.append(result_str[number_list[j]:number_list[j + 1]])
return new_result_list
def get_all_cases(self):
"""讀取到Excel所有的測試用例並寫入一個列表存儲起來 """
all_case_list = []
excel = load_workbook(self.excel_file)
_, excel_name = os.path.split(self.excel_file)
sheet = excel.active
max_rows = sheet.max_row
for row_num in range(2, max_rows):
print("正在處理%s文件的第%s行" % (excel_name, row_num))
casedict = {}
summary = sheet.cell(row=row_num, column=SUMMARY_COL).value
# print(summary)
if summary:
precondition = sheet.cell(row=row_num, column=PRECONDITION_COL).value
execution_type = sheet.cell(row=row_num, column=EXECUTETYPE_COL).value
importance = sheet.cell(row=row_num, column=IMPORTANCE_COL).value
steps = sheet.cell(row=row_num, column=STEPS_COL).value
excepted_results = sheet.cell(row=row_num, column=EXCEPTED_RESULT_COL).value
keyword = sheet.cell(row=row_num, column=KEYWORD_COL).value
if keyword == None:
keyword = ""
casedict["internalid"] = "1"
casedict["summary"] = summary
casedict["status"] = "7"
casedict["preconditions"] = precondition
casedict["keyword"] = keyword
if (importance == "" or importance == None):
print(u"格式錯誤, 第%s行, \"優先順序\"列, 不能為空!" % row_num)
return None
else:
importance = importance.strip()
importance = importance.capitalize() # 首字母大寫
if (importance == "Medium" or importance == "M"):
casedict["importance"] = "2"
elif (importance == "High" or importance == "H"):
casedict["importance"] = "3"
elif (importance == "Low" or importance == "L"):
casedict["importance"] = "1"
else:
print(u"優先順序錯誤, 第%s行, \"優先順序\"列, 必須是High, Medium, Low 或者H, M, L!" % row_num)
return None
if (execution_type != '手動' and execution_type != "自動" and execution_type != "手工"):
print(u"格式錯誤, 第%s行, \"執行方式\"列, 必須為\"手動\"或\"自動\"!" % row_num)
return None
else:
if (execution_type == u"手動"):
casedict["execution_type"] = "1"
else:
casedict["execution_type"] = "2"
if ("Step1" not in steps):
print(u"格式錯誤, 第%s行, \"測試步驟\"列,必須以Step1:, Step2:, Step3:...等進行格式化!" % row_num)
return None
else:
steps_list = self.__parse_steps(steps)
for i in range(1, len(steps_list) + 1):
casedict["step" + str(i)] = steps_list[i - 1]
if not (re.match(r".*Result.*", excepted_results)):
print(u"格式錯誤, 第%s行, \"期望結果\"列,必須以Result1:, Result2:, Result3:...等進行格式化!" % row_num)
return None
else:
result_list = self.__parse_results(excepted_results)
for i in range(1, len(result_list) + 1):
casedict["result" + str(i)] = result_list[i - 1]
all_case_list.append(casedict)
else:
break
# print(allcase_list)
return all_case_list
class XmlWriter():
'''Write to XML'''
def __init__(self, all_cases_list, save_path):
self.all_cases_list = all_cases_list
self.save_path = save_path
def write_xml(self):
xml_file = ElementTree()
testcases_node = Element("testcases")
xml_file._setroot(testcases_node)
for eachcase in self.all_cases_list:
testcase_node = Element("testcase", {"internalid": eachcase["internalid"], "name": eachcase["summary"]})
try:
SubElement(testcase_node, "externalid").append(CDATA(eachcase["externalid"]))
except:
pass
try:
SubElement(testcase_node, "version").append(CDATA(eachcase["version"]))
except:
pass
SubElement(testcase_node, "summary").append(CDATA(eachcase["summary"]))
SubElement(testcase_node, "preconditions").append(CDATA(eachcase["preconditions"]))
SubElement(testcase_node, "execution_type").append(CDATA(eachcase["execution_type"]))
SubElement(testcase_node, "importance").append(CDATA(eachcase["importance"]))
SubElement(testcase_node, "status").append(CDATA(eachcase["status"]))
stepsnode = SubElement(testcase_node, "steps")
for i in range(1, 20):
try:
step = eachcase["step" + str(i)]
result = eachcase["result" + str(i)]
stepnode = SubElement(stepsnode, "step")
SubElement(stepnode, "step_number").append(CDATA(str(i)))
SubElement(stepnode, "actions").append(CDATA(self.__remove_step_num(step)))
SubElement(stepnode, "expectedresults").append(CDATA(self.__remove_result_num(result)))
SubElement(stepnode, "execution_type").append(CDATA(eachcase["execution_type"]))
except:
break
try:
keywords_node = SubElement(testcase_node, "keywords")
keyword_node = SubElement(keywords_node, "keyword", {"name": eachcase["keyword"]})
SubElement(keyword_node, "notes").append(CDATA(""))
except:
pass
testcases_node.append(testcase_node)
self.__indent(testcases_node)
xml_file.write(self.save_path, encoding="utf-8", xml_declaration=True)
return xml_file
def __remove_step_num(self, text=None):
"""# 剔除Step :字元"""
step_text = re.sub(r"Step\s+:", "", text)
return step_text
def __remove_result_num(self, text=None):
"""# 剔除Result :字元"""
result_text = re.sub(r"Result\s+:", "", text)
return result_text
def __indent(self, elem, level=0):
i = "\n" + level * " "
if len(elem):
if not elem.text or not str(elem.text).strip():
elem.text = i + " "
for e in elem:
# print e
self.__indent(e, level + 1)
if not e.tail or not e.tail.strip():
e.tail = i
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
return elem
def CDATA(text=None): # 為了配合顯示xml里的CDATA格式化,本處全部採用大寫形式。
"""生成CDATA標簽相關xml數據"""
element = Element("CDATA")
element.text = text
return element
if __name__ == '__main__':
curpath = os.getcwd()
excel_folder = os.path.join(curpath, "Excel_Input")
excel_list = os.listdir(excel_folder)
xml_folder = os.path.join(curpath, "XML_Output")
# 先刪除之前的老的XML_Output文件夾
try:
shutil.rmtree(xml_folder)
except:
pass
if not os.path.exists(xml_folder):
os.mkdir(xml_folder)
# 批量逐個解析Excel
for each_excel in excel_list:
print("*" * 60)
print("正在處理%s" % each_excel)
print("*" * 60)
e_obj = ExcelReader("%s%s%s" % (excel_folder, os.sep, each_excel))
all_cases_list = e_obj.get_all_cases()
excel_name, posfix = os.path.splitext(each_excel)
x_obj = XmlWriter(all_cases_list, "%s%s%s.xml" % (xml_folder, os.sep, excel_name))
x_obj.write_xml()
print("\nExcel to XML全部處理完畢! XML生成到了XML_Output文件夾!")
os.system("pause")
本案例素材下載
包括:測試用例模板,etree包(含ElementTree),Python腳本
跳轉到自拍教程官網下載
武散人出品,請放心下載!
Testlink導入效果
跳轉到官網查看Excel轉XML,並導入Testlink的視頻。
更多更好的原創文章,請訪問官方網站:www.zipython.com
自拍教程(自動化測試Python教程,武散人編著)
原文鏈接:https://www.zipython.com/#/detail?id=d5a9c3981b4b4d8ab560a09c428c39c1
也可關註“武散人”微信訂閱號,隨時接受文章推送。