• 基于Jmeter的接口测试报告


    【引言】

    由于要使用Jmeter做接口自动化测试,Jmeter原生报告展示的内容多是基于性能参数展示;自动化测试不需要这么繁杂的报告;故决定自己开发一个简单明了的展示报告;

    【思路】

    利用jmeter执行结果树,生成xml报告;通过对内容进一步解析生成html报告;

    需要做以下几件事:

    代码目录结构:

    root:
    ├─jmx
    ├─jtl
    ├─reports
    │ ├─1640250447
    |---sax_xml.py

    |---report.py

    |---run_jmeter.py

    1.jmx用来存放jmeter  jmx脚本

    2.jtl用于存储jmeter生成的xml报告

    3.reports用于存储生成的报告  xxxx时间戳目录\report.html

    4. sax_xml.py用于解析jmeter xml并在reports目录生成报告;

    5.run_jmeter.py 用于执行jmeter脚本;  

    备注:

    1.如果你只是想解析报告,不需要第5步文件,只要按照目录将xml报告存在jtl中即可;

    2.如果你想,执行和解析一体,那就需要将jmeter的jmx脚本存到jmx目录;并且对结果树进行设置;

    --------------------------------------------------------------详细代码-------------------------------------------------

    1.利用python的jinja2生成报告模版;

    【模版html】

    template.html

    【生成Html代码】

    import jinja2
    import os
    import io
    import xml.sax
    from sax_xml import ReportHandler
    
    
    class Report:
        @classmethod
        def report(cls, root_dir: str, report_path: str, result_json: dict) ->dict:
            """填充报告模版"""
            env = jinja2.Environment(
                loader=jinja2.FileSystemLoader(root_dir),
                extensions=(),
                autoescape=True
            )
    
            template = env.get_template("template.html", root_dir)
            html = template.render({"results": result_json})
            output_file = os.path.join(
                report_path,
                "report.html"
            )
            with io.open(output_file, 'w', encoding="utf-8") as fp:
                fp.write(html)
            print(output_file)
    
            return result_json
    
        @classmethod
        def parse_xml_generate_html(cls, xml_file: str, report_path: str) -> dict:
            """
            从xml解析数据,填充到html模版,并成生html报告
            """
            # 创建一个 XMLReader
            parse = xml.sax.make_parser()
            # turn off namespaces
            parse.setFeature(xml.sax.handler.feature_namespaces, 0)
    
            # 重写 ContextHandler
            Handler = ReportHandler()
            parse.setContentHandler(Handler)
    
            parse.parse(xml_file)
            results = Handler.content()
    
            results_json = Report.report(os.getcwd(), report_path, results)
    
            return results_json
    
    
    
    if __name__ == '__main__':
        # 模版解析格式
        # results = {
        #     "tester": "test",
        #     "case_count": 123,
        #     "time": 456,
        #     "success": 123,
        #     "fail": 0,
        #     "error": 0,
        #     "table": [{
        #         "result": True,
        #         "desc": "123",
        #         "exe_time": "123",
        #         "time": "123",
        #         "case_exec_time":"执行时间",
        #         "deail": "12313"
        #     },
        #     {
        #         "result": False,
        #         "desc": "333",
        #         "exe_time": "44",
        #         "time": "55",
        #         "case_exec_time":"执行时间",
        #         "deail": "666"
        #     }
        #     ]
        # }
        xml_report_file = os.path.join(
            os.path.join(os.getcwd(), 'jtl'),
            "1639479506.xml"
        )
        html_report_path = os.path.join(os.getcwd(), 'reports')
        Report.parse_xml_generate_html(xml_report_file, html_report_path)
    report.py

    2.解析xml代码

    import time
    import xml.sax
    import json
    
    
    class ReportHandler(xml.sax.handler.ContentHandler):
        def __init__(self):
            # 存储遍历每个节点名称;
            self.current_data = ""
            # 存储请求,响应数据;
            self.request_data = []
            self.response_data = []
            self.method = ""
            self.url = ""
            # url中带特列字符,解析时会进行分开遍历;需要放在list最后进行join拼接;
            self.url_list = []
            self.ts = ""
            # 临时存储线程组结果
            self.result = {}
            # 单接口名称,即jmeter请求命名;
            self.sample_name = ""
            # 线程组名称
            self.group_name = ""
            # 接口响应,请求,临时存储字段;
            self.response = ""
            self.request = ""
            # 存储所有结果,按结果传给html模版进行渲染页面;
            self.all_result = []
            # case总数,case成功总数,case失败总数
            self.case_count = {}
            self.case_success_count = {}
            self.case_error_count = {}
            # 存储,成功,错误,失败结果;
            self.success = []
            self.error = []
            self.failure = []
            # 临时存储每个每个case结果,{[case1,case2]}
            self.temp_result = {}
            # 总体case执行耗时s
            self.total_time = []
            # 单个case执行耗时总时间ms
            self.case_total_time = {}
            # 存储每个请求耗时ms
            self.t = ""
            # 单个case开始请求时间;
            self.case_exec_time = {}
            self.s = ""
    
        def startElement(self, name, attrs):
            """
            元素开始事件处理
            """
            self.current_data = name
            if name == "httpSample":
                self.ts = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(attrs["ts"])/1000))
                self.success.append(eval(str(attrs["s"]).capitalize()))
                self.sample_name = attrs["lb"]
                self.group_name = attrs["tn"]
                self.case_count[attrs["tn"]] = 1
                self.t = attrs["t"]
                self.s = eval(str(attrs["s"]).capitalize())
    
                # 记录各线程组结果list {”线程级名称1“:[True, False, True], ”线程级名称2“:[True, True, True]}
                if not self.result.get(attrs["tn"]):
                    self.result[attrs["tn"]] = []
                self.result[attrs["tn"]].append(eval(str(attrs["s"]).capitalize()))
    
                self.total_time.append(int(attrs["t"]))
    
        def endElement(self, name):
            """元素结束事件处理"""
            if self.current_data == "responseData":
                if self.response_data:
                    try:
                        self.response = json.dumps(
                            json.loads(
                                "".join(self.response_data)
                            ),
                            sort_keys=True,
                            indent=2
                        )
                    except json.JSONDecodeError:
                        self.response = self.response_data
                else:
                    self.response = ""
    
            if self.current_data == "queryString":
                if self.request_data:
                    try:
                        self.request = json.dumps(
                            json.loads(
                                "".join(self.request_data)
                            ),
                            sort_keys=True,
                            indent=2
                        )
                    except json.JSONDecodeError:
                        self.request = self.request_data
                else:
                    self.request = ""
    
            if self.current_data == "httpSample":
                self.case_success_count[self.group_name].append(eval(str(self.s.capitalize())))
    
            if self.response and self.method and self.url:
                result_string = '''%(desc)s\n%(ts)s %(method)s 请求:%(url)s\n请求:\n%(request)s\n响应:\n%(response)s\n'''
    
                # 单条case上的详细内容
                res = result_string % dict(
                    desc=self.sample_name,
                    ts=self.ts,
                    method=self.method,
                    url=self.url,
                    request=self.request,
                    response=self.response
                )
    
                # temp_result按线程组名称为key划分,存组整个线程中请求结果;
                if not self.temp_result.get(self.group_name):
                    self.temp_result[self.group_name] = []
                    self.case_total_time[self.group_name] = []
                    self.case_exec_time[self.group_name] = []
                    self.case_success_count[self.group_name] = []
    
                self.temp_result[self.group_name].append(res)
                self.case_total_time[self.group_name].append(int(self.t))
                self.case_exec_time[self.group_name].append(self.ts)
                self.case_success_count[self.group_name].append(self.s)
    
                # 恢复初始值
                self.method = ""
                self.url = ""
                self.ts = ""
                self.sample_name = ""
                self.group_name = ""
                self.response = ""
                self.request = ""
                self.t = ""
                self.s = ""
    
            self.request_data = []
            self.response_data = []
            self.current_data = ""
            self.url = ""
            self.url_list = []
    
        def characters(self, content):
            """内容处理事件"""
            if self.current_data == "responseData":
                self.response_data.append(content)
    
            if self.current_data == "queryString":
                self.request_data.append(content)
    
            if self.current_data == "method":
                self.method = content
    
            if self.current_data == "java.net.URL":
                if "http" in content or content:
                    self.url_list.append(content)
                self.url = ''.join(self.url_list)
    
            if self.current_data == "error":
                self.error.append(eval(str(content).capitalize()))
    
            if self.current_data == "failure":
                self.failure.append(eval(str(content).capitalize()))
    
        def content(self):
            """结果进行结构组合"""
            # 遍历按线程组分组的结果;
            for key, val in self.temp_result.items():
                self.all_result.append(
                    {
                        "result": all(self.result[key]),
                        "desc": key,
                        "exe_time": self.total_time,
                        "case_exec_time": self.case_exec_time[key][0],
                        "time": sum(self.case_total_time[key]),
                        "deail": ''.join(val)
                    }
                )
    
            case_result_count = [all(val) for _, val in self.case_success_count.items()]
    
            # 最终所有结果;
            results = {
                "tester": "test",
                "case_count": len(self.case_count.keys()),
                "time": sum(self.total_time) / 1000,
                "success": case_result_count.count(True),
                "fail": case_result_count.count(False),
                "error": self.error.count(True),
                "table": self.all_result
            }
    
            return results
    
    
    if __name__ == "__main__":
        # 创建一个 XMLReader
        parser = xml.sax.make_parser()
        # turn off namespaces
        parser.setFeature(xml.sax.handler.feature_namespaces, 0)
    
        # 重写 ContextHandler
        Handler = ReportHandler()
        parser.setContentHandler(Handler)
    
        parser.parse("1639479506.xml")
        print(
            Handler.content()
        )
    sax_xml.py

    3. jmeter运行脚本

    import os
    import time
    import requests
    from report import Report
    
    
    class WX:
        """企业微信"""
    
        @classmethod
        def send_message(cls, content: str, token: str) -> dict:
            """
            发送企业微信机器人文本消息
            :param content: 推送内容
            :param token: 机器人key
            :return: json  {"errcode":0,"errmsg":"ok"}
            """
            url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={}".format(token)
            payload = {
                "msgtype": "text",
                "text": {
                    "content": content,
                }
            }
            res = requests.post(url, json=payload)
    
            return res.json()
    
    
    class Result:
        """从statistics.json读取测试结果"""
    
        @classmethod
        def run_jmeter(cls, timestamp: str, jmx_file_name) -> str:
            """执行jmeter"""
            cur = os.getcwd()
            jtl = os.path.join(cur, "jtl")
            reports = os.path.join(cur, "reports")
    
            # 创建report文件夹
            os.makedirs(os.path.join(reports, timestamp))
    
            cmd = "jmeter -n -t {}  -Jresultsfile={}".format(
                os.path.join(
                    os.path.join(cur, "jmx"),
                    jmx_file_name
                ),
                os.path.join(jtl, "{}.xml".format(timestamp))
            )
    
            os.system(cmd)
            resultsfile = os.path.join(jtl, "{}.xml".format(timestamp))
            return resultsfile
    
        @classmethod
        def get_test_result(cls, timestamp: str, desc: str, url: str, jmx_file_name: str) -> str:
            """
            从jmeter执行结果timeStamp.xml中读取结果;
            """
            cur_path = os.getcwd()
    
            tmpl = """%(desc)s【已完成】:\n共%(case)s个接口, 执行耗时 %(used_time)ss, 通过 %(Pass)s, 失败 %(error)s, 通过率 %(rate)s \n测试报告地址:%(url)s"""
    
            # 执行jmeter
            cls.run_jmeter(timestamp, jmx_file_name)
    
            report_dir = os.path.join(
                os.path.join(cur_path, "reports"),
                timestamp
            )
            xml_report_file = os.path.join(
                os.path.join("jtl", "{}.xml".format(timestamp))
            )
    
            results_json = Report.parse_xml_generate_html(xml_report_file, report_dir)
    
            # 报告成功率
            total_count = results_json['case_count']
            error_count = int(results_json['case_count']) - int(results_json['success'] - int(results_json['fail']))
            success_count = int(results_json['success'])
            success_rate = success_count / total_count * 100
    
            ret = tmpl % dict(
                desc=desc,
                case=str(total_count),
                used_time=results_json['time'],
                Pass=str(success_count),
                error=str(error_count),
                rate='{}%'.format(str(success_rate)),
                url=url
            )
    
            return ret
    
    
    if __name__ == "__main__":
        report_dir_name = str(int(time.time()))
        url = "http://60.205.217.8:5004/pro_mall/reports/{}".format(report_dir_name)
        content = Result.get_test_result(
            report_dir_name,
            "Pro H5商城API自动化测试执行",
            url,
            "H5商城自动化Pro.jmx"
        )
        #WX.send_message(content, "3d38fe6b-c49f-46d6-8b17-9d92b9d5a143")
    run_jmeter.py

    4.jmeter设置

     合勾上:

     输出xml存储位置:这里是脚本运行时指的变量存储的位置resultsfile;

    这个是利用了jmeter本身命令行输出参数-J:jmeter -n -t {}  -Jresultsfile={}

     

    【结果样式】


    作  者: 天枢
    出  处: http://www.cnblogs.com/yhleng/
    关于作者:专注于软件自动化测试领域。如有问题或建议,请多多赐教!
    版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
    特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者 直接私信
    声援博主:如果您觉得文章对您有帮助,可以点击文章右下角 推荐 一下。您的鼓励是作者坚持原创和持续写作的最大动力!

  • 相关阅读:
    Elasticsearch集群使用ik分词器
    ES Docs-3:Modifying Data
    ES Docs-2:Exploring ES cluster
    ES Docs-1:Installation Elasticsearch-2.3.1
    Ubuntu的apt-get本地源搭配——根据需要自己添加软件作源
    Linux中Root用户密码变更、密码忘记
    Eclipse安装SVN插件
    【转】Subversion快速入门教程-动画演示
    【转】‘svn’不是内部或外部命令,也不是可运行的程序
    pt-summary pt-mysql-summary工具
  • 原文地址:https://www.cnblogs.com/yhleng/p/15691297.html
Copyright © 2020-2023  润新知