使用Flask搭建基于unittest的简单用例挑选及执行平台

在用例组织上,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个页面:

  1. 添加用例 suite_add
  2. 用例列表(可以选择suite执行)suite_list
  3. 报告页面 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这个接口的实现逻辑为:

  1. 使用unittest的discover遍历并抽取所有用例返回给客户端(GET请求)
  2. 获取到客户端挑选的用例并生成unittest的TestSuite对象
  3. 根据客户端传的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" >{{test}}</div>
    {% endfor %}
    <div><input type="submit" value="保存"></div>
</form>
</body>
</html>

执行后访问 http://127.0.0.1:5000/suite_add
使用Flask搭建基于unittest的简单用例挑选及执行平台
标题输入suite1,选择用例,点击添加会在dashboard/suites下生成suite1.testsuite文件
使用Flask搭建基于unittest的简单用例挑选及执行平台

由于添加后跳转到"/",这个接口(页面)还没实现,所以会报错,我们现在来实现suite列表, 思路:

  1. 使用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>

使用Flask搭建基于unittest的简单用例挑选及执行平台

显示报告方法(报告必须生成在templates目录下)

@app.route("/report", methods=['GET'])
def report():
    return render_template('report.html')

执行某个testsuite后会跳转到报告页面
使用Flask搭建基于unittest的简单用例挑选及执行平台

完整代码:api_test_framework

更多学习资料请加添加作者微信:lockingfree获取