zoukankan      html  css  js  c++  java
  • app自动化测试---自动化测试框架搭建

    1.安装依赖以及项目的基本目录

    # 安装依赖
    pip install pytest       
    pip install appium-python-client   
    pip install openpyxl                     # excel文件处理
    pip install pytest-html                  # 测试报告

     

    2.pom解析

    pom 设计的核心思想 就是将不同的页面单独进行维护,在做自动化的过程中,如果前端页面进行更改,原来写自动化代码就可能不再适用,因为前端页面更改了之后,元素定位已经不再适合,自动化用例执行失败。就要重新更改代码,比较麻烦。

    pom 将页面与测试用例单独封装,页面上的每个操作都单独封装起来,测试用例只需要调用封装好的方法即可。如果页面有改动。只需要改页面中封装的操作即可。

    下面以登录场景为例,编写自动化:

    conftest.py

    from appium import webdriver
    import pytest
    import os
    chromedriver= os.path.join(os.path.dirname(os.path.abspath(__file__)),'drivers/chrome/75.0.3770.140/chromedriver.exe')
    
    
    @pytest.fixture(scope='session')
    def driver():
        desired_caps = {
            'platformName': 'Android',                    # 测试Android系统
            'platformVersion': '7.1.2',                   # Android版本 可以在手机的设置中关于手机查看
            'deviceName': '127.0.0.1:62001',              # adb devices 命令查看  设置为自己的设备
            'automationName': 'UiAutomator2',             # 自动化引擎
            'noReset': False,                             # 不要重置app的状态
            'fullReset': False,                           # 不要清理app的缓存数据
            'chromedriverExecutable': chromedriver,       # chromedriver 对应的绝对路径
            'appPackage': "org.cnodejs.android.md",       # 应用的包名
            'appActivity': ".ui.activity.LaunchActivity"  # 应用的活动页名称(appium会启动app的加载页)
        }
        driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_capabilities=desired_caps)
        driver.implicitly_wait(5)  # 全局的隐式等待时间
    
        yield driver  # 将driver 传递出来
    
        driver.quit()

    pom/basePage.py

    """
      所有的页面都继承这个类,获得driver
    """
    import time
    from appium.webdriver.webdriver import WebDriver
    from selenium.common.exceptions import NoSuchElementException
    
    
    class BasePage:
        def __init__(self,driver:WebDriver):
            self.driver = driver
    
        # 获取toast的文本值
        @property
        def result_text(self):
            try:
                toast = self.driver.find_element_by_xpath('//android.widget.Toast')
                return  toast.text
            except NoSuchElementException:
                return "找不到这个元素,请检查自己的自动化代码"

    pom/loginPage.py

    """
      登陆页面
    """
    import time
    
    from appium.webdriver.webdriver import WebDriver
    
    from pom.basePage import BasePage
    
    
    class LoginPage(BasePage):
    
        # 初始化类的时候,打开登陆页面
        def __init__(self,driver:WebDriver):
            super(LoginPage,self).__init__(driver)
            # 判断是否是登陆页面
            current_activity = self.driver.current_activity
            if ".ui.activity.LoginActivity" in current_activity:
                pass
            else:
                # 不是登陆页面,则调用方法,打开登陆页面
                self.__go_login_page()
    
    
        # 导航到loginPage(登陆页面),定义一个私有的方法
        def __go_login_page(self):
            # 清空app的登陆状态(如果已经登陆,则去掉登陆状态)
            self.driver.reset()
            # 打开首页
            self.driver.start_activity(app_package='org.cnodejs.android.md',app_activity='.ui.activity.MainActivity')
    
            toggle_but = self.driver.find_element_by_android_uiautomator('resourceId("org.cnodejs.android.md:id/toolbar").childSelector(new UiSelector().className("android.widget.ImageButton"))')
            toggle_but.click()
            time.sleep(1)
    
            # 点击头像,去登陆页面
            avatar = self.driver.find_element_by_android_uiautomator('text("点击头像登录").resourceId("org.cnodejs.android.md:id/tv_login_name")')
            avatar.click()
    
    
        # 使用token的方式进行登录
        def with_token_login(self,token):
            self.driver.find_element_by_id('org.cnodejs.android.md:id/edt_access_token').send_keys(token)
            loginbtn = self.driver.find_element_by_android_uiautomator('text("登录").resourceId("org.cnodejs.android.md:id/btn_login")')
            # 点击登陆
            loginbtn.click()
    
        # 登陆失败的断言
        @property
        def with_token_failed_text(self):
            # 1. 截图
            ele = self.driver.find_element_by_id('org.cnodejs.android.md:id/edt_access_token')
            png = ele.screenshot_as_base64
            # 2. TODO 调用 ocr 图片识别 将图片中文字识别出来
    
            return ""
    
        # 扫码登陆
        def with_code_login(self):
            pass
    
    
        # 使用github登陆
        def with_github_login(self):
            pass

    testcases/test_user.py

    from pom.loginPage import LoginPage
    
    # 登陆的测试用例
    # 使用conftest.py 中定义的 driver
    def test_login(driver):
        # 打开登录页面
        loginpage = LoginPage(driver)
        # 使用token进行登录
        loginpage.with_token_login('d1563473-1f0d-4307-9774-6c2ff49c93ab')
    
        # 登陆成功,验证totas文本值
        result = loginpage.result_text
        assert result == "登录成功"

    启动appium , 执行   pytest testcases est_user.py -s -v  ,查看运行结果:

    (登陆成功)

     (登陆失败)

    下面以登录后发帖场景为例,编写自动化:

    pom/homePage.py

    """
      首页
    """
    from pom.basePage import BasePage
    from pom.createTopicPage import CreateTopicPage
    
    
    class HomePage(BasePage):
    
        def __init__(self,driver):
            super(HomePage,self).__init__(driver)
            # 判断一下,是否是首页
            if '.ui.activity.MainActivity' in self.driver.current_activity:
                pass
            else:
                self.__go_home_page()
    
    
        # 打开首页
        def __go_home_page(self):
            self.driver.start_activity(app_activity='.ui.activity.LaunchActivity')
    
    
        # 去发帖页面
        def go_create_topic(self):
            # 判断是否已经到达创建话题页面
            while not '.ui.activity.CreateTopicActivity' in self.driver.current_activity:
                # 再重新点击一下
                create_btn = self.driver.find_element_by_android_uiautomator(
                    '.resourceId("org.cnodejs.android.md:id/fab_create_topic")')
                create_btn.click()
    
            return CreateTopicPage(self.driver)

    pom/createTopicPage.py

    """
      发帖页面
    """
    from pom.basePage import BasePage
    
    
    class CreateTopicPage(BasePage):
    
        # 发布话题
        def create_new_topic(self,tab,title,content):
            # 选择类型
            spinner = self.driver.find_element_by_android_uiautomator('.resourceId("org.cnodejs.android.md:id/spn_tab")')
            spinner.click()
            tab_selcotor = f'.resourceId("android:id/text1").text("{tab}")'
            self.driver.find_element_by_android_uiautomator(tab_selcotor).click()
    
            # 输入标题
            title_content = self.driver.find_element_by_android_uiautomator(
                'resourceId("org.cnodejs.android.md:id/edt_title")')
            title_content.send_keys(title)
    
            # 输入内容
            content_area = self.driver.find_element_by_android_uiautomator(
                'resourceId("org.cnodejs.android.md:id/edt_content")')
            content_area.send_keys(content)
    
            # 点击发送
            send_btn = self.driver.find_element_by_android_uiautomator(
                'resourceId("org.cnodejs.android.md:id/action_send")')
            send_btn.click()

    testcases/test_topics.py

    from pom.homePage import HomePage
    from pom.loginPage import LoginPage
    
    
    # 发帖的测试用例
    def test_create_topic(driver):
        loginpage = LoginPage(driver)
        # 用户登录成功
        loginpage.with_token_login('d1563473-1f0d-4307-9774-6c2ff49c93ab')
    
        # 首页打开
        hp = HomePage(driver)
        # 进入创建话题页面
        create_page = hp.go_create_topic()
    
        create_page.create_new_topic(tab='分享',title='123',content='哈哈哈哈哈哈')
        result = create_page.result_text
        # 根据发帖结果做断言
        assert result == "标题要求10字以上"

    启动appium , 执行   pytest testcases est_topics.py -s -v  ,查看运行结果:

    也可以执行 pytest,查看登陆,和发帖2个测试用例的执行结果:

    3. Excel数据驱动

    testdata/data.xlsx

    utils/file_handler.py

    """
      登陆测试用例的数据驱动化测试
    """
    import pytest
    
    from pom.loginPage import LoginPage
    from utils.file_handler import FileHandler
    
    fl = FileHandler()
    # 从Excel文件中获取数据
    data = fl.get_data_by_sheet('用户登录')
    
    class TestDdtLogin:
    
        @pytest.mark.parametrize('token,status,expect_val',data)
        def test_login(self,driver,token,status,expect_val):
            # 打开登录页面
            loginpage = LoginPage(driver)
            # 使用token进行登录
            loginpage.with_token_login(token)
            if status == '成功':
                # 登录成功, 验证toast的文本值为登录成功
                result = loginpage.result_text
                assert result == expect_val
            if status == "失败":
                result = loginpage.with_token_failed_text
                assert result == expect_val

    testcases/test_ddt/test_ddt_login.py

    """
      登陆测试用例的数据驱动化测试
    """
    import pytest
    
    from pom.loginPage import LoginPage
    from utils.file_handler import FileHandler
    
    fl = FileHandler()
    # 从Excel文件中获取数据
    data = fl.get_data_by_sheet('用户登录')
    
    class TestDdtLogin:
    
        @pytest.mark.parametrize('token,status,expect_val',data)
        def test_login(self,driver,token,status,expect_val):
            # 打开登录页面
            loginpage = LoginPage(driver)
            # 使用token进行登录
            loginpage.with_token_login(token)
            if status == '成功':
                # 登录成功, 验证toast的文本值为登录成功
                result = loginpage.result_text
                assert result == expect_val
            if status == "失败":
                result = loginpage.with_token_failed_text
                assert result == expect_val

    启动appium , 执行   pytest testcases est_ddt est_ddt_login.py -s -v  ,查看运行结果:

    4.测试报告

    pytest-html https://pypi.org/project/pytest-html/

    pytest-allure https://pypi.org/project/allure-pytest/

    测试报告一:pytest-html

    main.py

    """
      项目运行文件,并添加测试报告
    """
    import pytest
    import os,time
    
    if __name__ == '__main__':
        report_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),'reports')
        if not os.path.exists(report_dir):
            os.mkdir(report_dir)
    
        report = time.strftime('%Y_%m_%d_%H_%M_%S')
    
        reportfile = os.path.join(report_dir,report+'.html')
    
        pytest.main(['testcases','-s','-v',f'--html={reportfile}'])

    运行main.py 文件,Run main.py 执行即可,会执行 testcases 里面所有的测试用例

    执行结束之后,生成的测试报告,我们可以在浏览器中打开

     5.优化conftest.py

       增加  每个测试用例执行完成之后,如果执行失败截图,截图的名称为测试用例名称+时间格式  的相关处理

    from appium import webdriver
    import pytest
    import os, time
    chromedriver= os.path.join(os.path.dirname(os.path.abspath(__file__)),'drivers/chrome/75.0.3770.140/chromedriver.exe')
    
    # scope='session' 标记的方法执行域为---->所有测试用例运行之前/之后 运行的方法
    @pytest.fixture(scope='session',autouse=True)
    def driver():
        desired_caps = {
            'platformName': 'Android',                    # 测试Android系统
            'platformVersion': '7.1.2',                   # Android版本 可以在手机的设置中关于手机查看
            'deviceName': '127.0.0.1:62001',              # adb devices 命令查看  设置为自己的设备
            'automationName': 'UiAutomator2',             # 自动化引擎
            'noReset': False,                             # 不要重置app的状态
            'fullReset': False,                           # 不要清理app的缓存数据
            'chromedriverExecutable': chromedriver,       # chromedriver 对应的绝对路径
            'appPackage': "org.cnodejs.android.md",       # 应用的包名
            'appActivity': ".ui.activity.LaunchActivity"  # 应用的活动页名称(appium会启动app的加载页)
        }
        driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_capabilities=desired_caps)
        driver.implicitly_wait(5)  # 全局的隐式等待时间
        yield driver  # 将driver 传递出来
        driver.quit()
    
    
    # 该方法是用来获取测试用例执行的结果(passed / FAILED)
    @pytest.hookimpl(tryfirst=True,hookwrapper=True)
    def pytest_runtest_makereport(item, call):
        outcome = yield
        rep = outcome.get_result()               # 获取用例的执行结果
        print('用例的执行结果rep---->',rep)
        setattr(item, "rep_" + rep.when, rep)    # 将执行结果保存到 item 属性中 , req.when 执行时
    
    
    # scope='function' 标记的方法执行域为---->每个测试用例运行之前/之后 运行的方法
    @pytest.fixture(scope='function',autouse=True)
    def case_run(driver:webdriver,request):   # request 为 pytest_runtest_makereport 方法获取到的执行结果(固定参数和用法)
        yield
        # 每个测试用例执行完成之后,如果执行失败截图,截图的名称为测试用例名称+时间格式
        if request.node.rep_call.failed:
            screenshots = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'screenshots')
            if not os.path.exists(screenshots):
                os.mkdir(screenshots)
            casename: str = request.node.nodeid
            # print("执行测试用例的名字:", casename)
            # 测试用例的名字很长 testcases/test_ddt/test_ddt_login.py::TestDdtLogin::test_login[....]
            # 对字符串进行截取,截取之后显示为  test_ddt_login-TestDdtLogin
            casename = casename[casename.rfind('/')+1:casename.rfind('::')].replace('.py::','-')
            filename = casename + '-' + time.strftime('%Y_%m_%d-%H_%M_%S')  +".png"
            screenshot_file = os.path.join(screenshots, filename)
            # 保存截图
            driver.save_screenshot(screenshot_file)

     6. 多设备连接,并行执行测试代码

       通过代码的方式自动的获取连接的多个设备,拿到设备的串号,启动多个appium,通过多进程的方式,在多个设备上并行的执行自动化测试用例

        app自动化测试----多设备并行运行   点击查看优化后的代码

    最后,执行 pip freeze > requirements.txt   ,将项目中使用的第三方包的包名和版本导出到文件中

                       pip install -r requirements.txt    # 安装requirements.txt文件夹中库及对应版本
  • 相关阅读:
    2016-09-13面试记录
    javascript中的深度拷贝的实现过程及深拷贝的几种方法。
    javascript中的for in循环
    常见的兼容问题及其解决方法。
    一次清空所有数据方法
    数组排序
    css对齐 挖坑~
    css reset样式重置
    CSS 表单
    CSS 表格
  • 原文地址:https://www.cnblogs.com/Z-Queen/p/14950773.html
Copyright © 2011-2022 走看看