UI自动化测试之页面对象设计模式

1、概述

1.1页面对象设计模式的优势

页面对象设计模式(page object)又叫po模式,PO模式是一种自动化测试设计模式,将页面定位和业务操作分开,也就是把对象定位和测试脚本分开,从而提供可维护性。核心思想是通过对界面元素的封装减少冗余代码,主要体现在对界面交互细节的封装,也就是在实际测试中只关注业务流程;同时在后期维护中,若元素定位发生变化, 只需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。在自动化测试过程中,对于维护的成本而言,需要考虑进一步的优化,那么我们可以使用页面对象设计模式,它的具体优势如下:

(1)创建可以跨多个测试用例共享的代码

(2)减少重复代码的数量

(3)如果用户界面发生了维护,我们只需要维护一个地方,这样修改以及维护的成本相对而言是比较低的。

1.2创建项目

在pycharm中创建项目的步骤如图:

 

 然后点击attch即可在project中看到新创建的项目:

 

 给已经创建好的项目配置python解释器:

 2、页面对象设计

2.1目录结构设计

编写的目录顺序可自行调整,如下图:

各个目录详解:

(1)base:基础层,主要编写底层定位元素的类,它是一个包。

(2)common:公共类,里面编写公共使用到的方法。

(3)config:配置文件存储目录。

(4)data:存储测试使用到测试数据。

(5)page:对象层,编写具体的业务逻辑,把页面每一个操作行为单独的写一个方法或者是函数。

(6)report:测试报告目录,主要用来存放测试报告。

(7)test:测试层,里面主要是测试模块,也可以说是每个测试的场景的代码。

(8)utils:工具类,存放工具,如文件处理、说明文档等。

(9)run:运行层:整个自动化测试的运行目录。

2.2具体页面设计模式

这里以新浪邮箱为例,来说明整个页面设计的具体目录如何实现的代码。

2.2.1基础层:base

该目录是基础层,主要编写底层定位元素的类,它是一个包;元素定位有两类,单个元素定位和多个元素定位。以及一种特殊的情况,定位iframe框架中的元素,我们之前已经说过,进入iframe框架有三种方式:ID、name和索引,这里简述通过ID进入iframe框架的方式,base层里的东西,适用于所有场景,即无论做什么产品的自动化测试,都可以直接进行调用:

#base:基础层,主要编写底层定位元素的类,它是一个包;元素定位有两类,单个元素定位和多个元素定位,还有一种特殊情况,即需要定位的元素在iframe框架中。
from selenium.webdriver.support.expected_conditions import NoSuchElementException      #显示异常信息要导入的类
#定义一个编写底层定位元素的类
class WebUI(object):
	def __init__(self,driver):
#=driver里的drive可以理解为webdriver实例化后的对象,这里只是做一个假设,在测试类中进行验证,
		self.driver=driver

		'''单个元素定位的方式
			args:*args:识别元素属性,ctr+鼠标放置到元素+左键:判断定位元素属性的方法是否正确
			:return:它是一个元组,需要带上具体什么方式定位元素属性以及元素属性的值'''

#单个元素定位:
	# def findElement(self,*args):
	# 	return self.driver.find_element(*args)

	# 如果想要获取到运行异常是的详细信息,可以使用函数异常逻辑,except返回具体错误信息。
	def findElement(self,*args):
		try:
			return self.driver.find_element(*args)
		except NoSuchElementException as e:
			return e.args[0]

		'''单个元素定位的方式
			args:*args:识别元素属性,ctr+鼠标放置到元素+左键:判断定位元素属性的方法是否正确
			index:识别元素的索引
			:return:它是一个元组,需要带上具体什么方式定位元素属性以及元素属性的值'''

# #多个元素定位,由于需要使用到他的索引,所以需要加一个index参数。
# 	def findElements(self, *args, index):
# 		return self.driver.find_elements(*args)[index]

# 如果想要获取到运行异常是的详细信息,可以使用函数异常逻辑,except返回具体错误信息。
	def findElements(self,*args,index):
		try:
			return self.driver.find_elements(*args)[index]
		except NoSuchElementException as e:
			return e.args[0]

#iframe框架中的元素定位,这里简述通过ID的方式进入iframe框架:
	def findFrame(self,frameID):
		return self.driver.switch_to.frame(frameID)

2.2.2对象层:page

该目录是对象层,编写具体的业务逻辑,把页面每一个操作行为单独的写一个方法或者是函数,他是一个包。

#page:对象层,编写具体的业务逻辑,把页面每一个操作行为单独的写一个方法或者是函数。如下:
from selenium.webdriver.common.by import By    #By:具体元素定位需要用到的类
from base.base import WebUI    #导入base层,也就是基础层,WebUI元素定位的类。
import time    #关于时间的库
#定义一个登录的类:username、password、loginEnter、errorPrompt是登录的类中的数据属性,可以在整个类中调用,调用的方式为:self.数据属性。
class Login(WebUI):   #要定位元素,所以需要继承WebUl类。
	username=(By.ID,"freename")
	password=(By.ID,"freepassword")
	loginEnter=(By.CLASS_NAME,"loginBtn")
	errorPrompt=(By.XPATH,"/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]")

#用户名:输入用户名这个操作的函数
	def InputUsername(self,username):
		time.sleep(3)
		self.findElement(*self.username).send_keys(username)

#密码:输入密码这个操作的函数
	def InputPassword(self,password):
		time.sleep(3)
		self.findElement(*self.password).send_keys(password)

#点击登陆这个操作的函数
	def loginClick(self):
		self.findElement(*self.loginEnter).click()

#获取提示框文本信息的函数
	def textInformation(self):
		return self.findElement(*self.errorPrompt).text

#封装:把新浪输入用户名和密码后点击登录的操作封装为一个函数。
	def sinaLogin(self,username,password):
		self.InputUsername(username=username)
		self.InputPassword(password=password)
		self.loginClick()

2.2.3测试层:test

该目录是测试层,里面主要是测试模块,也可以说是每个测试的场景的代码。

#test:测试层,里面主要是测试模块,也可以说是每个测试的场景的代码。
from selenium import webdriver   #自动化测试必须导入的类。
import unittest    #测试固件所用到的方法所在的库。
from page.login import Login   #导入登陆的类Login
import time   #关于时间的库
#定义一个测试类:
class UnitTest(unittest.TestCase,Login):
#初始化
	def setUp(self) -> None:
		self.driver=webdriver.Chrome()    #这里是对webdriver进行实例化,也是对基础层中driver假设的证明。
		self.driver.maximize_window()
		self.driver.get("https://mail.sina.com.cn/")
		self.driver.implicitly_wait(30)
#清理
	def tearDown(self) -> None:
		self.driver.quit()

#测试模块:登陆的用户名和密码为空
	def test_login_Empty(self):    #测试用例的名称一定要以test开头,推荐test_。
		self.InputUsername(username="")   #调用page中的输入用户名的函数
		self.InputPassword(password="")   #调用page中的输入密码的函数
		self.loginClick()     #调用page中点击登录的函数
		# self.sinaLogin(username="",password="")     #调用在page中封装的函数sinaLogin()。
		time.sleep(5)
		self.assertEqual(self.textInformation(),"请输入邮箱名")    #断言,验证登录提示的文本信息,调用page中的获取提示框文本信息的函数
		time.sleep(5)

#测试场景:新浪邮箱的登陆
	def test_login_format(self):    #测试用例的名称一定要以test开头,推荐test_。
        '''验证登陆的用户名和密码格式不对'''     #测试用例的注释,测试用例一定要有注释。
		self.InputUsername(username="asd123")      #调用page中的输入用户名的函数
		self.InputPassword(password="asd123")     #调用page中的输入密码的函数
		self.loginClick()     #调用page中点击登录的函数
		# self.sinaLogin(username="asd123",password="asd123")    #调用在page中封装的函数sinaLogin()。
		time.sleep(5)
		self.assertEqual(self.textInformation(),"您输入的邮箱名格式不正确")    ##断言,验证登录提示的文本信息,调用page中的获取提示框文本信息的函数
		time.sleep(5)


	def test_login_error(self):    #测试用例的名称一定要以test开头,推荐test_。   #调用在page中封装的函数sinaLogin()。
        '''验证登陆的用户名和密码错误'''     #测试用例地注释。
		self.InputUsername(username="asd123@sina.com")    #调用page中的输入用户名的函数
		self.InputPassword(password="asd123")     #调用page中的输入密码的函数
		self.loginClick()     #调用page中点击登录的函数
		# self.sinaLogin(username="asd123@sina.com",password="asd123")
		time.sleep(5)
		self.assertEqual(self.textInformation(),"登录名或密码错误")     ##断言,验证登录提示的文本信息,调用page中的获取提示框文本信息的函数
		time.sleep(5)

2.2.4公共类:common

这个目录里面编写公共使用到的方法,如路径处理,即获取当前文件目录,该目录也通用于所有测试场景,但前提是po设计模式的目录结构如上图所示。

import os  #路径处理的库
def base_dir():  #获取当前文件的目录的上一级目录,也就是获取整个自动化测试的Po设计模式是在哪个目录下。
	return os.path.dirname(os.path.dirname(__file__))   
print(base_dir())

2.2.5配置文件存储目录:config

该目录主要用来储存测试中的配置文件。

2.2.6工具类:utils

这个目录主要用来存放我们测试中用到的一些工具,比如数据驱动时关于json文件的处理、整个测试的说明文档等。该目录也适用于所有的测试场景,但是需要修改对应的文件名,如上所述,适用的前提是po设计模式的目录结构如上图所示。

import json  #处理json文件必须要导入的库
from common.public import base_dir   #导入获取自动化测试po设计模式的路径的类
import os   #路径处理的库
def readJson():   #定义一个返回读取的自动化测试po设计模式data目录下sina.json文件的内容
	return json.load(open(file=os.path.join(base_dir(),"data","sina.json"),encoding="utf-8"))   #json文件反序列化的过程。

 2.2.7数据层:data

该目录下主要存放测试使用到的数据,一般是数据驱动时分离出来的数据,储存在json文件中,该层需要注意的就是json文件中的内容始终是一个字典的形式。

{
  "addressEmpty":"请输入邮箱名",
  "passwordEmpty":"请输入密码",
  "confirm":"请再次输入密码"
}

 2.2.8报告层:report

该目录主要时用来存放测试报告的。

2.2.9运行层:run

该目录是运行测试用例的目录,我们可以根据测试模块来运行,也可以运行所有模块。该层的内容也适用于所有场景,如上所述,适用的前提是po设计模式的目录结构如上图所示。

import HTMLTestRunner   #生成测试报告必须要用到的库。
import os    #处理路径的库。
import unittest   #加载测试模块要用到的库。
import time  #时间处理的库。

#按模块来执行:
if __name__=="__main__":
#start_dir=加载所有的测试模块来执行,pattern通过正则的方式获取到要执行的模块。
    pathTest = os.path.dirname(os.path.dirname(__file__))    #获取当前路径的上一级目录,也就是自动化测试po设计模式所在的目录,这里简称位主目录。
	#(os.path.join(pathTest, "test"))路径拼接,加载的是主目录下的test层中的模块。
    suite = unittest.TestLoader().discover(start_dir=(os.path.join(pathTest, "test")),pattern="test_sina.py")
	unittest.TextTestRunner().run(suite)

#加载所有的测试模块来执行:执行用到的是unittest库里的TextTestRunner类中的run方法。
#第一种方式:
if __name__=="__main__":
#start_dir=加载所有的测试模块:用到的是unittest库下的TestLoader类中的discover方法,pattern通过正则的方式获取到要执行的模块,*表示所有。
	suite =unittest.TestLoader().discover(start_dir=(os.path.join(pathTest, "test")), pattern="test_*.py")   #加载要执行的测试模块。
	unittest.TextTestRunner().run(suite)   #执行加载到的测试模块。

#第二种方式:
def getSuite():
	suite = unittest.TestLoader().discover(start_dir=os.path.dirname(__file__), pattern="test_*.py")
	return suite
if __name__ == '__main__':
	unittest.TextTestRunner().run(getSuite())


#生成自动化测试报告:

#加载所有的测试模块并返回:unittest库下的TextTestRunner()类中的run方法。
def getSuite():
	#start_dir=加载所有的测试模块来执行,pattern通过正则的方式获取到要执行的模块
	pathTest = os.path.dirname(os.path.dirname(__file__))    #获取主目录。
	#获取所有的测试模块:unittest库下的TestLoader()类中的discover的方法。
    #(os.path.join(pathTest, "test"))路径拼接,加载的是主目录下的test层中的模块。
	suite = unittest.TestLoader().discover(start_dir=(os.path.join(pathTest, "test")), pattern="test_*.py")
	return suite

#获取当前时间,由于python格式的限制,当前时间最好采用下面的方式:
def getNowTime():
	return time.strftime("%y-%m-%d %H_%M_%S",time.localtime(time.time()))

#执行获取的测试模块,并获取测试报告
def sinaReport():
	#获取当前路径的上一级目录,即主目录。
	reportPath=os.path.dirname(os.path.dirname(__file__))
#定义文件名和文件储存的路径,文件的储存路径为:reportPath,"report",定义文件名为当前时间+report.html,主要是为为了区分不同时间段生成的测试报告。
	filename=os.path.join(reportPath,"report",getNowTime()+"report.html")
	time.sleep(3)
#把测试报告写入文件中,b是以二进制的方式写入
	fp=open(filename,"wb")
	time.sleep(3)
	#HTMLTestRunner实例化的过程,stream是流式写入,title是测试报告的标题,description是对测试报告的描述
	runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title="新浪邮箱",description="新浪邮箱自动化测试")
	time.sleep(3)
	#运行所有测试套件:HTMLTestRunner里的run方法。
	runner.run(getSuite())
	time.sleep(3)

#主函数:执行的入口
if __name__ == '__main__':
	sinaReport()

 

posted @ 2022-04-12 20:13  柒の夜  阅读(225)  评论(0编辑  收藏  举报