在用例组织上,unittest的Test Suite的拥有非常好的灵活性,然而Test Suite一般要提前编制好,添加和组织用例必须使用代码,不方便使用。
本文使用 Flask + unittest.TestSuite + pickle搭建一个简单的unittest用例挑选和执行平台。
思路:
添加Test Suite: 使用discover()发现所有测试用例 -> 挑选用例 并生成Test Suite对象 -> 使用pickle.dump()序列化成文件 保存
执行Test Suite: 使用os.listdir()得到Test Suite列表 -> 使用pickle.load()反序列化成TestSuite对象 -> 执行TestSuite并生成报告
需要安装 Flask, pip install flask
在你的测试框架或用例同级目录下建立dashboard目录,结构如下
- dashboard
- suites 序列化的Test Suite文件目录
- templates 页面模板
- app.py
- test
- case 用例目录
新建app.py
作为我们的接口服务文件
这里我们只实现3个页面:
- 添加用例 suite_add
- 用例列表(可以选择suite执行)suite_list
- 报告页面 report
使用Flask写接口(页面)的大致套路
# 1. 导入包
from flask import Flask # Flask类,使用flask框架的基本类
from flask import request # 请求对象,用于获取请求参数
from flask import render_template # 用于渲染模板,并返回客户端一个html页面
from flask import redirect # 用于跳转到其他url
# 2. 实例化Flask类
app = Flask(__name__) # 使用当前模块实例化一个Flask对象,app是自定义的变量(后面使用要一致)
# 3. 编写接口(页面)并挂载访问地址,指定允许的请求方法
@app.route("/suite_add", methods=["GET", "POST"] # 接口(页面)地址为/suite_add, 支持GET和POST
def suite_add():
if request.method == 'POST': # POST方法用来处理添加
.....
return redirect("/") # 添加后跳转到 首页
return render_template("suite_add.html") # 如果是GET方法,渲染返回suite_add页面
# 4. 运行调试
if __name__ == '__main__':
app.run()
suite_add这个接口的实现逻辑为:
- 使用unittest的discover遍历并抽取所有用例返回给客户端(GET请求)
- 获取到客户端挑选的用例并生成unittest的TestSuite对象
- 根据客户端传的TestSuite名称,序列化成指定名称的文件并保存
代码如下:
from flask import Flask # Flask类,使用flask框架的基本类
from flask import request # 请求对象,用于获取请求参数
from flask import render_template # 用于渲染模板,并返回客户端一个html页面
from flask import redirect # 用于跳转到其他url
import os # 用于组装绝对路径
import unittest
import pickle # 序列化方法
# 一些要使用到的目录绝对路径
app_dir = os.path.dirname(os.path.abspath(__file__)) # dashborad目录
case_dir = os.path.join(os.path.dirname(app_dir), 'test', 'case') # 测试用例目录
suite_dir = os.path.join(app_dir, 'suites') # 测试套件目录
def collect(): # 收集用例并组成Test Suite
suite = unittest.TestSuite() # 新建Test Suite
def _collect(tests): # 由于unittest discover得到的TestSuite中包含了目录及子目录的路径,这里只把所有的用例抽取出来
if isinstance(tests, unittest.TestSuite):
if tests.countTestCases() != 0:
for i in tests:
_collect(i)
else:
suite.addTest(tests)
_collect(unittest.defaultTestLoader.discover(case_dir))
return suite
app = Flask(__name__) # 使用当前模块实例化一个Flask对象,app是自定义的变量(后面使用要一致)
@app.route("/suite_add", methods=["GET", "POST"] # 接口(页面)地址为/suite_add, 支持GET和POST
def suite_add():
tests = [] # 用例集合
for case in collect():
tests.append(case.id()) # 遍历testsuite中的用例,通过case.id()拿到用例名
if request.method == 'POST': # POST方法用来处理添加
suite_name = request.form.get("suite_name") # 从前端获取需要新建的testsuite名称
cases = request.form.getlist("cases") # 获取到前端选择的用例列表
suite = unittest.defaultTestLoader.loadTestsFromNames(cases) # 通过用例名列表生成TestSuite
with open(os.path.join(suite_dir, suite_name+".testsuite"), 'wb') as f: # 序列化并保存TestSuite
pickle.dump(suite, f)
return redirect("/") # 添加后跳转到 首页
return render_template("suite_add.html") # 如果是GET方法,渲染返回suite_add页面
if __name__ == '__main__': # 运行接口方法
app.run()
templates/suite_add.html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新增Test Suite</title>
</head>
<body>
<h1>新增Test Suite</h1>
<form action="#" method="post">
标题:<input type="text" name="suite_name">
<h4>选择用例</h4>
{% for test in tests%}
<div><input type="checkbox" id="cases" name="cases" value="{{test}}">{{test}}</div>
{% endfor %}
<div><input type="submit" value="保存"></div>
</form>
</body>
</html>
执行后访问 http://127.0.0.1:5000/suite_add
标题输入suite1,选择用例,点击添加会在dashboard/suites下生成suite1.testsuite文件
由于添加后跳转到"/",这个接口(页面)还没实现,所以会报错,我们现在来实现suite列表, 思路:
- 使用os.listdir()遍历suites目录下的.testsuite文件并渲染到页面上
在app.py中添加
from HTMLTestReportCN import HTMLTestRunner # 这个是生成html报告的文件,放在app.py同级即可
@app.route("/", methods=['GET', 'POST'])
def suite_list():
suite_list = [suite.split(".")[0] for suite in os.listdir('suites') if suite.endswith(".testsuite")] # 遍历并去掉.testsuite扩展名
if request.method == 'POST': # 执行testsuite方法
suite_name = request.form.get("suite")
import sys;sys.path.append(case_dir) # 反序列化必须将 用例目录添加到sys.path中
with open(os.path.join(suite_dir, suite_name+".testsuite"), 'rb') as f: # 反序列化并得到TestSuite对象
suite = pickle.load(f)
with open(report_file, 'wb') as f: # 执行TestSuite并在templates目录中生成html文件
result = HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite)
return redirect("/report") # 显示报告
return render_template('suite_list.html', suite_list=suite_list) # GET方法返回suite列表页面
suite_list页面代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Test Suite列表</h1>
<a href="/suite_add">添加Test Suite</a>
<br>
<form action="#" method="post">
{% for suite in suite_list %}
<input type="radio" name="suite" value="{{suite}}">{{ suite }}
<br>
{% endfor %}
<input type="submit" value="执行">
</form>
</body>
</html>
显示报告方法(报告必须生成在templates目录下)
@app.route("/report", methods=['GET'])
def report():
return render_template('report.html')
执行某个testsuite后会跳转到报告页面
完整代码:api_test_framework
更多学习资料请加添加作者微信:lockingfree获取