《自拍教程63》Python Testlink用例導入工具excel2xml

来源:https://www.cnblogs.com/zipython/archive/2020/05/07/12845601.html
-Advertisement-
Play Games

案例故事: Testlink是我們常用的用例管理工具,很多公司其實都在用, Testlink 支持制定測試計劃,支持多人線上同時管理維護/執行測試用例,自動生成測試報告等。 我個人也非常非常不推薦Excel線下管理測試用例, 但是官方提供的Testlink版本,是不支持Excel導入的,只能進行Xm ...


案例故事: Testlink是我們常用的用例管理工具,很多公司其實都在用,
Testlink 支持制定測試計劃,支持多人線上同時管理維護/執行測試用例,自動生成測試報告等。
我個人也非常非常不推薦Excel線下管理測試用例,
但是官方提供的Testlink版本,是不支持Excel導入的,只能進行Xml導入。

沒這個Excel導入功能,確實有點可惜了,
但是秉著Python是萬能的,只是將Excel轉Xml本也不是什麼難事。


準備階段
  1. 操作Excel的模塊,一直首選openpyxl, 直接pip install openpyxl即可
  2. 操作Xml的模塊,我建議首選ElementTree, 但是需要修改其源代碼,原因如下3,4步。
  3. 根據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
也可關註“武散人”微信訂閱號,隨時接受文章推送。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. OpenCV讀取圖片 1.1 簡述 OpenCV讀取圖片的方法是cv2.imread(),讀取出來圖片的格式是BGR與常規的彩色圖像的格式(RGB)相反,這一點一定要註意。 OpenCV顯示圖片的方法是cv2.imshow(),顯示的格式是BGR。 小生就言於此O(∩_∩)O哈哈~,直接上例子 ...
  • 消息隊列中間件是分散式系統中重要的組件,主要解決應用耦合,非同步消息,流量削鋒等問題。實現高性能,高可用,可伸縮和最終一致性架構。是大型分散式系統不可缺少的中間件。消息形式支持點對點和訂閱-發佈。 ActiveMQ是什麼 1、ActiveMQ是消息隊列技術,為解決高併發問題而生 2、ActiveMQ生 ...
  • 我們在的項目組呢,有一項工作是,收郵件(很大程度上使用郵件是為了存個底),然後從我們的系統裡邊查一下相關信息,然後回覆個郵件的工作。雖然工作量並不大,但是會把時間切的稀碎。為了拯救我的時間,所以做了一個郵件的值班機器人。讓他來頂替我自動回覆郵件,考慮到這個東西應該也有不少人會用得到,所以就把這個東西 ...
  • 數據很重要 在介紹MyBatis事務之前,先普及下資料庫事務相關知識 事務(Transaction)是訪問並可能更新資料庫中各種數據項的一個程式執行單元(unit)。事務通常由高級資料庫操縱語言或編程語言(如SQL,C++或Java)書寫的用戶程式的執行所引起,並用形如begin transacti ...
  • 原創聲明:本文轉載自公眾號【胖滾豬學編程】​ 某日,胖滾豬寫的代碼導致了一個生產bug,奮戰到凌晨三點依舊沒有解決問題。胖滾熊一看,只用了一個volatile就解決了。並告知胖滾豬,這是併發編程導致的坑。這讓胖滾豬堅定了要學好併發編程的決心。。於是,開始了我們併發編程的第一課。 序幕 BUG源頭之一 ...
  • 1、創建聊天消息表,其表的欄位有消息內容,發送時間和發送者的名稱; SQL: CREATE TABLE `guanhui`.`message` ( `id` INT(10) NOT NULL AUTO_INCREMENT COMMENT '消息ID' , `content` VARCHAR(255) ...
  • 官網:http://www.axios-js.com/zh-cn/docs/ 什麼是 axios? Axios 是一個基於 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中。 特性 從瀏覽器中創建 XMLHttpRequests 從 node.js 創建 http 請求 支持  ...
  • 如果熟悉C 語言的小伙伴們一般都會知道委托、事件的好處,只需在某個類中提前定義好公開的委托或事件(委托的特殊表現形式)變數,然後在其它類中就可以很隨意的訂閱該委托或事件,當委托或事件被觸發執行時,會自動通知所有的訂閱者進行消費處理。(觀察者模式用委托來實現是最好不過了,DDD所提倡的事件驅動其根本理 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...