• 【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路


    #1024程序员节|征文#

    在这里插入图片描述

    本文的重点是讲解搭建的思路,非完整源码:
    1、先把思路整理出来,后续会进行源码整理;
    2、思路主要是从基本的实现目的、框架设计、环境依赖、框架的主要组成等方面展开;
    3、适用对象主要是刚接触app自动化测试的同学;
    4、有不明白的可以相互讨论和学习哈。

    1 实现目的和需求

    1.1 实现目的

    • 模拟用户(鼠标、键盘)操作,达到快速、重复执行测试用例;
    • 便于回归测试,快速覆盖主线用例或功能;
    • 线上或线下巡检测试,结合持续集成,及时发现运行环境存在的问题;
    • 提升个人自动化测试技术能力,为业务提供强有力的测试手段。

    1.2 功能需求

    • 基于Unittest,封装、调用和组织所有的测试用例,进行批量或指定用例运行;
    • 支持邮件服务,可添加任意团队成员邮箱,及时通知团队成员自动化运行结果;
    • 支持log日志,保存运行过程所有或需要记录的数据;
    • 支持HTML测试报告,直观展示测试结果和数据;
    • 支持用例设计和测试结果分离,便于数据管理;
    • 支持用户登录封装,后续所有的用例登录公用一个方法;
    • 支持任意修改Beautifulreport,可定制测试报告模板;
    • 支持测试报告多语言(英文和中文);
    • 支持截图功能;
    • 支持Jenkins持续集成。

    2 框架设计说明

    2.1 需求分析

    功能说明
    使用Unittest框架开源自动化测试框架,直接使用
    批量或指定用例运行Unittest框架可支持此功能
    log日志使用Python的logging库即可
    生成HTML测试报告使用BeautifulReport模块可实现此功能
    用例设计和结果分离PO模式
    用户登录封装直接把登录功能模块化,使用Unittest框架中的setup,teardown即可
    定制测试报告模板使用BeautifulReport模块
    报告多语言使用BeautifulReport模块
    截图功能使用UIAutomation的CaptureToImage方法

    2.2 技术栈

    技术版本及说明
    PythonV3.x(本文为3.13.0)===编程语言支撑
    Appium控件的识别、定位及操作
    BeautifulReport生成Html测试报告
    LoggingPython自带===生成log日志
    UnittestPython自带===自动化测试框架
    SmtplibPython自带===邮件服务
    emailPython自带===邮件服务
    osPython自带===系统模块
    PyCharmCommunity 2020.2汉化版
    操作系统Windows10旗舰版64位
    其它后续补充

    2.3 框架设计

    在这里插入图片描述

    3 环境依赖说明

    因内容较多,这里不一 一说明详细的安装步骤,可以自行安装,需要安装的环境如下:

    3.1 Python安装

    • 根据自己需要,本文使用3.13.0版本;
    • 注意在安装过程中,勾选pip以及环境变量自动添加到系统中;
    • 安装完如下;
      在这里插入图片描述

    3.2 Pycharm安装

    • 自行安装,安装完下载语言包汉化即可;
    • 界面如下:
      在这里插入图片描述

    3.3 Appium-Inspector

    • 直接网上搜索Appium-Inspector网页版;
    • 直接打开如下:
      在这里插入图片描述
    • Desired Capabilities配置的一些参数,可以参考如下:
      在这里插入图片描述

    3.4 JDK安装配置

    • 下载需要的JDK,选择合适的操作系统版本即可;
      在这里插入图片描述
    • 记得配置环境变量:

    新建系统环境变量JAVA_HOME;
    在这里插入图片描述

    编辑系统变量Path:
    Windows10 电脑,新建变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
    Windows7 电脑,在变量值最后输入%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;注意用;号分隔

    新建系统变量CLASSPATH变量:
    变量名:CLASSPATH
    变量值:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar

    • 验证下java环境,比如在cmd中输入java,如下就表示正常安装:
      在这里插入图片描述

    3.5 SDK下载

    • 选择合适的SDK下载,如下:
      在这里插入图片描述
    • 下载后解压到指定目录,比如:
      在这里插入图片描述
    • 将SDK的platform-tools、根目录、tools配置到系统环境变量path中(根据自己的路径来设置):
    D:\android-sdk-windows\platform-tools
    D:\android-sdk-windows
    D:\android-sdk-windows\tools
    

    3.6 配置Android环境

    • 新建系统环境变量ANDROID_HOME值为SDK根目录;
    ANDROID_HOME
    D:\android-sdk-windows
    
    • 新建系统环境变量ANDROID_PATH值为SDK的platform-tools目录:
    ANDROID_PATH
    D:\android-sdk-windows\platform-tools
    

    3.7 安装NodeJs

    • 安装这个只要是用它来安装appium以及验证后续安装的Appium是否成功;
    • 下载后直接双击运行即可node-v20.10.0-x64.msi;
    • 安装完在命令行执行npm看到以下界面则表示node安装成功了:
      在这里插入图片描述

    3.8 安装配置Appium

    • 使用NPM安装:
    npm i --location=global appium
    
    • 命令行输入appium即可:
    C:\Windows\System32>appium
    [Appium] Welcome to Appium v2.2.3
    [Appium] Appium REST http interface listener started on http://0.0.0.0:4723
    [Appium] You can provide the following URLs in your client code to connect to this server:
    [Appium]        http://172.16.1.33:4723/
    [Appium]        http://127.0.0.1:4723/ (only accessible from the same host)
    [Appium]        http://172.31.32.1:4723/
    [Appium] No drivers have been installed in C:\Users\Administrator\.appium. Use the "appium driver" command to install the one(s) you want to use.
    
    • appium环境验证:
    C:\Windows\System32>appium-doctor
    WARN AppiumDoctor [Deprecated] Please use appium-doctor installed with "npm install @appium/doctor --location=global"
    info AppiumDoctor Appium Doctor v.1.16.2
    info AppiumDoctor ### Diagnostic for necessary dependencies starting ###
    info AppiumDoctor  ✔ The Node.js binary was found at: D:\nodejs\node.EXE
    info AppiumDoctor  ✔ Node version is 20.10.0
    info AppiumDoctor  ✔ ANDROID_HOME is set to: D:\android-sdk-windows
    info AppiumDoctor  ✔ JAVA_HOME is set to: D:\jdk-11.0.8
    info AppiumDoctor    Checking adb, android, emulator, apkanalyzer.bat
    info AppiumDoctor      'adb' is in D:\android-sdk-windows\platform-tools\adb.exe
    info AppiumDoctor      'android' is in D:\android-sdk-windows\tools\android.bat
    info AppiumDoctor      'emulator' is in D:\android-sdk-windows\tools\emulator.exe
    info AppiumDoctor      'apkanalyzer.bat' is in D:\android-sdk-windows\platform-tools\apkanalyzer.bat
    info AppiumDoctor  ✔ adb, android, emulator, apkanalyzer.bat exist: D:\android-sdk-windows
    info AppiumDoctor  ✔ 'bin' subfolder exists under 'D:\jdk-11.0.8'
    info AppiumDoctor ### Diagnostic for necessary dependencies completed, no fix needed. ###
    info AppiumDoctor
    info AppiumDoctor ### Diagnostic for optional dependencies starting ###
    info AppiumDoctor  ✔ opencv4nodejs is installed at: D:\nodejs\node_global. Installed version is: 5.6.0
    info AppiumDoctor  ✔ ffmpeg is installed at: D:\ffmpeg-6.1-essentials_build\bin\ffmpeg.EXE. ffmpeg version 6.1-essentials_build-www.gyan.dev Copyright (c) 2000-2023 the FFmpeg developers
    info AppiumDoctor  ✔ mjpeg-consumer is installed at: D:\nodejs\node_global. Installed version is: 2.0.0
    info AppiumDoctor  ✔ bundletool.jar is installed at: D:\android-sdk-windows\bundle-tools\bundletool.jar
    info AppiumDoctor  ✔ gst-launch-1.0.exe and gst-inspect-1.0.exe are installed at: F:\gstreamer\1.0\mingw_x86_64\bin\gst-launch-1.0.exe and F:\gstreamer\1.0\mingw_x86_64\bin\gst-inspect-1.0.exe
    info AppiumDoctor ### Diagnostic for optional dependencies completed, no fix possible. ###
    info AppiumDoctor
    info AppiumDoctor Everything looks good, bye!
    info AppiumDoctor
    

    4 关于元素定位

    4.1 uiautomatorviewer定位

    • uiautomatorviewer位于SDK目录下的tools\目录下;
    • 比如我的是:D:\android-sdk-windows\tools:
      在这里插入图片描述
    • 双击uiautomatorviewer.bat进行启动即可:
      在这里插入图片描述
      在这里插入图片描述

    4.2 uiautomator + accessibility_id定位

    • 打开uiautomatorviewer后,导入uix和png文件即可:
      在这里插入图片描述
    • accessibility_id定位主要使用的是元素的content-desc内容,如下:
      在这里插入图片描述

    5 框架结构设计

    5.1 封装登录

    • 在项目目录下右键-【新建】-【Python Package】,新建一个名为common的包;
      在这里插入图片描述
    • 在common上右键-【新建】-【Python file】,名为baseInfo.py,此时项目目录如下:
      在这里插入图片描述
    • 封装登录方法:
    from appium import webdriver
    from selenium.webdriver.common.by import By
     
    class LoginPage:
        def __init__(self, driver):
            self.driver = driver
     
        def open(self, app_path):
            # 启动应用
            desired_caps = {
                'app': app_path,
                # 其他desired capabilities...
            }
            self.driver.desired_capabilities.update(desired_caps)
            self.driver.start_session()
     
        def type_username(self, username):
            # 输入用户名
            username_element = self.driver.find_element(By.ID, 'username_field')
            username_element.clear()
            username_element.send_keys(username)
     
        def type_password(self, password):
            # 输入密码
            password_element = self.driver.find_element(By.ID, 'password_field')
            password_element.clear()
            password_element.send_keys(password)
     
        def click_login_button(self):
            # 点击登录按钮
            login_button = self.driver.find_element(By.ID, 'login_button')
            login_button.click()
     
    # 使用示例
    # 假设'app_path'是你的App路径
    app_path = '/path/to/your/app'
     
    # 初始化Appium驱动程序
    driver = webdriver.Remote('http://localhost:4723/wd/hub')
     
    # 初始化LoginPage类
    login_page = LoginPage(driver)
     
    # 打开应用
    login_page.open(app_path)
     
    # 输入用户名和密码
    login_page.type_username('your_username')
    login_page.type_password('your_password')
     
    # 点击登录按钮
    login_page.click_login_button()
     
    # 这里可以添加更多的断言来验证登录是否成功
    

    5.2 截图功能和调用

    • 依次点击【common】-【新建】-【Python File】,新建一个名为creenShot的py文件;
    • 截图名称命名规则为:

    年月日时分秒 + _screen.png

    • 截图功能代码:
    # -*- coding:utf-8 -*-
    # 作者:NoamaNelson
    # 文件名称:creenShot.py
    # 作用:封装截图功能并调用
    
    import time
    
    def save_creenshot(Windows):
        now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))  # 获取当前时间
        pic_path = "../creenshot/"+now+'_screen.png'                           # 保存截图到指定路径
        Windows.get_screenshot_as_file(savePath=pic_path)                      # 截图功能
    
    • 直接在其他需要截图的地方调用这个方法即可。

    5.3 日志模块封装

    • 依次【common】-【新建】-【Python File】,新建名为logOut的py文件,用于封装log日志;
    • 打开新建logOut.py,新建一个方法log_out来封装日志,log_out方法接受两个参数,一个是项目名称,一个是日志存放路径;
    # -*- coding:utf-8 -*-
    # 作者:NoamaNelson
    # 文件名称:logOut.py
    # 作用:封装log日志功能
    
    import logging                        # 引入日志模块
    import time
    
    
    def log_out(log_dir, name_project):
    
        '''
        :log_dir : 日志路径
        :name_project : 项目名称=>用于日志命名
        :return: 无
        '''
    
        now = time.strftime("%Y_%m_%d %H_%M_%S")   # 获取当前时间,格式:年月日时分秒
        logging.basicConfig(level=logging.INFO,
                            format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                            datefmt='%a, %d %b %Y %H:%M:%S',
                            filename=log_dir + now + '-' + name_project + '_test_log.log',
                            filemode='w')
        """
        level: 打印日志的级别,INFO:详细; WARNING:警告;ERROR:错误...
        format: 为处理程序使用指定的格式字符串;
        datefmt:使用特定的时间日期格式;
        filename:log日志的文件名称规则;
        filemode:文件读写模式。
        """
    

    5.4 测试报告模块封装

    • 直接使用pip install BeautifulReport;
    • 依次【common】-【新建】-【Python File】,新建名为reportOut的py文件,用于封装测试报告;
    # -*- coding:utf-8 -*-
    # 作者:NoamaNelson
    # 文件名称:reportOut.py
    # 作用:封装测试报告功能
    
    import time
    import unittest
    from BeautifulReport import BeautifulReport as bf     # 引入BeautifulReport报告模板
    
    
    def report_out(test_dir, report_dir, name_project):
        '''
        :test_dir: 用例路径
        :report_dir : 报告路径
        :name_project : 项目名称=>用于报告命名及描述
        :return: 无
        '''
    
        now = time.strftime("%Y_%m_%d %H_%M_%S")
        discover = unittest.defaultTestLoader.discover(test_dir,pattern='test*.py')      # 加载测试用例
        report_name = now + '-' + name_project + '_test_report.html'          # 报告名称
        run = bf(discover)
        run.report(filename=report_name, report_dir=report_dir, description=U"酷狗音乐UI自动化功能回归测试")
    
        """
        filename:报告名称;
        report_dir:测试报告存放路径;
        description:报告描述;
        """
    

    5.5 邮件服务模块封装

    • 前提是正确设置邮件服务第三方客户端,直接搜索设置方法即可;
    • 依次【common】-【新建】-【Python File】,新建名为sendMain的py文件,用于封装邮件服务;
    # -*- coding:utf-8 -*-
    # 作者:NoamaNelson
    # 文件名称:sendMain.py
    # 作用:封装邮件服务模块
    
    import time
    import smtplib
    import getpass
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.mime.base import MIMEBase
    from email import encoders
    import email
    import os
    
    
    def send_main(file_path, mail_to='yyy@126.com'):
        mail_from = 'xxx@126.com'
        f = open(file_path, 'rb')
        mail_body = f.read()
        f.close()
    
        # msg = email.MIMEMultipart.MIMEMultipart()
        msg = MIMEMultipart()
    
        # 构造MIMEBase对象做为文件附件内容并附加到根容器
        contype = 'application/octet-stream'
        maintype, subtype = contype.split('/', 1)
    
        # 读入文件内容并格式化
        data = open(file_path, 'rb')
        # file_msg = email.MIMEBase.MIMEBase(maintype, subtype)
        file_msg = MIMEBase(maintype, subtype)
        file_msg.set_payload(data.read())
        data.close()
    
        # email.Encoders.encode_base64(file_msg)
        encoders.encode_base64(file_msg)
    
        # 设置附件头
        basename = os.path.basename(file_path)
        file_msg.add_header('Content-Disposition', 'attachment', filename=basename)
        msg.attach(file_msg)
        print(u'msg 附件添加成功')
    
        msg1 = MIMEText(mail_body, "html", 'utf-8')
        msg.attach(msg1)
    
        if isinstance(mail_to, str):
            msg['To'] = mail_to
        else:
            msg['To'] = ','.join(mail_to)
        msg['From'] = mail_from
        msg['Subject'] = u'xxx自动化回归测试'
        msg['date'] = time.strftime('%Y-%m-%d-%H_%M_%S')
        print(msg['date'])
    
        smtp = smtplib.SMTP()
        smtp.connect('smtp.126.com')
        smtp.login('xxx@126.com', 'yyy')  # 登录账号和密码(密码为之前申请的授权码)
        smtp.sendmail(mail_from, mail_to, msg.as_string())
        smtp.quit()
        print('email has send out !')
    

    5.6 自动化用例设计

    • 在项目目录下,新建一个名为page的python包;
    • 依次项目目录下新建一个名为testcase的包;
    • 在page的下主要存放公共页面方法,比如:
    # -*- coding:utf-8 -*-
    
    # 作者:NoamaNelson
    # 文件名称:toolbar.py
    # 作用:封装用例中的元素(以酷狗音乐的工具栏为主)
    from common.baseInfo import InitInfor
    
    class ToolBar(object):
    
        def __init__(self):
            self.a = InitInfor()
            self.kugou = self.a.kugou
    
        def func_my_music(self):
            return self.kugou.TableControl(Name="我的音乐")
    
        def func_find(self):
            return self.kugou.TableControl(Name="发现")
    
        def func_live(self):
            return self.kugou.TableControl(Name="直播")
    
        def func_explore(self):
            return self.kugou.TableControl(Name="探索")
    
        def func_play(self):
            return self.kugou.TableControl(Name="手机Play")
    
        # 断言
        tool_bar = ["我的音乐", "发现", "直播", "探索", "手机Play"]
    
    
    • 在testcase的包下新建测试用例,调用页面方法:
    # -*- coding:utf-8 -*-
    # 作者:NoamaNelson
    # 文件名称:test_toolbar.py
    # 作用:遍历酷狗音乐工具栏的元素
    
    import unittest                                          # 引入unittest框架
    import time
    from page.toolbar import ToolBar                         # 引入页面元素
    import logging
    
    
    class TestToolBar(unittest.TestCase):
        """
        创建测试用例集
        """
    
        def setUp(self):
            self.toolbar = ToolBar()                         # 实例化,登录
            self.kugou = self.toolbar.kugou                  # 调用同一个窗口
            self.log = logging.getLogger()                   # 初始化log
    
        def tearDown(self):
            self.toolbar.a.login_out()                       # 退出酷狗音乐
    
        def test_toolbar(self):
            """ 循环点击酷狗音乐工具栏 """
            self.log.info("======酷狗工具栏======")            # 加入log
            time.sleep(0.5)
            self.toolbar.func_my_music().Click()
            self.toolbar.func_find().Click()
            self.toolbar.func_live().Click()
            self.toolbar.func_explore().Click()
            self.toolbar.func_play().Click()
            print("---------------", self.toolbar.func_play().Name)
    
            self.assertIn(self.toolbar.func_my_music().Name, self.toolbar.tool_bar, "用例执行成功")
            print("用例执行OK!")
    
    if __name__ == "__main__":
        unittest.main()
    

    5.7 框架主入口main.py设计&调用测试报告

    • 项目目录下新建一个名为mian的py文件;
    • 项目目录新建report目录,用于存放测试报告;
    • 打开main.py先设计脚本读取report目录下最新的测试报告;
    def acquire_report_address(reports_address):
        # 测试报告文件夹中的所有文件加入到列表
        test_reports_list = os.listdir(reports_address)
        # 按照升序排序生成新的列表
        new_test_reports_list = sorted(test_reports_list)
        # 获取最新的测试报告
        the_last_report = new_test_reports_list[-1]
        # 最新的测试报告地址
        the_last_report_address = os.path.join(reports_address, the_last_report)
        return the_last_report_address
    
    • 调用测试报告;
    def run_case():
        print("======开始执行!!!======")
        curpath = os.path.dirname(os.path.realpath(__file__))
        report_dir = os.path.join(curpath, "report/")        # 测试报告存放目录
        test_dir = os.path.join(curpath, "testcase/")        # 测试用例读取目录
        name_project = "KuGou "
        report_out(test_dir, report_dir, name_project)
        time.sleep(5)
        print("======执行结束!!!======")
    
    • 项目目录新建一个名为log的目录,用于存放日志;
    • run_case方法中调用日志;
    log_dir = os.path.join(curpath, "log/") 
    log_out(log_dir, name_project)
    
    • run_case方法中调用;
    send_main(acquire_report_address(report_dir), mail_to=['xxx@qq.com']) # mail_to为要发送给谁,可以写对应的邮箱,用逗号隔开即可
    

    6 运行效果

    • 日志效果:
      在这里插入图片描述
    • 测试报告:
      在这里插入图片描述
    • 邮件效果:
      在这里插入图片描述
  • 相关阅读:
    若依微服务前后端部署启动流程(只记录)
    Android 沉浸式状态栏
    Concurrent
    java与es8实战之一:以builder pattern开篇
    高校成绩管理数据库系统的设计与实现
    Github操作—SSH免密登录(六)——Git
    ODC现已开源:与开发者共创企业级的数据库协同开发工具
    全链路资金订单仓位诊断模型设计
    SparkSQL系列-7、自定义UDF函数?
    如何运行别的电脑上做好的项目,在自己的eclipse正常运行?eclipse如何正确的导入项目?
  • 原文地址:https://blog.csdn.net/NoamaNelson/article/details/143282288