1 PO模式

1.1 PO模式介绍

Page Object Model

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

(将项目分为page.py和test.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.py和test_display.py这两个文件就可以修改一下了,如下:

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

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

分离测试脚本

什么叫分离测试脚本?

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

这样做的好处是:

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

继续修改目录:

新建了一个page包,包里创建两个文件:display_page.py\network_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

如何将自己的测试脚本分离成PO模式的测试框架的更多相关文章

  1. 【NO.11】Jmeter - 构建1个可供Linux使用的Jmeter测试脚本 - 共3个步骤

    在Linux使用Jmeter做性能测试需要4个前提条件,这4个前提条件已经在之前的文档里提到了,重复一下加深印象: (1) 在本地已安装xshell 参考<SecureCRT-转换密钥-Xshe ...

  2. 测试脚本配置、ORM必知必会13条、双下划线查询、一对多外键关系、多对多外键关系、多表查询

    测试脚本配置 ''' 当你只是想测试django中的某一个文件内容 那么你可以不用书写前后端交互的形式而是直接写一个测试脚本即可 脚本代码无论是写在应用下的test.py还是单独开设py文件都可以 ' ...

  3. Web 2.0 浏览器端可靠性测试第1部分(浏览器端可靠性测试的概念和背景)

    Web 2.0 是一个体现当代网络技术发展趋势的流行概念.它使得基于 Web 的信息交互和用户间协作性更加灵活和丰富.很多的社交网站.博客.wiki,都是 Web 2.0 技术的典型应用. 我们知道, ...

  4. 使用py2exe将python脚本转换成exe可执行文件

    Python(wiki en  chs)是一门弱类型解释型脚本语言,拥有动态类型系统和垃圾回收功能,支持多种编程范式:面向对象.命令式.函数式和过程式编程. 由于Python拥有一个巨大而广泛的标准库 ...

  5. Jmeter(七)Jmeter脚本优化(数据与脚本分离)

    午休时间再来记一记,嗯..回顾着使用Jmeter的历程,想着日常都会用到的一些功能.一些组件:敲定了本篇的主题----------是的.脚本优化. 说起脚本优化,为什么要优化?又怎么优化?是个永恒的话 ...

  6. seleniumGrid分布式远程执行测试脚本

    执行UI自动化测试脚本时,不仅可以在本地执行,也可以远程指定某计算机执行测试脚本,seleniumGrid为我们提供了这类服务,但还需要自己搭建环境. 一.本地计算机需要准备java环境和seleni ...

  7. ajax提交文件,django测试脚本环境书写,froms组件,钩子函数

    1.在新版本中,添加app是直接在settings设置中,将INSTALLED_APPS里添加app名字, 但是他的完整写法是   'app01.apps.App01Config'  因为新版本做了优 ...

  8. Java&Selenium自动化测试实现页面元素、页面对象及测试代码分离

    一.摘要 本篇博文将介绍自动化测试实现页面元素.页面对象及测试代码分离在自动化框架中的实现 二.解析页面元素定位信息 首先,将页面元素与实际的代码分离,首先我们将页面元素定位信息和定位表达式保存在属性 ...

  9. Appium+Python之测试数据与脚本分离

    如果脚本中有很多的魔法数据,那代码的复用性就不会很高,所以我们需要将测试数据和脚本分离. 思路:将测试数据放在一个json文件中,然后写一个读取json文件的基类,测试用例中通过调基类中方法来获取js ...

随机推荐

  1. unittest(7)-作业- 全局变量传递cookie

    全局变量存储cookie 测试类中有多个测试函数 # 1.http_requset.py import requests class HttpRequest: def http_request(sel ...

  2. Java正则表达式java.util.regex类的简单使用

    1.什么是正则表达式? 正则表达式(regular expression)是根据字符串集合内每个字符串共享的共同特性来描述字符串集合的一种途径.正则表达式可以用于搜索.编辑或者处理文本和数据. Jav ...

  3. <NOIP2005提高T2>过河の思路

    emm又一道dp dp真有趣(你的良心呢?!!! Description 在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧.在桥上有一些石子,青蛙很讨厌踩在这些石子上.由于桥的长度和青蛙一 ...

  4. javascript学习内容

    http协议 犀牛书 MDN js单线程 let只在代码块内有效 es5只有全局作用域 const变量指向的内存地址不得改动,值不能保证不变 全局变量不加var node.js 更改连接到服务器的方式 ...

  5. JAVA:初识Java · Xer97

    1. 什么是Java Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. Java语言作为静 ...

  6. 从846家初创倒下 看A轮融资后的悬崖

    看A轮融资后的悬崖" title="从846家初创倒下 看A轮融资后的悬崖"> 相比往年,今年的寒冷冬天来得更早.在互联网行业,今年的"大雪"更 ...

  7. LeetCode~1033.移动石子直到连续

    1033.移动石子直到连续 三枚石子放置在数轴上,位置分别为 a,b,c. 每一回合,我们假设这三枚石子当前分别位于位置 x, y, z 且 x < y < z.从位置 x 或者是位置 z ...

  8. Flash之后是不是该IE浏览器了

    Flash死亡,Adobe推荐大家拥抱HTML5. 其实Flash本身也是支持手机端的. 一.Flash宣告死亡 Adoebe官方网站发布了公告,2020年12月30日起终止支援Flash.目前Chr ...

  9. Logstash实践

    转载请注明出处:https://www.cnblogs.com/shining5/p/9542710.html Logstash简介 一个开源的数据收集引擎,具有实时数据传输能力,可以统一过滤来自不同 ...

  10. sql服务器第5级事务日志管理的阶梯:完全恢复模式下的日志管理

    sql服务器第5级事务日志管理的阶梯:完全恢复模式下的日志管理 原文链接http://www.sqlservercentral.com/articles/Stairway+Series/73785/ ...