1. 程式人生 > >Jenkins + TestNG 實現自助式自動化測試平臺

Jenkins + TestNG 實現自助式自動化測試平臺

摘要:
本文介紹瞭如何使用 Jenkins 和 TestNG 實現滿足複雜測試需求的”自助式”自動化測試平臺。該方案以 Jenkins 作為平臺的基礎,結合功能強大的外掛及系統配置,部署基於 TestNG 的自動化測試包,並提供了友好的 Web 訪問介面。專案成員可以在任何時間和地點,通過瀏覽器訪問該平臺,而且可以按照不同需求選擇測試環境、測試集、測試用例,並提交自動化測試請求,達到真正的“自助式”自動化測試。該平臺它可以極大地提高開發和測試團隊自動化指令碼的使用效率和便捷性。

目錄:

  1. 提出需求
  2. 方案設計
  3. 編碼
  4. 測試

正文:
一. 提出需求
測試部開發一套自己的質量中心,主要用於缺陷統計、介面自動化測試、APP自動化測試、線上監控等,在介面自動化測試和APP自動化測試過程中,我們需要實現,使用者選擇不同的測試集合,集合中包含哪些測試用例,TestNG會自己去執行不同的測試用例,做到根據使用者不同的輸入做出不同的響應。

二. 方案設計

  • 質量中心(WEB)提供測試用例管理和測試集合管理,建立測試任務,關聯測試集合,測試集合又關聯測試用例
  • 質量中心(WEB)將建立的測試任務相應的呼叫Jenkins任務,並把相應的需要傳遞的引數傳遞過去
  • Jenkins任務啟動,先根據傳進來的測試任務編號,找到相應的測試集合和測試用例,並用Python指令碼生成相應的TestNG XML檔案,其中XML檔案定義了需要執行的測試用例,這樣子就做到了,根據使用者不同的輸入做出不同的相應
  • Jenkins執行Python生成的TestNG XML文件
  • TestNG在Jenkins上執行後會在surefine-reports資料夾下面生成emailable-report.html測試報告
  • Jenkins任務執行完再次呼叫Python指令碼,將生成的emailable-report.html報告寫入MySQL儲存起來,提供給質量中心(WEB)檢視

三. 編碼

3.1 質量中心->APP自動化測試 資料庫設計
這裡寫圖片描述

app_elements:儲存app頁面控制元件,如Android的resource id,iOS的xpath
app_execute: 儲存支援執行結果,測試報告、測試結果等
app_mobile: 儲存測試機型的相關資訊
app_modules: 儲存測試APP中包含的模組,分層的思想,方便管理
app_platform: 儲存測試APP,支援多個APP
app_suitecase: 儲存測試集合與測試用例的關係,一個測試用例對應多個測試集合
app_testcase:儲存測試用例
app_testjob:儲存測試任務,關聯相應的Jenkins路徑,直接多地執行
app_testsuite:儲存測試集合

3.2 Jenkins引數配置
這裡寫圖片描述

TestPlatform: 測試平臺,是Android還是iOS
TestDevice: 測試裝置,Android需要傳入udid,iOS不需要
TestEnv: 測試環境,qa還是live
TestJobId: 測試任務編號,通過這個任務編號可以MySQL查詢到關聯的測試集合以及測試集合中的測試用例
TestExecuteId: 執行任務編號,傳入Python指令碼,講生成的測試報告emailable-report.html存放相應的位置

3.3 Python檔案生成TestNG XML

# -*- coding:utf-8 -*-
import os
import MySQLdb
import sys
import xml.dom.minidom

# 外部傳入的測試任務編號
test_job_id = sys.argv[1]


# 連線db
def connect_db(db):
    db = MySQLdb.connect(host="10.9.8.20",
                         port=3306,
                         user="***",
                         passwd="***",
                         db=db,
                         charset="utf8")
    return db


# 請求mysql獲取資料
def get_data(db, sql):
    conn = connect_db(db)
    cur = conn.cursor()
    cur.execute(sql)
    data = cur.fetchall()
    cur.close()
    conn.commit()
    conn.close()
    return data


# 判斷檔案是否存在,如果不存在立即建立,如果存在,則立即刪除,重新覆蓋
def xml_exist():
    file_name = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/resources/YouYu_Stock.xml"
    if os.path.exists(file_name):
        os.remove(file_name)
    f = open(file_name, "w")
    f.close()
    return file_name


# 獲取測試用例英文名
def get_case_name():
    suite_id = get_data("app", "select suite_id from app_testjob where id=" + test_job_id)[0][0]
    case_id = get_data("app", "select case_id from app_suitecase where suite_id=" + str(suite_id))
    list_case_name = []
    for i in xrange(0, len(case_id)):
        case_name = get_data("app", "select ename from app_testcase where id=" + str(case_id[i][0]))[0][0]
        list_case_name.append(case_name)
    return list_case_name


def main():
    file_name = xml_exist()
    case_names = get_case_name()
    doc = xml.dom.minidom.Document()
    root = doc.createElement("suite")
    root.setAttribute("name", "TestSuite")
    root.setAttribute("parallel", "false")
    doc.appendChild(root)
    # 新增parameter
    nodeManager = doc.createElement("parameter")
    nodeManager.setAttribute("name", "url")
    nodeManager.setAttribute("value", "127.0.0.1")
    root.appendChild(nodeManager)
    nodeManager = doc.createElement("parameter")
    nodeManager.setAttribute("name", "port")
    nodeManager.setAttribute("value", "4727")
    root.appendChild(nodeManager)
    nodeManager = doc.createElement("parameter")
    nodeManager.setAttribute("name", "device")
    nodeManager.setAttribute("value", "${TestPlatform}")
    root.appendChild(nodeManager)
    nodeManager = doc.createElement("parameter")
    nodeManager.setAttribute("name", "udid")
    nodeManager.setAttribute("value", "${TestDevice}")
    root.appendChild(nodeManager)
    nodeManager = doc.createElement("parameter")
    nodeManager.setAttribute("name", "env")
    nodeManager.setAttribute("value", "${TestEnv}")
    root.appendChild(nodeManager)
    # 新增test case
    for i in xrange(0, len(case_names)):
        print case_names[i]
        node_test = doc.createElement("test")
        node_test.setAttribute("name", case_names[i])
        node_classes = doc.createElement("classes")
        node_test.appendChild(node_classes)
        node_class = doc.createElement("class")
        node_class.setAttribute("name", "com.youyu.stock.automation.mobile.testcase.registerAndLogin.RegisterAndLoginTestCase")
        node_classes.appendChild(node_class)
        node_methods = doc.createElement("methods")
        node_class.appendChild(node_methods)
        node_include = doc.createElement("include")
        node_include.setAttribute("name", case_names[i])
        node_methods.appendChild(node_include)
        root.appendChild(node_test)
    f = file(file_name, "w")
    doc.writexml(f, "\t", "\t", "\n", "utf-8")
    f.close()

if __name__ == '__main__':
    main()

3.4 Jenkins配置與執行Maven TestNG
這裡寫圖片描述

Maven pom.xml定義:

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19.1</version>
                <configuration>
                    <systemPropertyVariables>
                        <testEnvironment>${TestDevice}</testEnvironment>
                        <testEnvironment>${TestEnv}</testEnvironment>
                        <testEnvironment>${TestJobId}</testEnvironment>
                    </systemPropertyVariables>
                    <suiteXmlFiles>
                        <suiteXmlFile>${automationFile}</suiteXmlFile>
                    </suiteXmlFiles>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
  </build>

3.5 生成的TestNG XML檔案,例如:

這裡寫圖片描述

3.6 生成的emailable-report.html儲存在MySQL中

# -*- coding:utf-8 -*-
import os
import sys
import MySQLdb
from bs4 import BeautifulSoup

# 外部傳入執行任務時引數的編號
execute_id = sys.argv[1]


# 連線db
def connect_db(db):
    db = MySQLdb.connect(host="10.9.8.20",
                         port=3306,
                         user="***",
                         passwd="***",
                         db=db,
                         charset="utf8")
    return db


# 請求mysql獲取資料
def get_data(db, sql):
    conn = connect_db(db)
    cur = conn.cursor()
    cur.execute(sql)
    data = cur.fetchall()
    cur.close()
    conn.commit()
    conn.close()
    return data


def write_result():
    file_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "/target/surefire-reports/emailable-report.html"
    f = open(file_path, "r")
    html = f.read()
    # 測試結果寫入MySQL
    soup = BeautifulSoup(html)
    PassCase = int(soup.find_all("th", class_="num")[0].get_text())
    FailCase = int(soup.find_all("th", class_="num")[2].get_text())
    # 測試報告寫入MySQL
    html = MySQLdb.escape_string(html)
    get_data("app", "update app_execute set test_result=\"%s\", test_report=\"%s\" where id=%s" % (str(PassCase) + "/" + str(PassCase+FailCase), html, str(execute_id)))

if __name__ == '__main__':
    write_result()

四. 測試

4.1 質量平臺
這裡寫圖片描述

這裡寫圖片描述

總結:
為了實習根據使用者不同的輸入做出不同的相應,期間嘗試方案如下:

  1. TestNG @Test enabled=false或者true 失敗,失敗原因:enabled傳入值必須是定值
  2. TestNG 自動-testname可以根據傳入不同的test name選擇執行不同的測試用例,失敗,依賴包太多,Maven專案下載的包不能通過classpath方式安裝到本地classpath,maven進行了一層封裝,maven的dependences
  3. Maven 執行命令列mvn clean test將TestNG引數傳遞進去,失敗,失敗原因:Maven -Dtest僅僅選擇需要執行的,並不能知道TestNG中的引數