一、开始
1.先把要做的事一步一步用注释写出来,然后再写代码。切记一定要写注释!不然回头看不懂自己写的是啥,这就尴尬了。
2.提高测试用例运行效率,减少测试用例运行时间:
Web自动化用例在编写的时候要注意用例的独立性。当然,流程性质的用例一定是关联在一起的,而且比较长比较复杂,上下用例之间是有关联的,那就必须关联起来。
目前,每个用例都有去打开浏览器,访问网址,然后登陆,tearDown()的时候关闭。
def setUp(self):
# 前置 访问登陆页面
self.driver=webdriver.Chrome()
self.driver.get(CD.web_login_url)
self.lg=LoginPage(self.driver)
def tearDown(self):
#后置
self.driver.quit()
3.能不能在所有用例执行之前只打开一次浏览器,在所有用例执行之后,关闭浏览器呢?
可以,但是必须考虑中间某一个用例失败了是否会影响下一个用例的运行?必须考虑好这样情况发生后,任何其它用例都不会受到影响。
想做到所有用例执行之前只访问网页一次,所有用例执行完成以后只关闭一次,就必须符合以下条件:
实际上,每个测试用例的起点都是在登陆页面。
1.保证所有用例在运行的时候,起点是在登陆页面;
2.前提是当前尚未登陆成功的状态;
因为异常用例都是在登陆页面,没用登陆成功的,先执行异常用例再执行正常用例,就做到了所有用例都是尚未登陆的状态。
4.但是,你的用户名和密码已经有数据输入了,怎么才能不影响下一条用例的运行?
要做到第一次访问登陆页面一样的效果。
1.所有用例运行之前,打开浏览器,访问登陆页面;
2.每一个页面操作完成之后,操作当前页面;
3.最后一个用例是登陆成功的用例。
所有用例运行之前,打开浏览器,访问登陆页面。setUp()和tearDown()做不到,它设计的目的就是每一个用例都会去执行的。
setUpClass()是每一个测试类运行的一次setUp。这个测试类下面有好几个测试用例,但是一个测试类只运行一次。setUp代表测试用例之前运行的。一个测试类当中,所有测试用例运行之前,会先执行setUpClass(),执行完之后,再去执行测试用例。
也就是说,首先执行setUpClass里面的代码-test1-test2-test3-testN-所有用例执行完之后是tearDownClass,这个时候,测试用例变成了夹心饼干。
tearDown()是每个用例做完之后可以做的事情。
不是必須setUp()和tearDown()成对出现的。可以只用tearDown()不用setUp()。这个是需要谁就用谁。测试用例中有什么样的前置就用setUp(),如果没有就不用。setUpClass()和tearDownClass()也是一样的,需要哪个就用哪个,不需要就不用,都要用就都写。
是TestCase中同样的一个方法。点击O可看到源码。
需要控制执行顺序。
import unittest
from selenium import webdriver
from PageObjects.login_page import LoginPage
from PageObjects.index_page import IndexPage
from TestDatas import Common_Datas as CD
from TestDatas import login_datas as LD
import ddt
@ddt.ddt
class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
#通过excel读取本功能当中需要的所有测试数据
print("===所有测试用例之前的,setup====整个测试用例只执行一次========")
cls.driver = webdriver.Chrome()
cls.driver.get(CD.web_login_url)
cls.lg = LoginPage(cls.driver)
pass
@classmethod
def tearDownClass(cls):
print("===所有测试用例之后的,teardown====整个测试用例只执行一次========")
cls.driver.quit()
# def setUp(self):
# # 前置 访问登陆页面
# pass
def tearDown(self):
#后置
#每一个页面操作完成之后,要刷新当前页面
self.driver.refresh()
# 正常用例-登陆成功
def test_login_1_success(self):
#步骤 输入用户名:XXx 密码XXX 点击登陆
self.lg.login(LD.success_data["user"],LD.success_data["passwd"])
#断言 首页当中-能否找到 退出 这个元素
self.assertTrue(IndexPage(self.driver).isExist_logout_ele())
#
#异常用例 -手机号格式不正确(大于11位、小于11位、为空、不在号码段) ddt\
@ddt.data(*LD.phone_data)
def test_login_0_user_wrongFormat(self,data):
# 步骤 输入用户名:XXx 密码XXX 点击登陆
self.lg.login(data["user"],data["passwd"])
# 断言 登陆页面 提示:请输入正确的手机号
#登录页面中 -获取提示框的文本内容
#比对文本内容与期望的值是否相等
self.assertEqual(self.lg.get_errorMsg_from_loginArea(),data["check"])
# @ddt.data()
# def test_login_wrongPwd_noReg(self):
#
# # 步骤 输入用户名:XXx 密码XXX 点击登陆
# # 断言 登陆页面 页面正中间提示:XXX
# # 登录页面中 -获取提示框的文本内容
# # 比对文本内容与期望的值是否相等
# pass
# #异常用例 - 用户名为空
# def test_login_noUser(self):
# self.lg.login('', 'python')
# # 步骤 输入用户名:XXx 密码XXX 点击登陆
# # 断言 登陆页面 提示:请输入手机号
# pass
#异常用例-未注册手机号
#异常用例-错误的密码
#异常用例-不输入密码
能实现这种方式有2个条件,首先必须考虑:1.每一个测试的失败,会不会影响其它用例的执行。2.如果你发现,无论如何这个问题都不好解决,或者说能解决很麻烦,就没有必要来做这种模式。
大家可以考虑下能不能实现,实现是最好的,实现不了就按照最开始讲的setUp()和tearDown(),多写点冗余的,时间多浪费点没关系。毕竟自动化代码是晚上运行的,稳定性为首要条件。
虽然做到了3次分层,但是很多网上的框架,看到别人写的框架中会有一个有意思的地方,元素定位目前是直接放在函数当中的,包括错误信息的获取,元素定位,全部放在函数当中的。
这里有个不好的地方,和测试数据的提取的方式是一样的原因:1.元素定位未必只在一个函数中用一次,有些元素定位可能在多个函数中都要用得到。2.这个页面其实不复杂。未来实际工作中不可能只有登录功能,还有其它的功能。那这个页面是比较复杂的,元素定位在几十个是很正常的。
几十个元素定位,你确认都是分布在不同的函数当中吗?
想把它分离开来就是希望能够针对性地去修改。login_page里面有元素变了,或者有操作步骤变了,那我也不想在每个函数中找元素定位方式。
二、3种方式
第一种方式,做成类的属性
记得加注释
这样写,如果只是元素定位发生变化,都不需要看下面的函数。
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
class LoginPage:
#元素定位
#用户名输入框
name_text = '//input[@name="phone"]'
#密码输入框
pwd_text = '//input[@name="password"]'
#登录按钮
login_but = '//button[text()="登录"]'
#错误提示框-登录区域
errorMsg_from_loginArea= '//div[@class="form-error-info"]'
def __init__(self,driver):
self.driver=driver
#登陆操作
def login(self,username,passwd,remember_user=True):
#输入用户名
#输入密码
WebDriverWait(self.driver,20).until(EC.visibility_of_element_located((By.XPATH,name_text)))
self.driver.find_element_by_xpath(self.name_text).send_keys(username)
self.driver.find_element_by_xpath(self.pwd_text).send_keys(passwd)
#判断一下rember_user的值,来决定是否勾选
self.driver.find_element_by_xpath(self.login_but).click()
#注册入口
def register_enter(self):
WebDriverWait(self.driver,20).until(EC.visibility_of_element_located((By.XPATH,"")))
self.driver.find_element_by_xpath("").click()
#获取错误提示信息-登录区域
def get_errorMsg_from_loginArea(self):
WebDriverWait(self.driver,20).until(EC.visibility_of_all_elements_located((By.XPATH,'//div[@class="form-error-info"]')))
return self.driver.find_element_by_xpath('//div[@class="form-error-info"]').text
#获取错误信息-页面正中间
def get_errorMsg_from_pageCenter(self):
#//div[@class="layui-layer-content"]
pass
#忘记密码
这是第一种方式,这种方式的弊端是:现在很多元素定位是Xpath,未来做项目还会用到id、css。比如现在是xpath定位,万一哪天元素多了个id,将来哪天想优化下,可能会修改定位方式。
这个地方只写了表达式没写定位类型,对应到这里的方法就是find_element_by_xpath()
。这里的函数名称是要跟元素定位表达式和定位类型保持完全一致的。 改的时候比较痛苦。
第二种方式,把元素定位类型和元素定位表达式全部都写在一起。
如果元素定位方式发生改变,下面的查找元素不受影响。改成什么类型,就用什么类型。find_element()
自动会去用的。
看find_element()
源码里有对各种方式的判断:
元素定位和元素操作互不影响。
第三种方式,把元素定位和函数的操作分开。
参考By的源码,这个类中只定义了数据,没有方法:
在PageLocators中,跟页面一一对应。
loc.
后面接的都是元素定位表达式,看名字筛选就好了。 如果是继承self.
会有一些函数名称跟它有很高的重复度,self.
的时候,要点的东西就很多了。
选的东西有点多,也有些是内置的driver
,也不记得每个元素定位是什么样的,就有点混乱。
locator是元素定位的总称。
单向调用。PageObjects调用了PageLocators。
TestCases调用了TestDatas和PageObjects。
写代码的时候特别注意不要出现双向调用。
三、代码
loginpage_locators.py
from selenium.webdriver.common.by import By
class LoginPageLocator:
#元素定位
#用户名输入框
name_text =(By.XPATH,'//input[@name="phone"]')
#密码输入框
pwd_text = (By.XPATH,'//input[@name="password"]')
#登录按钮
login_but =(By.XPATH,'//button[text()="登录"]')
#错误提示框-登录区域
errorMsg_from_loginArea= '//div[@class="form-error-info"]'
index_page.py
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
class IndexPage:
def __init__(self,driver):
self.driver=driver
def isExist_logout_ele(self):
# 等待10秒 元素有没有出现 //a[@href="/Index/logout.html"]
#如果存在就返回True,不存在就返回False
try:
WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located((By.XPATH, '//a[@href="/Index/logout.html"]')))
return True
except:
return False
login_page.py
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from PageLocators.loginpage_locators import LoginPageLocator as loc
class LoginPage:
def __init__(self,driver):
self.driver=driver
#登陆操作
def login(self,username,passwd,remember_user=True):
#输入用户名
#输入密码
WebDriverWait(self.driver,20).until(EC.visibility_of_element_located((self.name_text)))
self.driver.find_element(*loc.name_text).send_keys(username)
self.driver.find_element(*loc.pwd_text).send_keys(passwd)
#判断一下rember_user的值,来决定是否勾选
self.driver.find_element(*loc.login_but).click()
#注册入口
def register_enter(self):
WebDriverWait(self.driver,20).until(EC.visibility_of_element_located((By.XPATH,"")))
self.driver.find_element_by_xpath("").click()
#获取错误提示信息-登录区域
def get_errorMsg_from_loginArea(self):
WebDriverWait(self.driver,20).until(EC.visibility_of_all_elements_located((By.XPATH,'//div[@class="form-error-info"]')))
return self.driver.find_element_by_xpath('//div[@class="form-error-info"]').text
#获取错误信息-页面正中间
def get_errorMsg_from_pageCenter(self):
#//div[@class="layui-layer-content"]
pass
#忘记密码
test_login.py
import unittest
from selenium import webdriver
from PageObjects.login_page import LoginPage
from PageObjects.index_page import IndexPage
from TestDatas import Common_Datas as CD
from TestDatas import login_datas as LD
import ddt
@ddt.ddt
class TestLogin(unittest.TestCase):
@classmethod
def setUpClass(cls):
#通过excel读取本功能当中需要的所有测试数据
print("===所有测试用例之前的,setup====整个测试用例只执行一次========")
cls.driver = webdriver.Chrome()
cls.driver.get(CD.web_login_url)
cls.lg = LoginPage(cls.driver)
pass
@classmethod
def tearDownClass(cls):
print("===所有测试用例之后的,teardown====整个测试用例只执行一次========")
cls.driver.quit()
# def setUp(self):
# # 前置 访问登陆页面
# pass
def tearDown(self):
#后置
#每一个页面操作完成之后,要刷新当前页面
self.driver.refresh()
# 正常用例-登陆成功
def test_login_1_success(self):
#步骤 输入用户名:XXx 密码XXX 点击登陆
self.lg.login(LD.success_data["user"],LD.success_data["passwd"])
#断言 首页当中-能否找到 退出 这个元素
self.assertTrue(IndexPage(self.driver).isExist_logout_ele())
#
#异常用例 -手机号格式不正确(大于11位、小于11位、为空、不在号码段) ddt\
@ddt.data(*LD.phone_data)
def test_login_0_user_wrongFormat(self,data):
# 步骤 输入用户名:XXx 密码XXX 点击登陆
self.lg.login(data["user"],data["passwd"])
# 断言 登陆页面 提示:请输入正确的手机号
#登录页面中 -获取提示框的文本内容
#比对文本内容与期望的值是否相等
self.assertEqual(self.lg.get_errorMsg_from_loginArea(),data["check"])
# @ddt.data()
# def test_login_wrongPwd_noReg(self):
#
# # 步骤 输入用户名:XXx 密码XXX 点击登陆
# # 断言 登陆页面 页面正中间提示:XXX
# # 登录页面中 -获取提示框的文本内容
# # 比对文本内容与期望的值是否相等
# pass
# #异常用例 - 用户名为空
# def test_login_noUser(self):
# self.lg.login('', 'python')
# # 步骤 输入用户名:XXx 密码XXX 点击登陆
# # 断言 登陆页面 提示:请输入手机号
# pass
#异常用例-未注册手机号
#异常用例-错误的密码
#异常用例-不输入密码
Common_Datas.py
#全局-系统访问地址-登录链接
web_login_url="http://120.78.128.25:8765/Index/login.html"
login_datas.py
#正常场景-测试数据
success_data={"user":"18684720553","passwd":"python"}
#异常用例-手机号格式不正确(大于11位、小于11位、为空、不在号码段)
phone_data=[
{"user":"18684720","passwd":"python","check":"请输入正确的手机号"},
{"user":"18684720553123","passwd":"python","check":"请输入正确的手机号"},
{"user":"","passwd":"python","check":"请输入手机号"},
{"user":"11684720553","passwd":"python","check":"请输入正确的手机号"}
]
#异常用例
# layui-layer-content 此账号没有经过授权,请联系管理员! //div[@class="layui-layer-content"]
此代码未运行,代码未写完,未完待续~
代码截图:
四、总结代码优化了3点
1.数据分离-TestDatas
为什么要做数据分离?
1)多环境切换。
2)数据公用。
3)好维护。如果有多个环境,我可以统一修改。
如果有公共数据,我就准备一份就好啦。无论是模块级别的公共数据还是整个测试系统的公共数据,降低重复度,方便管理。
2.测试用例-引用ddt
降低了用例的重复度。
3.优化了执行效率:
setUpClass
tearDownClass
之前是每条用例都要打开页面,关闭页面。现在是执行全部用例前打开一次,执行全部用例后关闭。但是要保证每条用例间互不影响。
4.元素定位和函数分离:元素定位类型和表达式用元组来管理-PageLocators层。
五、问题总结
1.写自动化代码的顺序
先把页面封装起来,页面封装起来的时候必须依赖于测试用例的分析和业务功能的分析。实际过程中,不会先写用例,会先把页面封装。页面封装完成之后,再去写测试用例。
都已经准备好了,用例里面直接调用就行了。
在页面封装的过程中,元素定位和页面功能是一起实现的。先把元素定位准备好,再去写页面功能。
如果哪些元素定位是当时没定位好的,再去补就好了。这种模式下,在哪个页面补都是可以的,不影响其它部分。没有的都可以在上面追加,每一块都是可以这样做的。都不影响已写好的部分,也不需大改。
2.注意
在不清楚页面封装的情况下,最好的方式是:把测试用例用注释的方式写出来(不需要写代码),然后再一步一步补上代码。
3.Python框架和Python自动化框架有什么区别?
都是框架,方向不同。Python框架包含unittest
Python自动化框架目的非常明确是做项目级别的自动化测试的。
4.做自动化要执行那么多异常用例吗?
先执行正常的用例,如果是非常简单的异常用例就写。看情况,时间上安排得过来再去写异常的用例。
5.三次错误密码,会有验证码,这块怎么处理?
绕过验证码,3次错误密码,再写个用例对密码重试。
3次错误密码这个做不做自动化,看情况。
6.短信验证码去数据库查。
7.回归用例要不要有异常用例,因人因公司因项目而异。
欢迎扫码关注!