BytePush之基于质量运营的项目流程迭代工具篇

avatar
研发 @字节跳动

作者:大力智能技术团队-QA bot girl BytePush:致力于扎根业务场景,触达问题解决者,达成智能预警

背景

保证质量的前提下快速价值交付一直是我们大力智能QA团队测试改进不断追求的目标,高效能的价值交付是敏捷开发,持续交付等不断改进追求的方向。

现阶段字节各类质量保障工具平台应运而生,测试左移越来越常态化,eg:代码覆盖率、分支巡检、性能测试等等都左移到RD自测环节,提测后,由QA进行风险评估。对于风险的评估当前的现状是:RD/QA共同制定准入准出标准,在每次准入准出节点由QA收集各项指标是否达标,为了尽快达标,相应的RD、QA会去提醒催促,在每个迭代需要耗费1个QA和1个RD,2人力全程跟进。

为解决上述痛点,我们QA团队搭建流程管控中心+质效模型,贯穿在开发测试环节,通过合理的流程设置,适配多种开发模式。

目标

作为风险管控(流程、人、系统),贯穿产研全生命周期,结合衡量标准通过各类卡片发现并暴露质量和流程问题、通过质量运营的方式完成数据收敛,持续分析、升级标准,循环往复固化经验,形成闭环。

难点

  • 当前数据源平台均支持业务自定义配置的情况下,如何做成通用化去支持业务自定义卡片推送内容
  • 各业务达成推送策略、推送对象、推送方式要求均不统一的情况下,如何达成业务自定义配置推送策略

痛点

代码层⾯为最⼤的痛点:

  • 对机器⼈的初印象:调调open api,解析解析,推送个卡⽚就完事了
  • 结果:
    • 只能满⾜当前业务,⽆法⽀持多元化、定制化的需求,⽆法及时满⾜业务变化的需求

    • 当开始⽀持多⽅向业务时,产⽣⼤量冗余代码,需求变更困难,改动难度⼤

    • 基于初印象,由于前两点导致很多⼈兴冲冲的来写卡⽚,最后溜之⼤吉

为了解决上述痛点:

改造数据库使⽤⽅式
  • 原来:频繁的写sql
def search_branch_message(branch_name, rid, rd_name, version, src):
    sql = "SELECT * from branch_message WHERE branch_name = %s and rid = %s rd_name = %s and version = %s and src = %s"
    args = (branch_name, rid, rd_name, version, src)
    rows = db_base.search_mysql_args(sql, args)
    return len(rows)
  • 当前:BytePush结合aiomysql封装了⼀层ORM,只需要按照规则传递参数,调⽤⽅法即可
'''
增删改
增删改传递参数:
mode:必填,"add" or "update" or "delete"
model:必填,数据库表名
data:必填,要操做的数据
'''
def insert_by_params(mode=None, model=None, data=None):
    s = DBORM.Select()
    task = DBORM.insert_task_pool(mode=mode, models=model, data=data)
    result = s.insert_many(task)
    s.close()
    return result
'''
查询  
查询传递参数:
need_typee:必填,表格名称
dimension_type:选填,维度信息(人维度、需求维度、版本维度)
dimension_param:选填,查询参数,同时也表示要在卡片上显示的内容对应的字段 
'''
def get_search_result(self, need_type=None, dimension_type=None, dimension_param=None):
    em = exception_message(mode="group")
    tasks = []
    for need_type_ in need_type.keys():
        try:
            if hasattr(em, need_type_):
                # 获取到方法
                exp_message = getattr(em, need_type_)
                # 输入参数,返回
                if dimension_type and dimension_type in ["rid", "manager"]:
                    self.select_params[dimension_type] = dimension_param
                tasks += exp_message(self.select_params)
        except AttributeError:
            print('没有这个项目')
    result = self.s.select_many(tasks)
    return result
改造卡⽚⽣成⽅式
  • 原来:根据需要解析,⼀个获取bug的接⼝会被反复使⽤在多个卡⽚中
  • 现在:建⽴指标⼯⼚,举例如下:
    • ⽣成⼀个版本合码卡⽚,只需要从指标⼯⼚中挑选指标:meego缺陷:bug_message,slardar内存泄漏:mom_message,slardar各类crash:crash_message进⾏组装。
    • ⽣成⼀个open_bug超时卡⽚,只需要从指标⼯⼚中挑选指标:meego缺陷:bug_message
# 从指标工厂挑选指标:dict中的key对应指标工厂中的方法名,value即为需要显示的字段
need_exceptions_rid = {
    "h_owner_schedule_notice": ["node_name"],
    "bug_message": ["priority_name"],  # 表示根据优先级聚合
    "crash_message": ["crash_type"],  # 表示根据类型聚合
    "mom_message": ["issue_level"],  # 表示根据优先级聚合
}

# 指标工厂
class exception_message(Mode_Message):
    def __init__(self, mode):
        Mode_Message.__init__(self, mode)
        self.mode = mode

    def bug_message(self, args=None):
        result = self.mode_message("bug_message", args)
        return result

    def crash_message(self, args=None):
        result = self.mode_message("crash_message", args)
        return result

    def mom_message(self, args=None):
        result = self.mode_message("mom_message", args)
        return result

    # 实际参数很多:需求id,mr_id等
    def code_coverage_message(self, args=None):
        result = self.mode_message("code_coverage_message", args)
        return result
    # 等等
    
class Mode_Message(Base_Message):
    def __init__(self, mode=None):
        Base_Message.__init__(self)
        self.mode = mode

    def mode_message(self, model, args):
        if self.mode == "single":
            result = self.just_show_search_by_params(model, args)
            return result
        elif self.mode == "group":
            task = self.many_together_contain(model, args)
            return task
    
 class Base_Message(object):
    @staticmethod
    def just_show_search_by_params(model=None, message_select_args=None):
        s = DBORM.Select()
        task = DBORM.task_pool(models=[model], message_select_args=message_select_args)
        result = s.select_many(task)
        return result

    @staticmethod
    def many_together_contain(model=None, message_select_args=None):
        task = DBORM.task_pool(models=[model], message_select_args=message_select_args)
        return task
⽀持⾃定义配置参数
  • 原来:根据需要解析,代码冗余量巨⼤
  • 现在:建⽴字段⼯⼚
    • 对于通⽤字段只需要传递需要的字段的key:value即可
    • 对于⽤⼾⾃定义配置字段,传递字段对应的dict即可
# condition_params_dict:通用字段
# defined_condition_params: 自定义字段
class MeegoPayload(CommonInterface):
    def __init__(self, src, condition_params_dict, SearchType, defined_condition_params=None):
        CommonInterface.__init__(self, src)
        self.SearchType = SearchType
        self.defined_condition_params = defined_condition_params
        self.condition_params = condition_params_dict.keys()
        if "planning_version" in self.condition_params:
            self.planning_version_value_list = condition_params_dict["planning_version"]  # 需求计划版本
        if "actual_online_version" in self.condition_params:
            self.actual_online_version_value_list = condition_params_dict["actual_online_version"]  # 实际上车版本
        if "business" in self.condition_params:
            self.bussiness_value_list = condition_params_dict["business"]  # 拉取配置好的业务
        if "stage" in self.condition_params:
            self.view_value_list = condition_params_dict["stage"]  # 配置好视图
        if "work_item_status" in self.condition_params:
            self.state_value_list = condition_params_dict["work_item_status"]  # 缺陷状态
        if "resolve_version" in self.condition_params:
            self.resolve_version_value_list = condition_params_dict["resolve_version"]  # 缺陷解决版本
        if "linked_story" in self.condition_params:
            self.linked_story_value_list = condition_params_dict["linked_story"]  # 缺陷关联需求
        if "priority" in self.condition_params:
            self.priority_value_list = condition_params_dict["priority"]  # 缺陷优先级
        if "discovery_version" in self.condition_params:
            self.discovery_version_value_list = condition_params_dict["discovery_version"]  # 缺陷发现版本
实现壳⼯程
  • 上层:获取配置,公共的配置参数、版本、业务线
  • 中层:数据采集,各类原⼦数据的采集
  • 下层:数据消费,推送的卡⽚维度
'''
统一的卡片流程
'''
def process_slave(self, start_time, card_config):

    # ---获取配置---
    need_params = card_config["need_params"]
    trigger_mode = card_config["trigger_mode"]
    send_mode = card_config["send_mode"]
    params = {"stage": ["all_story"]}
    if self.mode not in trigger_mode:
        print("配置中不支持这个触发方式")
        return
        
    # 这里拿到的是某个类型的方法的形参
    need_func, params_process, need_type = self.process_master(need_params)
    
    # ---数据采集---
    version = self.get_version_by_config()
    params[need_params["version_type"]] = self.mv.get_work_items_version(version)
    if self.business and len(self.business) == 0:
        get_bussinesses = self.mv.get_bussinesses()
        params["business"] = get_bussinesses
        self.insert_gather_result(need_func, params_process, params)
    else:
        params["business"] = self.business
        self.insert_gather_result(need_func, params_process, params)

    # ---数据消费---
    # 数据消费查询条件
    select_params_1 = {
        "task_id": start_time
    }
    # 推送list到群
    if "chat" in send_mode:
        # 推送给需求list
        self.list_to_chat(select_params_1, card_config, need_type)
    # 推送给个人
    if "single" in send_mode:
        self.send_person_chat(select_params_1, card_config, need_type)

方案

全流程图

第⼀阶段(⾃动化)

  • ⽬标:
    • ⾃动识别项⽬形态、当前版本、当前阶段
    • ⾃动识别流程质量现状
    • 建⽴初始规则模型,即⼈事先编好了规则,⼯具依照既定的规则作出判断
  • 达成路径:

第⼆阶段(平台化)

  • ⽬标:
    • 上层策略⼤盘:提供过程指标和基准值,通过绑定的各类卡⽚发现并暴露存在的⻛险点
    • 下层策略⼤盘:各业务配置各类卡⽚推送策略(推送时间、推送⽅式、推送对象)

第三阶段(智能化)

  • ⽬标:根据业务现状,⾃动预测⻛险
    • 达成⾃动根据项⽬形态制定标准阈值
    • 达成⾃动根据版本需求数和产研估时,给出⼈⼒配⽐
    • 达成⾃动识别⻛险(也即是否能进⼊下⼀环节、阶段、版本)
  • 达成路径:
    • 统计每个版本数据,⼈⼯校准
    • 规则不再定死,也不是⼈给定,⽽是通过数据不停地学习,当规则模型积累了⾜够的数据量时,引⼊模型算法,进⾏智能化判断

技术架构图

落地策略

  • 推送卡⽚
    • 普通推送、加急推送
    • 个⼈推送、群推送、leader推送
  • ⽣成质量报告
    • 周报、⽉报、导⼊度量平台

效果评估

持续分析核⼼数据,输出质量报告,形成【流程规划-制定-检查-改善提升】的闭环

收益

  • ⼈⼒及指标收益
    • 当前BytePush已接⼊项⽬16+,每个项⽬节省0.5⼈⼒开发及维护机器⼈卡⽚
    • 当前BytePush已接⼊项⽬16+,每个项⽬节省1-2全⼈⼒跟进业务迭代
    • 排期、负责⼈填写⽐例⾼达100%、流转及时率⾼达95%,为measure效能数据校准赋能
    • bug及时解决⽐例环⽐上升10%
    • 上⻋⽇及时上⻋⽐例环⽐上升50%
    • 整体crash< 1‰ 达标,及时解决率环⽐上升50%
    • 上⻋⽇需求质量检查⽆需QA跟进合码情况,极⼤的是释放了QA同学
  • 使⽤频次
    • 排期提醒、流转提醒、负责⼈提醒卡⽚,⽇均使⽤32次
    • open_bug超时卡⽚⽇均使⽤32次
    • 需求执⾏进展⽇均使⽤次数10次
    • 需求执⾏进展上⻋前后⽇均使⽤次数上1000次
    • 灰度监测卡⽚⽇均使⽤次数16次
    • 发版数据卡⽚⽇均使⽤次数16次

基于模版卡⽚的最佳落地实践

以⼤⼒智能学习灯为例

贯穿产研流程,在关键节点给予提醒,持续跟踪数据,不断完成数据收敛

  • 流程:
    • 每天定时向个⼈和群推送排期提醒、完成提醒、负责⼈填写提醒
    • 研发期间每天凌晨定时rebase
    • 每天早上推送各业务需求执⾏进展
    • feature分⽀提交MR时触发pipline中的需求质量检查
  • bug数据:
    • 在上⻋⽇前2天到回归阶段,定时推送超时未解决bug(群list,个⼈加急)
    • 在上⻋⽇前2天开始到上⻋⽇当天,每天早晚定时推送feature合码卡⽚、未合码分⽀列表
    • 上⻋⽇后⼀天到灰度当天,每天早晚推送版本合码卡⽚
    • 灰度期间,每天早晚推送灰度监测卡⽚
    • 版本期间,每天早晚推送线上监测卡⽚
  • 质量报告:
    • 持续迭代、反复打磨标准

适⽤范围

⽬前针对学习灯的最佳实践,还⽀持其他项⽬版本形态:

  • 班⻋制
  • 版本制
  • 不跟版制

后续规划

  • 制定各类质量运营模版,例如:测试模型之效能篇等等
  • 开放壳⼯程,提供⾃定义采集和⾃定义输出内容功能
  • 利⽤机器学习算法和历史数据,达成业务智能预警

正在热招

掘金尾部官号.png