App自动化测试笔记(十):PO模式

什么是PO?

  --页面和脚本分离
   --是一种设计模式

优点

   --减少冗余,提高复用性
   --降低耦合度
   --降低维护成本

案例练习

 

"""
更多-移动网络-首选网络类型-点击2g
更多-移动网络-首选网络类型-点击3g
显示-搜索按钮-输入hello-点击返回
"""
from appium import webdriver
import pytest
import time
 
class TestSetting:
    def setup(self):
        desired_caps = dict()
        desired_caps['platformName'] = 'Android'
        desired_caps['platformVersion'] = '5.1'
        desired_caps['deviceName'] = '192.168.56.101:5555'
        desired_caps['appPackage'] = 'com.android.settings'
        desired_caps['appActivity'] = '.Settings'
        self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
        self.driver.implicitly_wait(5)
    def teardown(self):
        self.driver.quit()
//更多-移动网络-首选网络类型-点击2g
    def test_setting01(self):
        self.driver.find_element_by_xpath("//*[@text = '更多']").click()
        self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
        self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
        self.driver.find_element_by_xpath("//*[@text = '3G']").click()
//更多-移动网络-首选网络类型-点击3g
    def test_setting02(self):
        self.driver.find_element_by_xpath("//*[@text = '更多']").click()
        self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
        self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
        self.driver.find_element_by_xpath("//*[@text = '2G']").click()
//显示-搜索按钮-输入hello-点击返回
    def test_setting03(self):
        self.driver.find_element_by_xpath("//*[@text = '显示']").click()
        self.driver.find_element_by_id("com.android.settings:id/search").click()
        self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")

抽取前置代码

优点

可以复用

步骤

1、在项目下新建一个base模块
2、再base模块中新建一个base_driver.py
3、将“前置代码”放在文件的init_driver函数中
4、再test_display.py中的setup调用前置代码,并获取self.driver

结构

- base
--base_driver.py
-scripts
--test_setting.py
-pytest.ini

代码

base_driver.py代码
from appium import webdriver
 
def init_driver():
    desired_caps = dict()
    desired_caps['platformName'] = 'Android'
    desired_caps['platformVersion'] = '5.1'
    desired_caps['deviceName'] = '192.168.56.101:5555'
    desired_caps['appPackage'] = 'com.android.settings'
    desired_caps['appActivity'] = '.Settings'
    return webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
test_setting.py代码
import os, sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
 
"""
更多-移动网络-首选网络类型-点击2g
更多-移动网络-首选网络类型-点击3g
显示-搜索按钮-输入hello-点击返回
"""
class TestSetting:
    def setup(self):
        self.driver = init_driver()
    def teardown(self):
        self.driver.quit()
    def test_setting01(self):
        self.driver.find_element_by_xpath("//*[@text = '更多']").click()
        self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
        self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
        self.driver.find_element_by_xpath("//*[@text = '3G']").click()
    def test_setting02(self):
        self.driver.find_element_by_xpath("//*[@text = '更多']").click()
        self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
        self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
        self.driver.find_element_by_xpath("//*[@text = '2G']").click()
    def test_setting03(self):
        self.driver.find_element_by_xpath("//*[@text = '显示']").click()
        self.driver.find_element_by_id("com.android.settings:id/search").click()
        self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")

在运行过程中出现ModuleNotFoundError: No module named 'base',大体是因为python解释器在寻找模块的时候没有在我当前项目路径下找,所以没有找到。

解决办法:
在你的from base.base_driver import base_driver这句话上面加上:
import os, sys
sys.path.append(os.getcwd())

抽取page

好处

代码复用(多个脚本可能会用到相同的动作特征,只需要在page中写一份)
如果ui有更变,去找page
如果动作的顺序有更变,去找scripts

步骤

1、把之前test中的"self.driver.find_element_by_xpath("//*[@text = '更多']").click()"操作,放在page中
2、在scripts中调用
3、注意:每一个脚本,一定只会有一个driver对象
4、发现,在page中,需要使用到diver对象
5、将test在setup中创建的driver对象,通过page的init进行传递(一定是传递,不是重新调用init的方法)

结构

- base
-- base_driver.py
- page
-- display_page.py
-- more_page.py
- scripts
-- test_setting.py
- pytest.ini

代码

base_driver.py代码
from appium import webdriver
 
def init_driver():
    desired_caps = dict()
    desired_caps['platformName'] = 'Android'
    desired_caps['platformVersion'] = '5.1'
    desired_caps['deviceName'] = '192.168.56.101:5555'
    desired_caps['appPackage'] = 'com.android.settings'
    desired_caps['appActivity'] = '.Settings'
    return webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
display_page.py代码
class Display():
    def __init__(self,driver):
        self.driver = driver
    def page_display(self):
        self.driver.find_element_by_xpath("//*[@text = '显示']").click()
    def page_lookup(self):
        self.driver.find_element_by_id("com.android.settings:id/search").click()
    def page_send_keys(self):
        self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
more_page.py代码

class More():
    def __init__(self,driver):
        self.driver = driver
    def page_more(self):
        self.driver.find_element_by_xpath("//*[@text = '更多']").click()
    def page_mobile_network(self):
        self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
    def page_network_type(self):
        self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
    def page_type_3G(self):
        self.driver.find_element_by_xpath("//*[@text = '3G']").click()
    def page_type_2G(self):
        self.driver.find_element_by_xpath("//*[@text = '2G']").click()
test_setting.py代码
import os, sys
sys.path.append(os.getcwd())
from page.display_page import Display
from page.more import More
from base.base_driver import init_driver
 
"""
更多-移动网络-首选网络类型-点击2g
更多-移动网络-首选网络类型-点击3g
显示-搜索按钮-输入hello-点击返回
"""
class TestSetting:
    def setup(self):
        self.driver = init_driver()
        self.display = Display(self.driver)
        self.more = More(self.driver)
    def teardown(self):
        self.driver.quit()
    def test_setting01(self):
        self.more.page_more()
        self.more.page_mobile_network()
        self.more.page_network_type()
        self.more.page_type_3G()
    def test_setting02(self):
        self.more.page_more()
        self.more.page_mobile_network()
        self.more.page_network_type()
        self.more.page_type_2G()
    def test_setting03(self):
        self.display.page_display()
        self.display.page_lookup()
        self.display.page_send_keys()

注意:如果page中不添加driver会报错:

FAILED scripts/test_setting.py::TestSetting::test_setting01 - AttributeError: 'More' object has no attribute 'driver'
FAILED scripts/test_setting.py::TestSetting::test_setting02 - AttributeError: 'More' object has no attribute 'driver'
FAILED scripts/test_setting.py::TestSetting::test_setting03 - AttributeError: 'Display' object has no attribute 'driver'

所以可以在构造器__init__()中加入driver,但是不可重复调用init_driver()函数去生成driver,创建两个driver会报错。

抽出find_element

好处

1、代码的复用
2、page的代码更加简洁

步骤

1、将特征,添加一个查找方式(By)变成元组
2、自己写一个find_element方法,传入这个元组
3、在这个方法中,调用系统的find_element并且返回元素
4、发现,多个页面都有这个方法
5、新建了一个base_find.py文件
6、将方法移动到base_find.py文件中
7、让所有page继承Base
8、注意:此时操作行为需要使用到driver
9、直接在Base类的init中接收driver
10、并移除page中的init方法即可

结构

- base
-- base_driver.py
-- base_find.py
- page
-- display_page.py
-- more_page.py
- scripts
-- test_setting.py
- pytest.ini

代码

base_driver.py代码
from appium import webdriver
 
def init_driver():
    desired_caps = dict()
    desired_caps['platformName'] = 'Android'
    desired_caps['platformVersion'] = '5.1'
    desired_caps['deviceName'] = '192.168.56.101:5555'
    desired_caps['appPackage'] = 'com.android.settings'
    desired_caps['appActivity'] = '.Settings'
    return webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
base_find.py代码
class Base():
    def __init__(self,driver):
        self.driver = driver
    def find_element(self,loc):
        return self.driver.find_element(loc[0],loc[1])
display_page.py代码

from base.base_find import Base
from selenium.webdriver.common.by import By
 
class Display(Base):
    # driver.find_element_by_xpath(BY.XPATH,"//*[@text = '显示']").click()
    display_element = By.XPATH, "//*[@text = '显示']"
    # driver.find_element_by_id("com.android.settings:id/search").click()
    look_up_click = By.ID, "com.android.settings:id/search"
    # self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
    send_keys_element = By.ID, "android:id/search_src_text"
    def page_display(self):
        self.find_element(self.display_element).click()
    def page_lookup(self):
        self.find_element(self.look_up_click).click()
    def page_send_keys(self):
        self.find_element(self.send_keys_element).send_keys("hello")
more_page.py代码

from base.base_find import Base
 
from selenium.webdriver.common.by import By
 
class More(Base):
    # self.driver.find_element_by_xpath("//*[@text = '更多']").click()
    more_click = By.XPATH, "//*[@text = '更多']"
    # self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
    mobile_network_click = By.XPATH, "//*[@text = '移动网络']"
    # self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
    network_type_click = By.XPATH, "//*[@text = '首选网络类型']"
    # self.driver.find_element_by_xpath("//*[@text = '3G']").click()
    type_3G_click = By.XPATH, "//*[@text = '3G']"
    # self.driver.find_element_by_xpath("//*[@text = '2G']").click()
    type_2G_click = By.XPATH, "//*[@text = '2G']"
    def page_more(self):
        # self.driver.find_element_by_xpath("//*[@text = '更多']").click()
        self.find_element(self.more_click).click()
    def page_mobile_network(self):
        # self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
        self.find_element(self.mobile_network_click).click()
    def page_network_type(self):
        # self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
        self.find_element(self.network_type_click).click()
    def page_type_3G(self):
        # self.driver.find_element_by_xpath("//*[@text = '3G']").click()
        self.find_element(self.type_3G_click).click()
    def page_type_2G(self):
        # self.driver.find_element_by_xpath("//*[@text = '2G']").click()
        self.find_element(self.type_2G_click).click()
test_setting.py代码
import os, sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from base.base_find import Base
from page.display_page import Display
from page.more import More
 
"""
更多-移动网络-首选网络类型-点击2g
更多-移动网络-首选网络类型-点击3g
显示-搜索按钮-输入hello-点击返回
"""
class TestSetting:
    def setup(self):
        self.driver = init_driver()
        self.display = Display(self.driver)
        self.more = More(self.driver)
    def teardown(self):
        self.driver.quit()
    def test_setting01(self):
        self.more.page_more()
        self.more.page_mobile_network()
        self.more.page_network_type()
        self.more.page_type_3G()
    def test_setting02(self):
        self.more.page_more()
        self.more.page_mobile_network()
        self.more.page_network_type()
        self.more.page_type_2G()
    def test_setting03(self):
        self.display.page_display()
        self.display.page_lookup()
        self.display.page_send_keys()

注意Display和More类继承了Base类,Base类中构造了一个driver,所以此时的driver传给Display和More类的对象display和more就可以了。


增加WebDriverWait和默认时间

好处

对appium的find_element方法进行了二次封装
调用自己的方法,一句话,既可以快速实现两个功能(显示等待,找元素)

步骤

1、在find_element的外面,嵌套一个WebDriverWait
2、此时,如果在某些特殊情况下,需要换另外一个超时时间和频率
3、不应该将,超时时间和频率写死,将其变为参数,并设置一个最常用的数值为默认参数
4、应为base_action放的是一些page的公共方法,所以可以提前准备一个find_elements的方法

base_find.py中find_element()封装后的代码:

from selenium.webdriver.support.wait import WebDriverWait
 
class Base():
    def __init__(self,driver):
        self.driver = driver
    def find_element(self,loc,time = 5 ,poll = 1):
        ele = WebDriverWait(self.driver,timeout=time,poll_frequency=poll).until(lambda x:x.find_element(loc[0],loc[1]))
        return ele
 
    def find_elements(self,loc,time = 5 ,poll = 1):
        ele = WebDriverWait(self.driver,timeout=time,poll_frequency=poll).until(lambda x:x.find_elements(loc[0],loc[1]))
        return ele


抽取动作到base_find

步骤

1、在page的小的动作中(函数),发现想要点击或输入,一定需要先根据特征找对应的元素。然后才能做操作
2、此时可以将“找”和“点击”进行封装
3、在base_find中写一个click方法,传入一个特征
4、此方法会根据特征,对应的去找元素,并点击
5、input同理
6、数据应交给由scripts管理,如果page或base_action想要使用,则通过脚本进行传递
    1、比如说,输入“hello”,此时hello字符串应该写在test函数中

代码

base_find.py代码

from selenium.webdriver.support.wait import WebDriverWait
 
class Base():
    def __init__(self,driver):
        self.driver = driver
    def find_element(self,loc,time = 5 ,poll = 1):
        ele = WebDriverWait(self.driver,timeout=time,poll_frequency=poll).until(lambda x:x.find_element(loc[0],loc[1]))
        return ele
 
    def find_elements(self,loc,time = 5 ,poll = 1):
        ele = WebDriverWait(self.driver,timeout=time,poll_frequency=poll).until(lambda x:x.find_elements(loc[0],loc[1]))
        return ele
    def base_click(self,loc):
        self.find_element(loc).click()
    def base_send_keys(self,loc,value):
        self.find_element(loc).send_keys(value)
display_page.py中代码

from base.base_find import Base
from selenium.webdriver.common.by import By
 
class Display(Base):
    # driver.find_element_by_xpath(BY.XPATH,"//*[@text = '显示']").click()
    display_element = By.XPATH, "//*[@text = '显示']"
    # driver.find_element_by_id("com.android.settings:id/search").click()
    look_up_click = By.ID, "com.android.settings:id/search"
    # self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
    send_keys_element = By.ID, "android:id/search_src_text"
    def page_display(self):
        self.base_click(self.display_element)
    def page_lookup(self):
        self.base_click(self.look_up_click)
    def page_send_keys(self):
        self.base_send_keys(self.send_keys_element,"hello")
more.py中代码

from base.base_find import Base
from selenium.webdriver.common.by import By
 
class More(Base):
    # self.driver.find_element_by_xpath("//*[@text = '更多']").click()
    more_click = By.XPATH, "//*[@text = '更多']"
    # self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
    mobile_network_click = By.XPATH, "//*[@text = '移动网络']"
    # self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
    network_type_click = By.XPATH, "//*[@text = '首选网络类型']"
    # self.driver.find_element_by_xpath("//*[@text = '3G']").click()
    type_3G_click = By.XPATH, "//*[@text = '3G']"
    # self.driver.find_element_by_xpath("//*[@text = '2G']").click()
    type_2G_click = By.XPATH, "//*[@text = '2G']"
    def page_more(self):
        # self.driver.find_element_by_xpath("//*[@text = '更多']").click()
        self.base_click(self.more_click)
    def page_mobile_network(self):
        # self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
        self.base_click(self.mobile_network_click)
    def page_network_type(self):
        # self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
        self.base_click(self.network_type_click)
    def page_type_3G(self):
        # self.driver.find_element_by_xpath("//*[@text = '3G']").click()
        self.base_click(self.type_3G_click)
    def page_type_2G(self):
        # self.driver.find_element_by_xpath("//*[@text = '2G']").click()
        self.base_click(self.type_2G_click)


将不同的元素放在不同的page中

需求

让特征和动作,在对应的page中
需要新建大量的page,在手电的过程中,遇到多少个页面,就应该由多少个page文件

步骤

1、观察network脚本,跳转了多少个页面
2、设置点击更多-更多 点击 移动网络-移动网络 点击首选网络类型-首选网络类型 点击2g或3g
3、新建上述四个对应的page
4、元素的特征和动作,在哪个页面就写在哪个对应的文件中

结构

-page
-- display_page.py
-- more_page.py
-- setting.py
-- network.py


代码

setting_page.py中代码
from base.base_find import Base
from selenium.webdriver.common.by import By
class Setting(Base):
    # driver.find_element_by_xpath(BY.XPATH,"//*[@text = '显示']").click()
    display_element = By.XPATH, "//*[@text = '显示']"
    # self.driver.find_element_by_xpath("//*[@text = '更多']").click()
    more_click = By.XPATH, "//*[@text = '更多']"
    def page_display(self):
        self.base_click(self.display_element)
    def page_more(self):
        # self.driver.find_element_by_xpath("//*[@text = '更多']").click()
        self.base_click(self.more_click)
display_page中代码
from base.base_find import Base
from selenium.webdriver.common.by import By
 
class Display(Base):
    # driver.find_element_by_id("com.android.settings:id/search").click()
    look_up_click = By.ID, "com.android.settings:id/search"
    # self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
    send_keys_element = By.ID, "android:id/search_src_text"
    def page_lookup(self):
        self.base_click(self.look_up_click)
    def page_send_keys(self):
        self.base_send_keys(self.send_keys_element,"hello")
more.py中代码
from base.base_find import Base
from selenium.webdriver.common.by import By
 
class More(Base):
    # self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
    mobile_network_click = By.XPATH, "//*[@text = '移动网络']"
    # self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
 
    def page_mobile_network(self):
        # self.driver.find_element_by_xpath("//*[@text = '移动网络']").click()
        self.base_click(self.mobile_network_click)
network.py中代码

from base.base_find import Base
from selenium.webdriver.common.by import By
class Network(Base):
    network_type_click = By.XPATH, "//*[@text = '首选网络类型']"
    # self.driver.find_element_by_xpath("//*[@text = '3G']").click()
    type_3G_click = By.XPATH, "//*[@text = '3G']"
    # self.driver.find_element_by_xpath("//*[@text = '2G']").click()
    type_2G_click = By.XPATH, "//*[@text = '2G']"
    def page_network_type(self):
        # self.driver.find_element_by_xpath("//*[@text = '首选网络类型']").click()
        self.base_click(self.network_type_click)
    def page_type_3G(self):
        # self.driver.find_element_by_xpath("//*[@text = '3G']").click()
        self.base_click(self.type_3G_click)
    def page_type_2G(self):
        # self.driver.find_element_by_xpath("//*[@text = '2G']").click()
        self.base_click(self.type_2G_click)


page的统一入口

需求

方便测试脚本调用其他页面,而不需要每一次都在setup中创建page对象

步骤

1、在page模块中新建一个page的类
2、有多少个页面,就写多少个方法
3、每一个方法都去创建对应的page对象
4、在测试脚本中创建入口page对象,再通过这个对象调用里面的具体的页面对象,在调用动作

page包改造后结构

-page
-- display_page.py
-- more_page.py
-- setting.py
-- network.py
-- page.py

代码

page.py代码
 

#将所有page所要创建对象的方式封装在page.py文件中,作为统一入口
from page.display_page import Display
from page.more import More
from page.network import Network
from page.setting_page import Setting
 
 
class Page():
    def __init__(self,driver):
        self.driver = driver
    def setting(self):
        return Setting(self.driver)
    def display(self):
        return Display(self.driver)
    def more(self):
        return More(self.driver)
    def network(self):
        return Network(self.driver)
test_setting.py改造后代码
 

import os, sys
sys.path.append(os.getcwd())
from page.page import Page
# from page.network import Network
# from page.setting_page import Setting
from base.base_driver import init_driver
# from page.display_page import Display
# from page.more import More
 
"""
更多-移动网络-首选网络类型-点击2g
更多-移动网络-首选网络类型-点击3g
显示-搜索按钮-输入hello-点击返回
"""
class TestSetting:
    #所有创造对象的步骤全部在page对象中完成
    def setup(self):
        self.driver = init_driver()
        # self.display = Display(self.driver)
        # self.more = More(self.driver)
        # self.setting = Setting(self.driver)
        # self.network = Network(self.driver)
        self.page = Page(self.driver)
    def teardown(self):
        self.driver.quit()
    def test_setting01(self):
        self.page.setting().page_more()
        self.page.more().page_mobile_network()
        self.page.network().page_network_type()
        self.page.network().page_type_3G()
    def test_setting02(self):
        self.page.setting().page_more()
        self.page.more().page_mobile_network()
        self.page.network().page_network_type()
        self.page.network().page_type_2G()
    def test_setting03(self):
        self.page.setting().page_display()
        self.page.display().page_lookup()
        self.page.display().page_send_keys()


总结

工作中的步骤

1、把之前的base和ini复制到新项目中
2、创建page和scripts模块
3、先写page,考虑手指测试的过程需要用到多少页面,就在page中创建多少哥文件
4、这个页面上的特征和对这个特征的动作
5、记得:page需要继承base_action
6、page统一入口,有多少个页面,就写所烧个函数,并创建对应的对象
7、回到test脚本,setup中连接手机并获取driver对象,创建一个page的入口的对象,并且传入刚刚获取的driver
8、在test函数中,调用不同的页面的不同的动作

模块在项目中的角色

scripts注重先后顺序 数据
page注重UI、动作(当前页面,其他页面不用的)
base_action动作(所有的page或者其他的项目可能用到的公共的方法)

简单的写

- xx_page
-- 继承base_action
-- 类属性(特征)
-- 方法(动作)
- page
-- init传入driver
-- 方法(创建不同的page对象)
- test_xx
-- setup(连接手机、创建page对象)
-- teardown(退出驱动)
-- test脚本(调用page对象中不同的页面的不同的动作)

作为一个软件测试的过来人,我想尽自己最大的努力,帮助每一个伙伴都能顺利找到工作。所以我整理了下面这份资源,现在免费分享给大家,有需要的小伙伴可以关注【公众号:开心螺蛳粉】自提!

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。在这里插入图片描述

在这里插入图片描述

行动吧,在路上总比一直观望的要好,未来的你肯定会感谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入群:1150305204,里面有各种测试开发资料和技术可以一起交流哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值