运用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秒左右)

原文地址:https://www.cnblogs.com/reconova-56/p/13945282.html