作者:大力智能技术团队-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合码卡⽚、未合码分⽀列表
- 上⻋⽇后⼀天到灰度当天,每天早晚推送版本合码卡⽚
- 灰度期间,每天早晚推送灰度监测卡⽚
- 版本期间,每天早晚推送线上监测卡⽚
- 质量报告:
- 持续迭代、反复打磨标准
适⽤范围
⽬前针对学习灯的最佳实践,还⽀持其他项⽬版本形态:
- 班⻋制
- 版本制
- 不跟版制
后续规划
- 制定各类质量运营模版,例如:测试模型之效能篇等等
- 开放壳⼯程,提供⾃定义采集和⾃定义输出内容功能
- 利⽤机器学习算法和历史数据,达成业务智能预警