二、PO或POM
1.POM:PageObjectModel页面对象模型,指将每一个页面(网页)都设计为一个对象,抽象为一个类来实现这个对象的描述,网页里的操作都可以设计为该类里的方法,这些方法可以被多个模块的测试用例代码文件来调用,从而实现代码的复用。
2.应用场景:在某一个网页里的定位操作等步骤的代码在多个测试用例代码中都需要实现。
3.POM优势:在被测系统版本升级时,代码更容易维护和管理。
4.PO与测试类父类MyBase的相同点和区别点:
(1)相同点:为了代码的复用而设计的类,都可以在类里设计通用的函数。
(2)区别点:MyBase类里只写与被测系统的业务没有关系的通用函数,比如判断元素是否存在,判断弹出框是否存在。而PO里所封装的函数都是与被测系统的业务有密切关系的,业务不同,类里的函数也会不同,比如只有登录网页类里才需要封装登录的函数,ECShop登录与淘宝登录函数体里的具体步骤也会不同。
5.案例:ECShop后台登录页---> BGLoginPage类
(1)复制ecshopv1包,粘贴到seleniumproject1,修改为ecshopv2,为了让ecshopv2不依赖于ecshopv1存在而运行,所以建议把case包和suite包里的文件开头处的导入语句from ecshopv1……全都修改为from ecshopv2……
(2)在ecshopv2上新建子包page,用于存储所有网页对象模块文件。
(3)在page包里新建python file,名称bg_login_page.py,文件里面定义类,类名是BGLoginPage
(4)在类BGLoginPage里定义几个函数:
a.构造函数:给类变量driver赋值为传入进来的driver,后续会由调用网页类的测试用例类来传入,另外,也可以定义其他类变量,比如url代表当前网页网址。
示例:
def __init__(self,driver):
self.driver=driver
self.url="http://localhost/upload/admin/index.php"
b.open函数:无参,打开类变量url所指定的当前网页。
c.
bg_login函数:登录操作动作,包括几个具体操作步骤,传入的参数
有用户名数据、密码数据、验证码数据。
d.get_pw函数:获得密码文本框里的当前实际数据内容,无参,返回值是str类型的文本框内容。
注意:page层里的网页类不负责检查数据,负责获得实际数据,case层负责检查实际数据是否与预期数据一致。
示例:
# 获得密码文本框的当前内容
# 返回值:str
def get_pw(self):
mi = self.driver.find_element(By.NAME, "password")
return mi.get_attribute("value")
(5)改造后台登录测试用例代码:case包里的bg_login_case.py
a.从ecshopv2下page包里bg_login_page文件模块里导入BGLoginPage类
from ecshopv2.page.bg_login_page import BGLoginPage
b.在测试函数中,需要调用网页类里的函数之前,先实例化网页类对象,传入self.driver作为构造函数的实参。
示例:
# 实例化后台登录页类对象
blp=BGLoginPage(self.driver)
c.实例化网页类以后,就可以调用该类里的操作动作函数来完成相应操作,如果需要参数,传入实际测试数据。
示例1:
把self.driver.get("http:xxcxxx/xxxx")修改为:
# 打开ECShop后台登录页
blp.open()
好处:以后各个版本被测系统做回归测试时,一旦后台登录页网址有变化,我们不需要打开各个测试用例类文件来修改,只需要打开网页类文件,修改一处即可。
示例2:
# 输入三个文本框数据c,d,e,来点击“进入管理中心”按钮
blp.bg_login(c,d,e)
示例3:
# 检查登录页里的密码文本框为空
c3=blp.get_pw()
self.assertEqual("",c3,msg="密码文本框不为空")
6.案例:在后台商品列表里用不同的关键字来搜索,检查搜索结果,设计测试用例代码。
(1)case包里新建Python unit test类型的文件bg_goods_list_case.py,修改类名,从MyTestCase改为BGGoodsListCase,导入MyBase后,把父类修改为MyBase
,把测试函数名称从test_something修改为test_search负责测试搜索商品的功能。
示例:
from ecshopv2.base.mybase import MyBase
class BGGoodsListCase(MyBase):
def test_search(self):
(2)准备测试数据:导入ddt里的ddt和data,把搜索的关键字5,8两个值传入到测试函数中。
示例:
from ddt import ddt,data
@ddt
class BGGoodsListCase(MyBase):
@data("5","8")
def test_search(self,k):
(3)导入后台登录页类,然后在测试函数中实例化BGLoginPage,然后调用open函数打开后台登录页,再调用bg_login函数做登录操作,参数写正确的用户名admin、
正确密码admin123和万能验证码0。
示例:
from ecshopv2.page.bg_login_page import BGLoginPage
@ddt
class BGGoodsListCase(MyBase):
@data("5","8")
def test_search(self,k):
# 打开后台登录页后,用正确账号登录进去
blp=BGLoginPage(self.driver)
blp.open()
blp.bg_login("admin","admin123","0")
(3)登录后,实现点击左侧“商品列表”后,在右侧输入关键字,点击“搜索”,然后逐行检查搜索结果中商品名称或商品货号包含搜索关键字。
a1=self.driver.find_elements(By.XPATH,".//*[@id='listDiv']/table[1]/tbody/tr")
for i in range(3,len(a1)+1):
ming=self.driver.find_element(By.XPATH,".//*[@id='listDiv']/table[1]/tbody/tr[%d]/td[2]/span"%i)
t2=ming.text
hao = self.driver.find_element(By.XPATH, ".//*[@id='listDiv']/table[1]/tbody/tr[%d]/td[3]/span"%i)
t3=hao.text
self.assertTrue((k in t2) or (k in t3))
7.ecshopv2代码已经把后台登录页单独设计为一个类来进行管理,所以我们的测试用例case文件里不会再出现后台登录页里的具体操作步骤,所有定位和操作的基本
内容都移到page层来实现。
8.优化需求:把网页类里的定位和操作分开,有利于在版本升级时维护修改定位条件。
(1)复制ecshopv2包,粘贴到seleniumproject1,修改为ecshopv3,为了让ecshopv3不依赖于ecshopv2存在而运行,所以建议把case包和suite包里的文件
开头处的导入语句from ecshopv2……全都修改为from ecshopv3……
(2)打开page包里的bg_login_page.py,设计一些该类的属性变量来描述各个元素的定位条件,因为元素的定位条件需要两个值来表示(By.XX,"xxx"),所以
使用元组类型来存储定位条件,赋值给变量,将赋值语句写在构造函数里。
注意:这些变量需要是类变量,所以需要加self.
示例:
def __init__(self,driver):
self.driver=driver
self.url="http://localhost/upload/admin/index.php"
# 用户名文本框的定位条件
self.yong_locator=(By.NAME, "username")
# 密码文本框的定位条件
self.mi_locator=(By.NAME, "password")
# 验证码文本框的定位条件
self.yan_locator=(By.NAME, "captcha")
# 进入管理中心按钮的定位条件
self.jin_locator=(By.XPATH, "//input[@value='进入管理中心']")
(3)修改网页类的操作动作函数:将find_element后小括号里的定位条件换为前面所准备好的变量。
注意:为了将元组里的两个值拆分出来,作为find_element的两个参数,需要做元组类型的变量的解包处理,用*写在变量名称前面。
示例:
mi = self.driver.find_element(By.NAME, "password")
修改为:
mi = self.driver.find_element(*self.mi_locator)
9.优化需求:把网页类里的定位条件从page层提取出来,写入单独的文件。
(1)复制ecshopv3包,粘贴到seleniumproject1,修改为ecshopv4,为了让ecshopv4不依赖于ecshopv3存在而运行,所以建议把case包和suite包里的文件
开头处的导入语句from ecshopv3……全都修改为from ecshopv4……
(2)在ecshopv4包新建子包locator,用于存储各个网页里的元素定位条件的类文件。
(3)在子包locator里新建python文件bg_login_locator.py,定义一个类,名称是BGLoginLocator,用于记录后台登录页的所有元素的定位条件。
(4)该类里定义构造函数,构造函数中书写后台登录页里所有元素定位条件的类属性变量。
示例:
from selenium.webdriver.common.by import By
class BGLoginLocator():
def __init__(self):
# 用户名文本框的定位条件
self.yong_locator=(By.NAME, "username")
# 密码文本框的定位条件
self.mi_locator=(By.NAME, "password")
# 验证码文本框的定位条件
self.yan_locator=(By.NAME, "captcha")
# 进入管理中心按钮的定位条件
self.jin_locator=(By.XPATH, "//input[@value='进入管理中心']")
(5)改造page包里的网页对象类:导入BGLoginLocator类,修改BGLoginPage的构造函数,删除原来的定位条件属性变量,增加实例化BGLoginLocator的步骤
,把实例化出来的定位对象赋值给BGLoginPage类里的属性loc变量。
示例:
from ecshopv4.locator.bg_login_locator import BGLoginLocator
class BGLoginPage():
def __init__(self,driver):
self.driver=driver
self.url="http://localhost/upload/admin/index.php"
# 实例化BGLoginLocator
self.loc=BGLoginLocator()
(6)继续改造page包里的网页对象类:把BGLoginPage操作动作的函数中find_element的参数都从当前Page类的属性改为Locator类的属性。
示例:
yong = self.driver.find_element(*self.yong_locator)
修改为:
yong = self.driver.find_element(*self.loc.yong_locator)
(7)如果case层里的测试用例中做检查时也需要使用网页里元素定位条件,那么也可以通过page引用到locator。
比如:case包里bg_login_case.py,该文件里检查元素出现时涉及到后台登录页元素定位条件。
示例:
# 检查跳转回登录页:检查用户名文本框出现
b62=self.is_element_present(By.NAME,"username")
修改为:
b62=self.is_element_present(*blp.loc.yong_locator)
三、总结自动化测试代码架构
1.基于python + selenium webdriver + unittest + htmltestrunner + csv + ddt + po这些技术我们设计了自己的自动化测试代码框架。以后大家可
以补充发送邮件、记录日志等功能,也可以把这些代码使用版本管理软件工具(例如svn、git)来管理多个版本的测试代码,也可以使用CI(持续集成工具比如jenkins
等来定时执行测试代码)。
2.如何管理我们的测试代码:采用分层设计的思想,suite---case---page---locator,case---data---util,suite---util,suite---report,case---base。
(1)suite:测试套件层,批量运行多条测试用例。
(2)case:测试用例层,设计实现测试用例测试代码。
(3)page:网页对象层,负责实现网页里的操作。
(4)locator:网页元素定位层,负责实现元素定位条件。
(5)data:测试数据层,存储大量的测试数据文件。
(6)util:工具层,负责给case或suite层代码提供基础工具。
(7)base:测试用例基础层,存储测试用例类的父类,提供基础通用功能。
(8)report:测试报告层,存储suite运行后所生成的测试结果报告文件。