您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
自动化测试实践之脚本模块化
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
自动化测试实践之脚本模块化
自猿其说Tech
2022-10-19
IP归属:未知
18440浏览
测试
### 1 背景 #### 1.1 序言 基于运力平台技术部定位调整,意在打通多运力模式,整合内外部运力资源,建立行业领先的运力服务平台,以技术驱动,实现成本,效率与体验最优,服务京东一体化供应链发展的需要,服务于运力平台部等部门的建设工作;系统定位为京东物流的运力大脑,结合京东物流一体化供应链战略在公路,航空,铁路,多式联运业务线的全面布局、行业发展趋势以及新型技术,建设赋能业务体验、效率、成本最优,行业领先型的综合运力系统。鉴于此,京东业务未来实现一体化供应链其中一个特征要求对外服务重点从“重产品”逐步进阶到“重方案”,因此运力平台通过头部客户定制化需求持续解耦沉淀为行业服务的标准化组件,实现为腰尾部客户提供行业运输解决方案。 #### 1.2 目标 从21年开始到22年的建设规划,运力平台技术部整体围绕着:规划产品,调度产品,执行产品,资源产品,结算产品,管控产品,智能计算中心,生态产品展开。随着运力业务场景八大产品定位,与现运输自动化测试脚本的数量累积和场景维护,故自动化框架如何更快速、更便捷的服务业务测试成为框架设计关键节点,所以当前目标是将用例设计成标准化,组件化,以提高脚本复用性和可维护性。 ### 2 解决路径 #### 2.1 关键思路 ##### 2.1.1 原调用链 调用流程按照每个步骤需要的应用接口依次调用 ![](//img1.jcloudcs.com/developer.jdcloud.com/489f5b81-f7e1-4ffb-8f44-fb9ee1e8fc0d20221019142551.png) ##### 2.1.2 改造思路 提供八大产品能力模块,每个能力模块内部处理调用应用接口逻辑关系 ![](//img1.jcloudcs.com/developer.jdcloud.com/7f2b1566-7d3d-4055-ab6c-86155399703020221019142603.png) #### 2.1.3 改造后调用链 调用链条,从直接调用具体应用接口,到调用八大产品能力模块, 通过测试数据或者测试场景,驱动能力模块内部进行接口分流执行 ![](//img1.jcloudcs.com/developer.jdcloud.com/aedd54de-cfab-4eab-a138-c84c0358cc4520221019143152.png) #### 2.2 策略模式设计用例 ##### 2.2.1 定义 策略模式定义了一系列的算法,将每一组相关的算法封装起各个策略分支,从而将分支相关的代码隐藏起来,并且它们之间可以相互替换。策略模式让算法的变化不会影响到使用算法的客户,希望可以提高程序的可扩展性。 ##### 2.2.2 操作步骤 ![](//img1.jcloudcs.com/developer.jdcloud.com/bee657fa-d1e8-4af4-a10e-062b4e2b80bc20221019143217.png) - 将频繁修改的算法进行抽取,独立为具体的算法类 - 创建抽象基类,实现一个约定的抽象策略方法 - 所有独立的算法类,必须实现基类中的抽象策略接口 - 建立上下类,该类可以动态的对算法进行setter,创建调用具体算法的方法,上下文可通过该方法与具体的策略交互 - 客户端进行调用,传入具体的算法类,上下文动态执行具体的算法任务 #### 2.3 场景驱动执行用例 ##### 2.3.1 操作步骤 结合@pytest.mark.parametrize制造测试场景,利用Executor()执行器读取步骤和测试数据. - 根据case_name获取当前测试方法中有多少测试场景 - 当前用例,根据param_id区分测试场景 - 不同场景配置不同测试数据和执行步骤,步骤即测试方法 - 是否执行测试方法根据方法的首行注释 - 根据场景设置执行测试类/测试方法的初始化数据 ### 3 环境依赖 #### 3.1 框架介绍 框架设计核心模块,包括公共服务层和业务脚本层。 公共服务层基于pytest,集成selenium/appnium和requests,提供Web/APP的UI自动化和API自动化测试能力。同时框架封装pymysql/cx_Oracle读取数据源,logger日志监控服务,Executor场景执行器,apscheduler定时调度器等,便于支撑更加复杂的测试场景。 业务脚本层作为底层支柱,直接利用框架本身公共服务,完成满足业务逻辑用例编写。运行时只需通过公共服务层的统一run.py入口,区分执行范围批量执行脚本。 ![](//img1.jcloudcs.com/developer.jdcloud.com/0cc519d7-303f-46f4-b075-a6f5eb0525a720221019143303.png) ### 4 实践分享 #### 4.1 改造案例概述 根据运输业务同一个流程存在不同场景,如询价服务接上游下发询价单节点,需要区分来源执行不同逻辑,目前设计五个算法能力,根据后期业务不断扩展,还会有更多算法加入进来,这个时候需要考虑一个好的结构对代码进行优化。可能前期大家通过if...elif...else 分支语句就可实现,但在考虑系统的健壮性和可维护性,这里就不能大量使用if分支语句。因为每一种算法能力的代码量极大且算法参数几十个,在随着更多上游接入可能存在十几个甚至更多else分支,很容易顾此失彼,牵一发而动全身。所以,利用策略模式设计一系列算法,再供用例拼装调用,提高代码的可读性和可复用性。 #### 4.2 询价接单接口改造 如原代码结构,根据不同业务来源,写在一个方法里通过if...else...分别组装场景,一旦上游任一系统存在需求变动,当前接单接口调用逻辑需要变动 ```python def receive_enquiry_bill(**kwargs): params=[{}] params[0].update(kwargs) if params[0].get("enquirySource") == 8: pass elif params[0].get("enquiryWay") == 2 and params[0].get("payMode") == 2: pass elif params[0].get("enquiryWay") == 2 and params[0].get("payMode") == 3: pass if params[0].get("enquirySource") == 46: pass if params[0].get("enquirySource") == 20: pass ``` 改造结构: 上下文类 ```python class AlgorithmStrategy(object): def __init__(self, algorithm_name): self.algorithm_name = algorithm_name @property def algorithm(self): return self.algorithm_name @algorithm.setter def algorithm(self, name): self.algorithm_name = name def execute_algorithm(self, params): return self.algorithm_name.execute(params) ``` 算法基类 ```python class CreateEnquiryBillBaseAlgorithm(ABC): # 算法能力基类 @abstractmethod def read_params(self, **kwargs): scenario=kwargs['scenario'] if "scenario" in kwargs and kwargs['scenario'] else 'base' return resource_custom_data[self.__class__.__name__][scenario][0].update(kwargs) @abstractmethod def execute(self, params): return jsf_receive_enquiry_bill(data=json.dumps(params)) ``` 不同算法 ```python class CreateTFCEnquiryBill(CreateEnquiryBillBaseAlgorithm): def read_params(self, **kwargs): params = super().read_params(**kwargs) params[0].update({"businessCode": kwargs['businessCode'] if 'businessCode' in kwargs else f"TJ{laputa_util.date_time_str(fmt='%y%m%d')}{laputa_util.get_random_num(8)}","receiveBeginTime": tms_util.data_time_str(minutes=100),"deliveryBeginTime": tms_util.data_time_str(minutes=180)}) return params def execute(self, params): return super().execute(params) class CreateECLPClodEnquiryBill(CreateEnquiryBillBaseAlgorithm): def read_params(self, **kwargs): # 若当前场景参数与基础参数改动较大建议直接在Yaml里另写Key params = super().read_params(**kwargs) params[0].update({"businessCode": kwargs['businessCode'] if 'businessCode' in kwargs else f"ECO{laputa_util.date_time_str(fmt='%y%m%d')}{laputa_util.get_random_num(8)}","receiveBeginTime": tms_util.data_time_str(minutes=100),"deliveryBeginTime": tms_util.data_time_str(minutes=180)}) return params def execute(self, params): super().execute(params) return jsf_do_assign(data=json.dumps(params)) ``` 算法注入使用 ```python def receive_enquiry_bill(algOne=None, sceOne=None, **kwargs): """ Args: algorithm: 业务类型 scenario: 测试场景:执行步骤,执行数据 Returns: """ if algorithm: # 采用字典形式进行手动注册算法,由python动态查找 st = {"TFC": CreateTFCEnquiryBill(), "ECLP冷链": CreateECLPClodEnquiryBill(), "TC": CreateTCEnquiryBill(),"终端用车": CreateTerminalEnquiryBill()} query_algorithm = st.get(algOne) return query_algorithm.execute(query_algorithm.read_params(scenario=sceOne, **kwargs)) else: pass ``` 当有需求变动,只需修改其一策略规则内部代码,如【分单策略需求】,除运输内部系统TFC下发询价指定个体标签,其他上游没有增加标签下发功能,则只需修改CreateTFCEnquiryBill()代码即可。 #### 4.3 Common用例组装 拼接task客户端方法组成case,利用feature组装测试数据,数据驱动测试方法执行 ```python @pytest.mark.parametrize("params", test_data('test_enquiry_core'), indirect=True) def test_enquiry_core(params): enquiry_code = receive_enquiry_bill_core(**params).get("data") return quote_enquiry_bill_core(enquiry_code=enquiry_code, **params) ``` #### 4.4 场景驱动测试用例 测试方法中的步骤执行器, 可自行配置期望步骤和传入自定义测试数据. laputa_execution.Executor ```python class Executor: """测试用例步骤执行器""" pass ``` Executor.load_steps ```python def load_steps(self, desired_steps: list = None): """加载期望执行的步骤列表. 执行器在运行时仅执行期望步骤, 忽略其他步骤. Args: desired_steps(list): 期望执行的步骤列表 """ pass ``` Executor.execute_step ```python def execute_step(self, func): """Executes test steps defined in `desired_steps` property. Args: func(function): The function step """ pass ``` Executor.extract_data ```python def extract_data(self, custom_data, func): """加载自定义步骤及提取自定义测试数据 Args: custom_data(dict): 数据字典, 包含测试方法名及其对应的期望步骤及数据 func(function): 测试方法对象 Returns: dict: 自定义数据 """ pass ``` 测试数据维护 ```python test_jsf_choose_high_quote: '司机报价成功再取消报价流程': steps: [ '下询价单','司机报价','司机取消报价' ] data: { 'jdAccount': 'hyuu',"enquiryType": 1,"enquirySource": 8,"beginProvinceId": 1,"beginProvinceName": "北京","beginCityId": 1,"beginCityName": "北京","businessType": 11,"requireVehicleType": 4,"receiptFlag": 0,"insuranceFlag": 1,"insuredValue": 20000,"cargoName": "京东货物","cargoVolume": 1,"cargoWeight": 0,"inquirerCode": "dbsxw","inquirerName": "雪薇姐","pathWayFlag": 0,"enquiryWay": 1,"enquiryMode": 1,"loadSwitch": 1,"businessDomain": 1,"distance": 4,"duration": 90000,"pathwayType": 1,"transType": 2,"oilCardRate": 0,"payMode": 2 } '司机报价取消报价再报价流程': steps: [ '下询价单','司机报价','司机取消报价','司机二次报价' ] data: { 'jdAccount': 'hyuu',"enquiryType": 1,"enquirySource": 8,"beginProvinceId": 2,"beginProvinceName": "上海","beginCityId": 2,"beginCityName": "上海","requireVehicleType": 4,"receiptFlag": 0,"insuranceFlag": 1,"insuredValue": 20000,"enquiryMode": 1,"loadSwitch": 1,"businessDomain": 1,"distance": 4,"duration": 90000,"pathwayType": 1,"transType": 2,"oilCardRate": 0,"payMode": 1 } ``` 测试用例编写 ```python @pytest.mark.parametrize("dataf", test_data('test_enquiry_core'), indirect=True) def test_jsf_driver_quote_enquiry_bill(dataf): # region 初始化步骤执行器 executor = Executor() data = executor.extract_data(custom_data, test_jsf_driver_quote_enquiry_bill) # 获取用例执行的基础测试数据 dataf.update(data) def step_receive_enquiry_bill(): """下询价单""" pass enquiry_code = executor.execute_step(step_receive_enquiry_bill) def step_driver_apply_transit_job(): """司机报价""" pass executor.execute_step(step_driver_apply_transit) ``` 4.5 运行报告展示 ![](//img1.jcloudcs.com/developer.jdcloud.com/b4812158-eb58-400b-af5d-6903ed3c8bce20221019160014.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/70f0c328-dcde-4ff9-b115-a524ddd0a94820221019160026.png) ### 5 结论 团队内部通过以上实践在自动化测试过程提高设计能力,减少冗余工作,主要概括以下四点: 1. 复杂逻辑放到每个产品模块内部,提高测试用例的可读性; 2. 减少重复代码,缩短测试用例调用链条; 3. 操作节点清晰,报告展示产品模块执行结果; 4. 形成健全的产品模块,后续用例更多关注场景驱动,而非重复调用。 本文提出的设计用例方案,实现上需基于现有的运输测试框架和积累的测试脚本,但利用策略模式抽出公共能力,关注场景驱动测试执行的解决思路适用于不同测试团队和各种测试平台的实践。 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:刘红妍
原创文章,需联系作者,授权转载
上一篇:Android端APP稳定性测试实践
下一篇:Dive into TensorFlow系列(3)- 揭开Tensor的神秘面纱
相关文章
安全测试之探索windows游戏扫雷
Jmeter压测实战:Jmeter二次开发之JSF采样器实现
Laputa自动化测试框架介绍
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号