• 如何将自己的测试脚本分离成PO模式的测试框架


    1 PO模式

    1.1 PO模式介绍

    Page Object Model

    测试页面和测试脚本分离,即页面封装成类,供测试脚本调用。

    (将项目分为page.pytest.py

    测试用例:就是excel里面一条一条的case,叫作测试用例

    测试脚本:将测试用例用代码方式实现出来,py文件。

    测试页面:写脚本的时候,经常会获取某个测试页面中的按钮或别的内容,这个页面叫测试页面。

    优缺点

    优点:

      提高了测试用例的可能性

      减少了代码的重复

      提高测试用例的可维护性,特别是UI频繁改动的项目

    缺点:

      结构复杂:基于流程做了模块化的划分。

    1.2 项目示例

    需求:

    设置页面--点击更多--移动网络--首选网络类型--点击2G

    设置页面--点击更多--移动网络--首选网络类型--点击3G

    设置页面--点击显示--搜索按钮--输入hello--点击返回

    传统模式文件目录:

    - scripts

      - test_setting.py

    - pytest.ini

    test_setting.py内容为:

    # encoding=utf-8
    
    import time
    
    from appium import webdriver
    
    from selenium.webdriver.support.ui import WebDriverWait
    
    
    
    class TestSetting:
    
     
    
        def setup(self):
    
            server = r'http://localhost:4723/wd/hub'  # Appium Server, 端口默认为4723
    
            desired_capabilities = {}
    
            desired_capabilities['platformName'] = 'Android'
    
            desired_capabilities['deviceName'] = '127.0.0.1:62001'
    
            desired_capabilities['platformVersion'] = '5.1.1'
    
            desired_capabilities['appPackage'] = 'com.android.settings'
    
            desired_capabilities['appActivity'] = '.Settings'
    
            desired_capabilities['unicodeKeyboard'] = True
    
            desired_capabilities['reserKeyBoard'] = True
    
            self.driver = webdriver.Remote(server, desired_capabilities)  # 连接手机和APP
    
            self.wdw = WebDriverWait(self.driver, 10, 1)
    
     
    
        def test_setting_network_2g(self):
    
            button_more = self.wdw.until(lambda x:x.find_element_by_xpath("//*[contains(@text,'更多')]"))
    
            button_more.click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text,'移动网络')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '首选网络类型')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '2G')]")).click()
    
     
    
        def test_setting_network_3g(self):
    
            button_more = self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text,'更多')]"))
    
            button_more.click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '移动网络')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '首选网络类型')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '3G')]")).click()
    
     
    
        def test_setting_display_search(self):
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text,'显示')]")).click()
    
            time.sleep(3)
    
            self.wdw.until(lambda x: x.find_element_by_id(r"com.android.settings:id/search")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '搜索')]")).send_keys('hello')
    
            self.wdw.until(lambda x: x.find_element_by_class_name("android.widget.ImageButton")).click()
    
     

    分离页面

    多文件区分,也就是每个页面一个文件,将传统的路径修改为如下:

    - scripts

      - test_network.py

      - test_display.py

    - pytest.ini

    然后将“移动网络”页面的两个case放到test_network.py文件中

    “显示”页面的case放到test_display.py

    修改后,test_network.py文件的内容是:

    # encoding=utf-8
    
    import time
    
    from appium import webdriver
    
    from selenium.webdriver.support.ui import WebDriverWait
    
     
    
     
    
    class TestSetting:
    
     
    
        def setup(self):
    
            server = r'http://localhost:4723/wd/hub'  # Appium Server, 端口默认为4723
    
            desired_capabilities = {}
    
            desired_capabilities['platformName'] = 'Android'
    
            desired_capabilities['deviceName'] = '127.0.0.1:62001'
    
            desired_capabilities['platformVersion'] = '5.1.1'
    
            desired_capabilities['appPackage'] = 'com.android.settings'
    
            desired_capabilities['appActivity'] = '.Settings'
    
            desired_capabilities['unicodeKeyboard'] = True
    
            desired_capabilities['reserKeyBoard'] = True
    
            self.driver = webdriver.Remote(server, desired_capabilities)  # 连接手机和APP
    
            self.wdw = WebDriverWait(self.driver, 10, 1)
    
     
    
        def test_setting_network_2g(self):
    
            button_more = self.wdw.until(lambda x:x.find_element_by_xpath("//*[contains(@text,'更多')]"))
    
            button_more.click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text,'移动网络')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '首选网络类型')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '2G')]")).click()
    
     
    
        def test_setting_network_3g(self):
    
            button_more = self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text,'更多')]"))
    
            button_more.click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '移动网络')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '首选网络类型')]")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '3G')]")).click()

    修改后,test_display.py文件的内容是:

    # encoding=utf-8
    
    import time
    
    from appium import webdriver
    
    from selenium.webdriver.support.ui import WebDriverWait
    
     
    
     
    
    class TestSetting:
    
     
    
        def setup(self):
    
            server = r'http://localhost:4723/wd/hub'  # Appium Server, 端口默认为4723
    
            desired_capabilities = {}
    
            desired_capabilities['platformName'] = 'Android'
    
            desired_capabilities['deviceName'] = '127.0.0.1:62001'
    
            desired_capabilities['platformVersion'] = '5.1.1'
    
            desired_capabilities['appPackage'] = 'com.android.settings'
    
            desired_capabilities['appActivity'] = '.Settings'
    
            desired_capabilities['unicodeKeyboard'] = True
    
            desired_capabilities['reserKeyBoard'] = True
    
            self.driver = webdriver.Remote(server, desired_capabilities)  # 连接手机和APP
    
            self.wdw = WebDriverWait(self.driver, 10, 1)
     
    
        def test_setting_search(self):
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text,'显示')]")).click()
    
            time.sleep(3)
    
            self.wdw.until(lambda x: x.find_element_by_id(r"com.android.settings:id/search")).click()
    
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '搜索')]")).send_keys('hello')
    
            self.wdw.until(lambda x: x.find_element_by_class_name("android.widget.ImageButton")).click()

    这样做的好处是等将来需要修改哪个模块的哪个功能的时候,比较容易找到。

    另外,可以看到测试用例中的setup()方法里面,连接手机的代码是重复的,所以可以把他提取出来,提取后的目录为:

    多了一个base文件夹,这个文件夹就放所有的公共函数,文件夹中多了一个base_driver.py文件,这个文件中写的代码就是连接手机的代码,如下:

    from appium import webdriver
    
    from selenium.webdriver.support.ui import WebDriverWait
    
    
    def init_driver():
    
        server = r'http://localhost:4723/wd/hub'  # Appium Server, 端口默认为4723
    
        desired_capabilities = {}
    
        desired_capabilities['platformName'] = 'Android'
    
        desired_capabilities['deviceName'] = '127.0.0.1:62001'
    
        desired_capabilities['platformVersion'] = '5.1.1'
    
        desired_capabilities['appPackage'] = 'com.android.settings'
    
        desired_capabilities['appActivity'] = '.Settings'
    
        desired_capabilities['unicodeKeyboard'] = True
    
        desired_capabilities['reserKeyBoard'] = True
    
        driver = webdriver.Remote(server, desired_capabilities)  # 连接手机和APP
    
        wdw = WebDriverWait(driver, 10, 1)
    
        return wdw
    

    有了这个文件,test_network.pytest_display.py这两个文件就可以修改一下了,如下:

    里面有两处圈红的位置,下面那处是修改代码共同的部分的,将原来的连接手机的代码删掉,放到这里就可以了。改好记得将我们自己写的这个模块导入进来。

    如果你在运行过程中,报错了,提示找不到你写的base这个模块,尤其是用pytest运行的时候,可能会报错,那就可以在代码中加入上面圈红的部分。这部分代码意思是让python系统查找模块的时候也查找一下os.getcwd()这个路径。(当前项目的路径)

    分离测试脚本

    什么叫分离测试脚本?

    就是说将脚本的流程和其他步骤分开来,测试脚本只剩下流程,其他的步骤放到page中。换句话说就是脚本是写你想干什么,page写怎么干。

    这样做的好处是:

    测试脚本只关注过程;过程改变,只需要修改测试脚本。

    继续修改目录:

    新建了一个page包,包里创建两个文件:display_page.py etwork_page.py,你需要测试多少个页面,就创建多少个_page文件,注意起名字不能起test_开头的,以免被当成测试用例执行。

    修改test_display.py

    里面原来的方法:

    test_setting_search(self):

    这个方法简单总结一下就是:

      # 点击显示

      # 点击放大镜

      # 输入文字

      # 点击返回

    前面说了,test_display.py文件里面的方法只关注过程,而display_page.py文件里面关注如何做,所以点击显示,怎么点击,放到page中,包括点击放大镜,输入文字,点击返回,怎么做都写到page中。修改后,test_display.py文件内容如下:

    # encoding=utf-8
    import time
    from base.base_driver import init_driver
    from page.display_page import DisplayPage
    
    
    class TestDisplay:
    
    
        def setup(self):
    
    
            self.wdw = init_driver()
            # 创建一个DisplayPage类的对象
            self.display_page = DisplayPage(self.wdw)
    
        def test_setting_search(self):
    
            # 点击显示
            self.display_page.click_display()
            time.sleep(3)
    
            # 点击放大镜
            self.display_page.click_search()
    
            # 输入文字
            self.display_page.input_text('hello')
    
            # 点击返回
            self.display_page.click_back()

    display_page.py文件内容为

    class DisplayPage:
    
        def __init__(self, wdw):
            self.wdw = wdw
    
        def click_display(self):
    
            """点击显示"""
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text,'显示')]")).click()
    
        def click_search(self):
            """点击放大镜"""
            self.wdw.until(lambda x: x.find_element_by_id(r"com.android.settings:id/search")).click()
    
        def input_text(self, str):
            """搜索处输入文字"""
            self.wdw.until(lambda x: x.find_element_by_xpath("//*[contains(@text, '搜索')]")).send_keys(str)
    
        def click_back(self):
    
            """点击返回"""
            self.wdw.until(lambda x: x.find_element_by_class_name("android.widget.ImageButton")).click()

    test_network里面也是同样的改法。

    抽取元素特征

    接下来继续修改display_page.py这个文件,将文件中的字符串元素抽取出来。

    抽取之前先认识一个方法:

    这个find_element_by_id的源码,里面的内容只有一个,就是return self.find_element(by = By.ID, value=id_)

    find_element这个方法两个参数,第一个参数是通过什么方式查找,比如这里是ID,后面就是一个字符串,这里是ID值。

    认识了这个函数,那么下面这两句话,就是等价的,做的动作是一样的:

    find_element_by_id(r"com.android.settings:id/search")).click()

    find_element(By.ID, r"com.android.settings:id/search")).click()

    然后可以将之前display_page.py中的代码修改为:

    from selenium.webdriver.common.by import By
    
    from selenium.webdriver.support.wait import WebDriverWait
    
     
    
    class DisplayPage:
    
        display_button = By.XPATH, "//*[contains(@text,'显示')]"
    
        search_button = By.ID, r"com.android.settings:id/search"
    
        search_text = By.XPATH, "//*[contains(@text, '搜索')]"
    
        return_button = By.CLASS_NAME, "android.widget.ImageButton"
    
     
    
        def __init__(self, driver):
    
            self.driver = driver
            # 点击显示进入显示页面(这里可以放所有的test_display.py里面的前置代码)
            self.click_display()
    
        def click_display(self):
            """点击显示"""
            # self.wdw.until(lambda x: x.find_element_by_xpath()).click()
            # self.wdw.until(lambda x: x.find_element(self.display_button)).click()
    
            self.find_element(self.display_button).click() 
    
        def click_search(self):
            """点击放大镜"""
            self.find_element(self.search_button).click()
    def input_text(self, str): """搜索处输入文字""" self.find_element(self.search_text).send_keys(str) def click_back(self): """点击返回""" self.find_element(self.return_button).click() def find_element(self, loc): """定义一个自己的find_element方法,只有一个参数loc,方法内部调用driver的find_element方法,将loc的两个元素传进去""" by = loc[0] value = loc[1] # 统一调用WebDriverWait的方法,注意将找到的元素控件return回去 return WebDriverWait(self.driver, 5, 1).until(lambda x: x.find_element(by, value))

    此时test_display.py里面的内容:

    # encoding=utf-8
    
    import time
    
    from base.base_driver import init_driver
    
    from page.display_page import DisplayPage
    
     
    
    class TestDisplay:
    
     
    
        def setup(self):
    
            self.driver = init_driver()
    
            # 创建一个DisplayPage类的对象
    
            self.display_page = DisplayPage(self.driver)
    
     
    
        def test_setting_search(self):
    
            # 点击显示
            # self.display_page.click_display()    # 如果这个文件中所有的case都需要进入显示页面,那这句话可以写到display_page中# 点击放大镜
            self.display_page.click_search()
            # 输入文字
            self.display_page.input_text('hello')
            # 点击返回
            self.display_page.click_back()

    抽取action

    action文件里面封装的是一些动作类方法,共用的方法。

    比如click()方法,所有的测试用例都需要用到这个方法,就可以把这个抽取出来,放到base类中,让base中的类为父类,page中都继承这个父类,就可以使用父类中的方法了。

    base包中新建一个base_action.py文件

    文件内容为:

    from selenium.webdriver.support.wait import WebDriverWait
    
    
    class BaseAction:
    
        def __init__(self, driver):
    
            self.driver = driver
    
    
        def click(self, loc):
            self.find_element(loc).click()
    
     
    
        def input_text(self, loc, text):
            self.find_element(loc).send_keys(text)
    
    
        def find_element(self, loc):
    
            """
    
            定义一个自己的find_element方法,
    
            只有一个参数loc,方法内部调用driver的find_element方法,
    
            将loc这个元组的两个元素传进去
    
            """
    
            by = loc[0]
    
            value = loc[1]
    
            # 统一调用WebDriverWait的方法
    
            return WebDriverWait(self.driver, 5, 1).until(lambda x: x.find_element(by, value))

    PO模式最终

    综上几步抽取,最终PO模式的结果为:

    路径结构

    base_action放所有的公共方法,比如所有的查找元素的方法,所有的click方法,input方法等。

    base_driver中放的是连接手机,返回driver的方法

    display_page中放的是显示页面的所有元素的元素特征,以及操作

    network_page中放的是移动网络页面的所有元素的元素特征,以及操作

    test_display中放显示页面的测试步骤

    test_network中放移动网络页面的测试步骤

    baseaction.py文件内容

    basedriver.py文件的内容:

    display_page文件内容:

    network_page.py文件的内容为:

    test_display.py文件内容为:

    test_network.py文件的内容为:

    pytest.ini基本没变化:

    后面又修改了一些截图、报告等相关的功能,可以参考源码连接:

    https://github.com/suyang2020/PoDemo

  • 相关阅读:
    Codeforces 294B Shaass and Bookshelf:dp
    Codeforces 372B Counting Rectangles is Fun:dp套dp
    Codeforces 402D Upgrading Array:贪心 + 数学
    Codeforces 571B Minimization:dp + 贪心【前后相消】
    Codeforces 509F Progress Monitoring:区间dp【根据遍历顺序求树的方案数】
    codeforces 447E or 446C 线段树 + fib性质或二次剩余性质
    类斐波那契数列的一些性质
    CF 1097D
    最近点对问题
    2018ACM-ICPC EC-Final 现场赛I题 Misunderstanding...Missing 倒着DP
  • 原文地址:https://www.cnblogs.com/sy_test/p/12565864.html
Copyright © 2020-2023  润新知