• 实战 | 如何用 Python 统计 Jira 数据并可视化


    目前公司使用 Jira 作为项目管理工具,在每一次迭代完成后的复盘会上,我们都需要针对本次迭代的 Bug 进行数据统计,以帮助管理层能更直观的了解研发的代码质量

    本篇文章将介绍如何利用统计 Jira 数据,并进行可视化

    1. 准备

    首先,安装 Python 依赖库

    # 安装依赖库
    pip3 install jira
    pip3 install html-table
    pip3 install pyecharts
    pip3 install snapshot_selenium
    
    • 1
    • 2
    • 3
    • 4
    • 5

    其中

    jira 使用 jsql 语法从在项目中获取需要的数据

    html-table 用于生成一个 HTML 格式的表格数据

    pyecharts 和 snapshot_selenium 用于数据可视化

    2. 实战一下

    下面我们通过 7 个步骤来实现上面的功能

    2-1 登录获取客户端连接对象

    from jira import JIRA
    
    class JiraObj(object):
        def __init__(self, bug_style, project_type):
            """
            :param project_name
            :param sprint: 迭代号码
            :param bug_style: BUG状态
            """
            # Jira首页地址
            self.server = 'https://jira.**.team'
    
            # Jira登录账号信息
            self.basic_auth = ('用户名', '密码')
    
            # 创建一个客户端连接信息
            self.jiraClinet = JIRA(server=self.server, basic_auth=self.basic_auth)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2-2 根据项目类型获取看板 id

    ...
            # 获取boards看板
            # 所有看板信息
            boards = [(item.id, item.name) for item in self.jiraClinet.boards()]
            board_id = self.__get_board_id(boards, project_type)
            print("看板id:", board_id)
    ...
        def __get_board_id(self, boards, project_type):
            """
            获取看板id
            :param project_type:
            :return:
            """
            board_id = 1
            for item in boards:
                if (project_type == PROJ_TYPE.Type1 and item[1] == 't1') or (
                        project_type == PROJ_TYPE.Type2 and item[1] == 't2'):
                    board_id = item[0]
                    break
            return board_id
    ..
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2-3 根据看板 id 获取迭代 id 及迭代名称

    ...
     # 获取项目Sprint,让用户进行选择
            sprints = self.jiraClinet.sprints(board_id=board_id)
            for item in sprints:
                if str(sprint_no) in item.name:
                    self.sprint_id = item.id
                    self.sprint_name = item.name
                    print(f"选择Sprint,id:{self.sprint_id},name:{self.sprint_name}")
                    break
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2-4 根据项目名、Bug 类型、迭代 id 组成 jsql 语句,并查询数据

    ...
     def get_bug_status_jsql(self, bug_status: BUG_STATUS):
            """
            通过bug状态,获取jsql
            :param bug_status:
            :return:
            """
            status_jsql = ''
            if bug_status == BUG_STATUS.ALL:
                status_jsql = ' '
            elif bug_status == BUG_STATUS.TO_VERIFY:
                # 待验证(已解决)
                status_jsql = ' AND status = 已解决 '
            elif bug_status == BUG_STATUS.TO_FIXED:
                # 待解决(打开、重新打开、处理中)
                status_jsql = ' AND status in (打开, 重新打开, 处理中) '
            elif bug_status == BUG_STATUS.CLOSED:
                # 关闭
                status_jsql = ' AND status = Closed '
            elif bug_status == BUG_STATUS.TO_FIXED_CONTAIN_DELAY:
                # 待解决(打开、重新打开、处理中、延期处理)
                status_jsql = ' AND status in (打开, 延期处理, 重新打开, 处理中) '
            return status_jsql
    ...
    jql = f'project = {project_name} and issuetype = 故障  {self.get_bug_status_jsql(self.bug_style)} AND Sprint = {self.sprint_id} ORDER BY priority desc, updated DESC'
            print(jql)
            lists = self.get_issue_list(jql)
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    2-5 生成本地 HTML 统计数据

    需要注意的是,使用 a 标签组装的链接不能直接跳转,需要针对数据进行二次替换才能正常进行链接跳转

    from HTMLTable import (
        HTMLTable
    )
    
    ...
     def gen_html_table(self, datas):
            """
            初始化表单样式
            :return:
            """
            table = HTMLTable(caption=f'实时BUG统计【{self.project_name}】,一共{len(datas)}个')
    
            # 表头行
            table.append_header_rows((('ID', '状态', '优先级', '责任人', '终端', 'URL'),))
    
            # 添加数据
            table.append_data_rows(datas)
    
            # 设置样式
            table.caption.set_style({'font-size': '15px'})
    
            # 其他样式设置
            ...
    
            # 替换数据,便于展示href地址
            html = table.to_html().replace("<", "<").replace(">", ">").replace(""", '"')
    
            with open(f"./output/{self.project_name}-bug_{current_time()}.html", 'w', encoding='utf-8') as file:
                file.write(html)
    ...
    # 生成本地文件的数据
    output_tuples = tuple([
                (item.get("key"), item.get("status"), item.get("priority"), item.get('duty'), item.get('end_type'),
                 f'点我查看') for item in lists])
    
    # 生成本地HTML文件
    self.gen_html_table(output_tuples)
    ..
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2-6 数据统计

    首先,这里按 Bug 责任人进行分组,然后按数目进行降序排列

    然后,按 Bug 优先等级进行降序排列

    最后,获取每一个端的 Bug 总数

    ...
            # 2、统计每个人(按数目)
            datas_by_count = {}
            for item in lists:
                datas_by_count[item.get("duty")] = datas_by_count.get(item.get("duty"), 0) + 1
    
            # 降序排序
            datas_by_count = sorted(datas_by_count.items(), key=lambda item: item[1], reverse=True)
    
            # print("按Bug总数排序:", datas_by_count)
    
            # 3、统计每个人(按优先级)
            datas_by_priority = {}
    
            for item in datas_by_count:
                # 责任人
                name = item[0]
                # 5个优先级对应的数目
                counts = self.get_assignee_count(lists, name)
                datas_by_priority[name] = counts
    
            # 排序(按优先级多条件降序排列)
            datas_by_priority = sorted(datas_by_priority.items(),
                                       key=lambda item: (item[1][0], item[1][1], item[1][2], item[1][3]), reverse=True)
    
            # print("按Bug优先级排序:", datas_by_priority)
    
            # 4、根据终端进行统计分类
            keys, values = self.get_end_type_count(lists)
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2-7 可视化

    针对上面的 3 组数据,使用 pyecharts 绘制成柱状图和饼状图

    ...
          def draw_image(self, datas_by_count, datas_by_priority, keys, values):
            """
            绘制图片
            :param values:
            :param keys:
            :param datas_by_count: 按bug总数排序结果
            :param datas_by_priority: 按bug优先级排序结果
            :return:
            """
            # 1、按BUG总数排序绘制
            bar = (
                Bar().set_global_opts(
                    title_opts=opts.TitleOpts(title=f"{self.project_name}", subtitle=f"{self.sprint_name}")))
            bar.add_xaxis([item[0] for item in datas_by_count])
            bar.add_yaxis(f"BUG总数", [item[1] for item in datas_by_count])
    
            # render 会生成本地 HTML 文件,默认会在当前目录生成 render.html 文件
            # 也可以传入路径参数,如 bar.render("mycharts.html")
            # bar.render(path=f'{sprint_name}-BUG总数.html')
            make_snapshot(snapshot, bar.render(), "./output/1.png")
    
            # 2、按优先级排序绘制
            bar2 = (
                # Bar(init_opts=opts.InitOpts(theme=ThemeType.INFOGRAPHIC))
                Bar()
                    .add_xaxis([item[0] for item in datas_by_priority])
                    .add_yaxis(self.__get_priority(BUG_PRIORITY.Highest), [item[1][0] for item in datas_by_priority],
                               color='#6aa84f')
                    .add_yaxis(self.__get_priority(BUG_PRIORITY.High), [item[1][1] for item in datas_by_priority],
                               color='#a2c4c9')
                    .add_yaxis(self.__get_priority(BUG_PRIORITY.Medium), [item[1][2] for item in datas_by_priority],
                               color="#ff9900")
                    .add_yaxis(self.__get_priority(BUG_PRIORITY.Low), [item[1][3] for item in datas_by_priority],
                               color="#ea9999")
                    .add_yaxis(self.__get_priority(BUG_PRIORITY.Lowest), [item[1][4] for item in datas_by_priority],
                               color="#980000")
                    .set_global_opts(
                    title_opts=opts.TitleOpts(title=f"{self.project_name}", subtitle=f"{self.sprint_name}"))
            )
            # bar2.render(path=f'{sprint_name}-BUG优先级.html')
            make_snapshot(snapshot, bar2.render(), "./output/2.png")
    
            # 3、根据终端来绘制饼图
            if len(keys) > 0 and len(values) > 0:
                c = (
                    Pie()
                        .add("", [list(z) for z in zip(keys, values)])
                        .set_global_opts(title_opts=opts.TitleOpts(title="各端BUG分布"))
                        .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
                )
                make_snapshot(snapshot, c.render(), f"./output/{self.project_name}_end.png")
    
            # 4、合并两张图片
            self.concatenate_img(['./output/1.png', './output/2.png'], img_name=f'./output/{self.sprint_name}_bug.png',
                                 axis=1)
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    3. 总结

    通过上面的操作,每次只需要输入项目类型、迭代版本号、要统计的 Bug 类型,就能统计出所需要的数据并绘制成图表

    现在我邀请你进入我们的软件测试学习交流群:746506216】,备注“入群”, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。

    喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!
    在这里插入图片描述

  • 相关阅读:
    Git仓库迁移实操(附批量迁移脚本)
    在Android上JAVA的file.list不完整没有压缩包文件
    elasticSearch的索引库文档的增删改查
    iptables mark 相关用法及使用举例
    7000+字图文并茂解带你深入理解java锁升级的每个细节
    Leetcode算法题练习(一)
    扬尘监测:智能化解决方案让生活更美好
    pandas导入到数据库
    Rstudio必用快捷键
    Vue2 | Vant uploader实现上传文件和图片
  • 原文地址:https://blog.csdn.net/wx17343624830/article/details/125994131