Appium+python+excel+unittest+ddt+多设备进行APP自动化测试

一、环境搭建

Appium+Python3

python第三方库:Appium-Python-Client、ddt、xlrd、xlutils

二、项目结构设计

Appium+python+excel+unittest+ddt+多设备进行APP自动化测试_第1张图片
image

三、代码实现

Appium+python+excel+unittest+ddt+多设备进行APP自动化测试_第2张图片
image

四、具体分析

1、Appium的server封装(多设备)

首先检测电脑已连接设备信息,保存设备信息,Server按设备数量命令行启动多个服务,以下为server类部分代码:

class Server(object):
    def __init__(self):
        self.project_dir = Tools.get_project_dir()
        device_config_path = self.project_dir + '/config/device.ini'
        self.cf = ConfigOperate(device_config_path)

    def main(self, device_list=None):
        """
        多进程启动appium server主程序
        :param device_list: 设备列表
        :return:
        """
        if device_list is None:  # 如果设备列表没有传进来,则自动去当前系统里去获取
            device_list = self._get_android_devices()  # 获取安卓设备列表
        if device_list:
            appium_port_list = self._create_port_list(4700, len(device_list))
            bootstrap_port_list = self._create_port_list(4900, len(device_list))
            self.cf.clean()  # 先清空device配置文件
            for i in range(len(device_list)):
                port = appium_port_list[i]
                bport = bootstrap_port_list[i]
                device = device_list[i]
                server_log_path = os.path.join(Tools.get_project_dir(), 'logs', 'servers', '%s_%s.log' % (device, Tools.get_now_date('%Y-%m-%d_%H:%M:%S')))
                self.cf.write_config(device, {'port': port, 'bport': bport, 'server_log_path': server_log_path})  # 把设备信息和端口信息写入配置文件
                t = multiprocessing.Process(target=self.start_server, args=(port, bport, device, server_log_path))
                t.daemon = True  # 设置为守护进程,且一定要在t.start()前设置,设置t为守护进程,禁止t创建子进程,并且父进程代码执行结束,t即终止运行
                t.start()
        else:
            raise ValueError('当前没有任何连接设备')

    def start_server(self, port, bport, device, log_path=None):
        """
        创建子进程运行appium的server
        :param port: 端口
        :param bport: b端口
        :param device:  设备名
        :param log_path: log路径
        """
        if log_path:
            command = 'appium -p {port} --bootstrap-port {bport} -U {device} --log {log_path} ' \
                      '--local-timezone --session-override'.format(
                port=port, bport=bport, device=device, log_path=log_path)
        else:
            command = 'appium -p {port} --bootstrap-port {bport} -U {device} ' \
                      '--local-timezone --session-override'.format(
                port=port, bport=bport, device=device)

        result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        if len(result.stderr) > 0:
            message = '--------启动port端口为%s,bport端口为%s,设备uuid为%s的appium服务失败!!!---------\n' \
                      'error_message:%s' % (port, bport, device, result.stderr)
        else:
            message = '--------启动port端口为%s,bport端口为%s,设备uuid为%s的appium服务成功!!!---------\n' \
                      % (port, bport, device)
        Logging().info(message)

2、Appium操作封装

为了方便,把appium的基础操作封装成一个个的关键字,输入、点击、滑动等等:

class KeyWords:
    # 初始化
    def __init__(self, driver):
        self.driver = driver
        self._element_finder = ElementFinder()
        self.log = logging.getLogger('main.keywords')  # 继承日志设置

    def click_element(self, locator):
        """
        封装点击元素操作
        :param locator: 自定义元素
        :return:
        """
        self._element_finder.find(self.driver, locator).click()

    def input_text(self, locator, text):
        """
        输入文本
        :param locator: 元素
        :param text: 文本
        :return:
        """
        self._element_finder.find(self.driver, locator).send_keys(text)

    def swipe_left(self, duration=1000):
        """
        封装滑动方法(按比例往左滑动)
        :param duration: 时间
        :return:
        """
        try:
            self._swipe_by_percent(80, 50, 20, 50, duration)
        except Exception as e:
            self.log.error('滑动屏幕出错'.format(e))

3、excel基础操作封装

excel的读取、保存等操作用到了xlrd和xlutils库,主要实现整个excel数据的读取、获取单元格内容、获取一行数据和数据写入等基础操作

class ExcelOperate(object):
    def __init__(self, file_path, sheet_name):
        self.file_path = file_path
        self.sheet_name = sheet_name
        self.excel = self._get_all_excel()
        self.sheet_data = self._get_sheet_data(sheet_name)

    def _get_all_excel(self):
        """
        获取整个excel数据
        :return:
        """
        excel = xlrd.open_workbook(self.file_path)
        return excel

    def _get_sheet_data(self, sheet_name=0):
        """
        获取excel中的某个表数据
        :param sheet_name: 表名,可以是下标也可以是名称
        :return:
        """
        if isinstance(sheet_name, int):
            sheet = self.excel.sheet_by_index(sheet_name)
        else:
            sheet = self.excel.sheet_by_name(sheet_name)
        return sheet

    def get_rows(self):
        """
        获取excel行数
        :return:
        """

        return self.sheet_data.nrows

    def get_cell_value(self, row, column):
        """
        获取单元格内容
        :param row:  行号
        :param column: 列号
        :return:
        """
        data = self.sheet_data.cell(row, column).value
        return data

    def get_row_values(self, row):
        """
        获取一行的数据
        :param row:
        """
        return self.sheet_data.row_values(row)

    def write_value(self, row, column, value):
        """
        数据写入
        :param row: 行
        :param column: 列
        :param value: 数据
        :return:
        """
        read_value = self.excel
        write_data = copy(read_value)
        write_save = write_data.get_sheet(self.sheet_name)
        write_save.write(row, column, value)
        write_data.save(self.file_path)

4、Excel和Appium操作结合

excel和appium的操作封装好了,那两者结合就可以实现excel读取数据,appium按读取数据执行用例的目的,当然这里Excel需要结合自己需求来设计。

4.1 Excel内容设计

Appium+python+excel+unittest+ddt+多设备进行APP自动化测试_第3张图片

EXCEL里第一行里是表头,表头是固定的。执行用例是按页面+步骤+元素+操作值的组合判断来执行相应的操作,比如页面=首页,步骤=点击,元素=我的头像按钮的组合,那操作就是点击首页上的我的头像按钮。

4.2部分代码实现如下:

class ExcelHandle(object):
    def __init__(self, driver, data):
        self.driver = driver
        self.key_words = KeyWords(driver)
        self.cf = ConfigOperate(APP_INFO_CONFIG_PATH)
        self.log = logging.getLogger('main.' + __name__)

        self.handle_page = data.get('页面', None)
        self.handle_step = data.get('步骤', None)
        self.element_key = data.get('元素', None)
        self.handle_value = data.get('操作值', None)
        self.description = data.get('描述', None)

        self.step = {  # 执行步骤
            '输入': self._step_input,
            '点击': self._step_click,
            '左滑': self._step_swipe_left,
            '右滑': self._step_swipe_right,
            '上滑': self._step_swipe_up,
            '下滑': self._step_swipe_down,
            '验证元素存在': self._expect_has_element,
            '验证元素不存在': self._expect_has_not_element,
            '验证toast': self._get_toast,
            '等待': self._wait_second,
        }

    def run(self):
        self.log.info('开始执行用例: 页面-{ym},步骤-{bz},元素-{ys}{isczz}'.format(
            ym=self.handle_page,
            bz=self.handle_step,
            ys=self.element_key,
            isczz=',操作值-%s' % self.handle_value if self.handle_value else '')
        )
        step_fun = self.step.get(self.handle_step, None)  # 获取操作函数名
        if step_fun is None:
            message = '没有找到步骤为"%s"的操作代码!' % self.handle_step
            self.log.error(message)
            raise ValueError(message)
        return step_fun()

这样EXCEL里每次读取一行数据,appium就会执行相应操作,用例就可以按照自定的规则写在excel里就行,方便管理和维护。

4.3元素信息保存和读取

appium定位元素是按照id、xpath、android等方式来定位具体元素的,故在保存元素的时候,需把定位方式和元素一起保存,后再分割处理.


Appium+python+excel+unittest+ddt+多设备进行APP自动化测试_第4张图片

5、unittest+ddt启动测试

5.1 unittest类重构传参

因为是多设备运行,所以多进程运行时需要传入不同的设备信息,所以重写unittest.TestCase的构造函数

    def __init__(self, methodName='runTest', param=None):
        super(CaseTests, self).__init__(methodName)
        global device
        device = param

5.2 运用ddt库数据驱动,批量执行用例

 @ddt.data(*ExcelData(EXCEL_BASE_PATH, sheet_name=0).dict_data())
 @screenshot
 def test_excel(self, data):
     run_result = ExcelHandle(self.driver, data).run()
     # 把结果保存到result文件夹里
     excel_op = ExcelData(os.path.join(EXCEL_RESULT_DIR, 'case_result_%s.xls' % self.now), sheet_name=0)
     excel_op.result_write(data['rowNum'], run_result)
     self.assertEqual(run_result, 'PASS')

5.3 运行

启动Appium Server类,再启动多进程分别为各个设备执行用例。

if __name__ == '__main__':
    cf = ConfigOperate(DEVICE_LIST_CONFIG_PATH)
    section_list = cf.get_sections()
    server = Server()
    # 先尝试停掉上次运行后的僵尸appium服务
    for section in section_list:
        port = cf.get_value(section, 'port')
        bport = cf.get_value(section, 'bport')
        for p in [port, bport]:
            server.kill_server(p)
            time.sleep(2)

    server.main()  # 启动Appium服务
    time.sleep(10)  # 加个时间等待服务先启动

    process = []
    for device in section_list:
        p = multiprocessing.Process(target=start, args=(device,))
        p.start()
        process.append(p)
    for p in process:
        p.join()  # 等待所有进程执行完毕

五、其他

主要的思路就是这些,后面就是丰富关键字库,以及提高稳定性和错误兼容度。另外,现在只做了Android的支持,后面需考虑同时运行IOS和Android设备的兼容。

你可能感兴趣的:(Appium+python+excel+unittest+ddt+多设备进行APP自动化测试)