Jenkins + TestNG 实现自助式自动化测试平台
摘要:
本文介绍了如何使用 Jenkins 和 TestNG 实现满足复杂测试需求的”自助式”自动化测试平台。该方案以 Jenkins 作为平台的基础,结合功能强大的插件及系统配置,部署基于 TestNG 的自动化测试包,并提供了友好的 Web 访问界面。项目成员可以在任何时间和地点,通过浏览器访问该平台,而且可以按照不同需求选择测试环境、测试集、测试用例,并提交自动化测试请求,达到真正的“自助式”自动化测试。该平台它可以极大地提高开发和测试团队自动化脚本的使用效率和便捷性。
目录:
1、提出需求
2、方案设计
3、编码
4、测试
正文:
一. 提出需求
测试部开发一套自己的质量中心,主要用于缺陷统计、接口自动化测试、APP自动化测试、在线监控等,在接口自动化测试和APP自动化测试过程中,我们需要实现,用户选择不同的测试集合,集合中包含哪些测试用例,TestNG会自己去执行不同的测试用例,做到根据用户不同的输入做出不同的响应。
二. 方案设计
1、质量中心(WEB)提供测试用例管理和测试集合管理,创建测试任务,关联测试集合,测试集合又关联测试用例
2、质量中心(WEB)将创建的测试任务相应的调用Jenkins任务,并把相应的需要传递的参数传递过去
3、Jenkins任务启动,先根据传进来的测试任务编号,找到相应的测试集合和测试用例,并用Python脚本生成相应的TestNG XML文件,其中XML文件定义了需要执行的测试用例,这样子就做到了,根据用户不同的输入做出不同的相应
4、Jenkins执行Python生成的TestNG XML文档
5、TestNG在Jenkins上执行后会在surefine-reports文件夹下面生成emailable-report.html测试报告
6、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__':
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中的参数
希望对大家有所帮助!!