运用tenacity库来提高自动化用例的稳定性
一. 案例说明
1.1 整体业务场景说明
1) 设备端为一台智能车载设备,有固定IP,设备开机后有WIFI功能
2) 设备端的开关状态可以通过继电器的开关状态来控制,本案例中使用python来控制继电器开关状态
3) 设备操作接口只有在连接上设备WIFI环境下才能访问
4) 云端接口可在公网环境下访问,但是需要注意的是,端云数据交互时会存在着明显的数据延时现象(业务上合理)
1.2 本文想讨论的用例场景
1)设备端操作(步骤1):通过python脚本开启继电器开关,进而完成设备启动
2)设备端验证(步骤2):设备启动成功后,会自动开启设备wifi,校验项为本地是否能成功连接到设备wifi
3)云端验证(步骤3):访问云端的查看设备信息接口,校验项为接口信息中是否显示设备为在线状态
1.3 用例场景的稳定性分析
1) 上述步骤2中,搜索设备端的wifi, 可能需要多次搜索。连接设备端wifi时,也可能需要多次连接
2) 上诉步骤3中,因为端云数据交互过程中,存在有数据延时同步的现象。故需要通过反复查询的方式来做校验
1.4 期望达到的稳定性效果
1)在设备启动后,期望本地可以尽可能快的连接上设备wifi, 不期望去死等一个可以保证设备一定能成功连接到wifi的时间
2)在设备启动后,期望在5min内,尽可能快的完成云端设备信息接口的校验,不期望死等5min后再去做接口信息校验
二. 运用tenacity库
2.1 编写连接wifi的函数
# -*- coding: utf-8 -*- # @Time : 2020/11/22 12:32 # @Author : chinablue # @File : wifi_helper.py import logging import pywifi from pywifi import const from tenacity import retry from tenacity import Retrying, stop_after_attempt, stop_after_delay, wait_fixed from tenacity import retry_if_exception_type, before_sleep_log logger = logging.getLogger(__name__) class WifiNotFoundException(Exception): """ 如果wifi名称搜索不到, 则抛出此异常 """ pass class WifiConnectException(Exception): """ 如果wifi无法连接成功, 则抛出此异常 """ pass def my_before_sleep(retry_state): logger.error(f"执行函数: {retry_state.fn}, 重试次数: {retry_state.attempt_number}, 重试结果: {retry_state.outcome}") class WifiHelper(): """ 连接wifi """ def __init__(self, wifi_name, wifi_passwd): self.wifi_name = wifi_name self.wifi_passwd = wifi_passwd # 创建一个wifi对象 self.wifi = pywifi.PyWiFi() # 获取无线网卡 self.itf = self.wifi.interfaces()[0] @retry( retry=retry_if_exception_type(WifiNotFoundException), # 重试条件 wait=wait_fixed(1), # 重试间隔 stop=stop_after_attempt(5) | stop_after_delay(5), # 停止重试条件 reraise=True, # 重试后如果再抛异常, 抛出的是原生异常 before_sleep=before_sleep_log(logger, logging.WARNING) ) def search_wifi(self) -> bool: """ 搜索wifi名字是否存在 :return: """ # 扫描wifi,并获取[wifi列表] self.itf.scan() wifi_list = self.itf.scan_results() # 判断[wifi名字]是否在[wifi列表]中 if self.wifi_name in [wifi.ssid for wifi in wifi_list]: return True else: raise WifiNotFoundException() def is_connect_success(self) -> bool: """ 判断wifi是否已连接成功 :return: """ if self.itf.status() == const.IFACE_CONNECTED: logger.info("wifi连接成功") return True else: raise WifiConnectException() def conn_wifi(self, retry_interval=1, retry_counts=20, timeout=30) -> None: """ 填写配置信息, 并连接wifi :param retry_interval: 重试间隔 :param retry_counts: 重试次数 :param timeout: 超时时间 :return: """ if self.search_wifi(): # 端口网卡连接 self.itf.disconnect() # 删除配置文件 self.itf.remove_all_network_profiles() # 加载配置文件 profile = pywifi.Profile() # 配置文件 profile.auth = const.AUTH_ALG_OPEN # 需要密码 profile.akm.append(const.AKM_TYPE_WPA2PSK) # 加密类型 profile.cipher = const.CIPHER_TYPE_CCMP # 加密单元 profile.ssid = self.wifi_name # wifi名称 profile.key = self.wifi_passwd # wifi密码 tmp_profile = self.itf.add_network_profile(profile) # 连接wifi self.itf.connect(tmp_profile) r = Retrying( retry=retry_if_exception_type(WifiConnectException), # 重试条件 wait=wait_fixed(retry_interval), # 重试间隔 stop=stop_after_attempt(retry_counts) | stop_after_delay(timeout), # 停止重试条件 reraise=True, # 重试后如果再抛异常, 抛出的是原生异常 before_sleep=before_sleep_log(logger, logging.WARNING) ) try: r(r, self.is_connect_success) except Exception as e: raise Exception(f"wifi连接失败, wifi名称: {self.wifi_name}, wifi密码: {self.wifi_passwd}, 异常信息: {e}") finally: pass
注意事项:
1)本示例中有两处运用了tenacity库的重试功能,分别是对search_wifi函数进行重试和对conn_wifi函数中的部分代码块进行重试
2)本示例中的重试条件是由指定的异常触发的,所以示例中先自定义了两个异常:WifiNotFoundException,WifiConnectException
3)搜索wifi的重试逻辑说明:最大重试次数为5次,最大执行时间为5秒,重试间隔为1s
4)连接wifi的重试逻辑说明:最大重试次数为20次,最大执行时间为30秒,重试间隔为1s
2.2 编写反复查询接口的校验函数
# -*- coding: utf-8 -*- # @Time : 2020/11/22 12:32 # @Author : chinablue # @File : validate.py from tenacity import retry, retry_if_result, wait_fixed, stop_after_attempt, stop_after_delay, before_sleep_log def get_vehicle_info(device_id): """ 业务接口:查询云端设备信息的接口,一般通过requests库来对业务接口进行封装 :param device_id: :return: """ pass def is_onlineStatus(value): # 假如提取的信息我们用变量onlineStatusValue表示 onlineStatusValue = "value为接口返回的json信息,可以通过objectpath库来提取value中的信息" if onlineStatusValue == "离线": return 1 else: return None @retry( retry=retry_if_result(is_onlineStatus), wait=wait_fixed(2), # 重试间隔 stop=stop_after_attempt(150) | stop_after_delay(300), # 停止重试条件 reraise=True, # 重试后如果再抛异常, 抛出的是原生异常 ) def validate_device_online(device_id): return get_vehicle_info(device_id=device_id)
注意事项:
1)本示例中的重试条件是由is_onlineStatus函数的返回值决定,在这个函数中可以对接口的返回信息进行必要的逻辑处理
2)接口验证的重试逻辑说明:最大重试次数为150次,最大执行时间为300秒,重试间隔为2s
3)本示例中,经多次执行验证发现,如果接口验证能通过时,验证时间在70s左右(意味着业务上端云数据的同步需要70秒左右)