本篇文章主要讲解两方面的内容:
- 模块化驱动测试。
- 数据驱动测试。
一、模块化驱动测试
一个abc.py的文件就是一个名字叫abc的模块,一个xyz.py的文件就是一个名字叫xyz的模块。
先不使用模块化,模拟登录退出,脚本如下:
scriptOne.py
#coding=utf-8
from selenium import webdriver
#引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image,ImageEnhance
import pytesseract
import time
driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("http://192.168.11.5:3004/#/login")
#登录
driver.find_element_by_class_name("el-input__inner").send_keys("admin")#username
driver.find_element_by_xpath("//input[@type='password']").send_keys("123456")#password
#浏览器页面截屏
driver.get_screenshot_as_file('/Users/guxuecheng/Desktop/screenImg.png')
#定位验证码位置及大小
location = driver.find_element_by_class_name('captcha').location
size = driver.find_element_by_class_name('captcha').size
left = location['x']
top = location['y']
right = location['x'] + size['width']
bottom = location['y'] + size['height']
#从文件读取截图,截取验证码位置再次保存
img = Image.open('/Users/guxuecheng/Desktop/screenImg.png').crop((left,top,right,bottom))
img = img.convert('L') #转换模式:L | RGB
img = ImageEnhance.Contrast(img)#增强对比度
img = img.enhance(2.0) #增加饱和度
img.save('/Users/guxuecheng/Desktop/screenImg.png')
#读取识别验证码
yanzhengma = Image.open('/Users/guxuecheng/Desktop/screenImg.png')
text = pytesseract.image_to_string(yanzhengma).strip()
driver.find_element_by_xpath('/html/body/div/div[2]/div[2]/form/div[3]/div/div/div[1]/div/input').send_keys(text)
#登录按钮
driver.find_element_by_class_name("el-button login-button el-button--primary")
time.sleep(5)
#退出
above = driver.find_element_by_class_name("el-icon-mgmt el-icon-mgmt-user")
#对定位到的元素(个人头像)进行悬停
ActionChains(driver).move_to_element(above).perform()
#定位到退出按钮并点击
exit = driver.find_element_by_class_name("el-dropdown-menu__item").click()
所有的测试用例都需要进行登录操作,结束进行退出操作。如果不使用模块化编写用例,那么每一个用例都有一个登录退出的操作,导致重复代码太多,日后维护也非常不便(因为只要退出登录功能发生了改变,那么每一个用例都需要修改)。于是就引入了模块化的概念。因为登录和退出可以看作是两个独立的功能,我们对其简单的封装,如下:
scriptTow.py
#coding=utf-8
from selenium import webdriver
#引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image,ImageEnhance
import pytesseract
import time
#登录,为了代码整洁,此处假设没有验证码
def login ():
driver.find_element_by_class_name("el-input__inner").clear()
driver.find_element_by_class_name("el-input__inner").send_keys("admin") # username
driver.find_element_by_xpath("//input[@type='password']").clear()
driver.find_element_by_xpath("//input[@type='password']").send_keys("123456") # password
driver.find_element_by_class_name("el-button login-button el-button--primary").click()
#退出
def logout ():
above = driver.find_element_by_class_name("el-icon-mgmt el-icon-mgmt-user")
# 对定位到的元素(个人头像)进行悬停
ActionChains(driver).move_to_element(above).perform()
driver = webdriver.Firefox()
driver.implicitly_wait(10)
driver.get("http://192.168.11.5:3004/#/login")
#调用登录模块
login()
time.sleep(10)
#乱七八糟的其他操作。。。。。。
#退出
logout()
这种封装好像并没有带来什么便利,代码量并未少。如果能够把这段代码封装成一个单独的文件,其他地方使用直接 import进来会不会方便很多?
loginPublic.py
# coding=utf-8
from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains
from PIL import Image,ImageEnhance
import pytesseract
import time
# 定义一个 Login类
class Login(object):
# Login 类有两个方法,一是登录,一是退出
def login (driver):
driver.find_element_by_class_name("el-input__inner").clear()
driver.find_element_by_class_name("el-input__inner").send_keys("admin") # username
driver.find_element_by_xpath("//input[@type='password']").clear()
driver.find_element_by_xpath("//input[@type='password']").send_keys("123456") # password
# 这个类名是是复合类名,不能用来进行定位
# driver.find_element_by_class_name("el-button login-button el-button--primary").click()
driver.find_element_by_xpath("//button[@type = 'button']").click()
# 退出
def logout(driver):
above = driver.find_element_by_class_name("el-icon-mgmt el-icon-mgmt-user")
# 对定位到的元素(个人头像)进行悬停
ActionChains(driver).move_to_element(above).perform()
driver.find_element_by_class_name("el-dropdown-menu__item").click()
这次的封装我们声明了一个新的类 Login,类有两个方法(函数),方法的调用需要输入一个参数 driver。driver 需要通过具体调用的用例给定。
publicTest.py
#coding=utf-8
from selenium import webdriver
from LoginPublic import Login
import time
driver = webdriver.Chrome('./chromedriver')
driver.implicitly_wait(5)
driver.get("http://192.168.11.5:3004/#/login")
time.sleep(5)
#调用登录模块
Login.login(driver)
二、数据驱动测试
模块化驱动测试很好的解决了脚本重复的问题。但是还有一个实际存在的问题,比如对于一个数据我们需要测试不同值输入的时候的情况,比如测试不同用户名进行登录等等。虽然脚本基本相同,步骤也想同,但是不同的用户名登录得出的数据可能会不同。于是,数据驱动测试的概念就出来了。顾名思义就是数据的改变驱动自动化测试的执行导致测试结果的不同。
1、参数化
我们以 loginPublic.py这个脚本为例,对 login这个 方法进行修改:
.....
def user_login(self, driver, username, password):
driver.find_element_by_class_name("el-input__inner").clear()
driver.find_element_by_class_name("el-input__inner").send_keys(username) # username
driver.find_element_by_xpath("//input[@type='password']").clear()
driver.find_element_by_xpath("//input[@type='password']").send_keys(password) # password
driver.find_element_by_xpath("//button[@type = 'button']").click()
.......
接下来我们修改publicTest.py:
# coding=utf-8
from selenium import webdriver
from LoginPublic import Login
import time
class LoginTest(object):
def __init__(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(10)
self.driver.get("http://192.168.11.5:3004/#/login")
# 用户1登录
def test_admin_login(self):
username = 'user1'
password = '123456'
Login().user_login(self.driver, username, password) # Login是类名,Login()是创建的一个对象
time.sleep(5)
self.driver.quit()
# 用户2登录user_login
def test_guest_login(self):
username = 'user2'
password = '123654'
Login().user_login(self.driver, username,password)
time.sleep(5)
self.driver.quit()
LoginTest().test_admin_login()
LoginTest().test_guest_login()
创建LoginTest 类,并在init()方法中初始化浏览器驱动、等待超时时长和 URL 等。这样test_admin_login和test_guest_login两个方法只需关注登录的用户名和密码,通过调用 Login 类的 user_login()方法并传入具体的参数来测试不同用户的登录。(类的方法需要由类创建的对象对象调用,Login()就是 Login 类的对象)
2、参数化关键字
比如我们通过百度进行搜索,每次操作步骤完全相同,不同的是输入的搜索关键词不同,此时我们就可以对关键词进行参数化。
# coding=utf-8
from selenium import webdriver
# 将关键词放入数组
search_text = ['python', 'Selenium', 'webDriver']
# 对关键词进行遍历,去掉 if 语句就可以对每一个关键词都进行搜索
for text in search_text:
# 只搜索指定的关键字
if text == "Selenium":
driver = webdriver.Firefox()
driver.implicitly_wait(5)
driver.get("http://baidu.com")
driver.find_element_by_id("kw").send_keys(text)
driver.find_element_by_id("su").click()
else:
print(text)
3、读取 txt 文件
Python 提供了几种读取 txt 文件的方式。
- read():读取整个文件
- readline():读取一行数据
- readlines():读取所有行的数据
现在有一个需求是,我们需要验证多个用户登录的场景,每个用户的 username 和 password 各不相同。这时候我们可以把这些信息使用 txt 文件存储:
user_info.txt
zhangsan,123
lisi,456
wangwu,789
zhaoliu,678
读取 txt 文件:
user_file = open('user_info.txt', 'r')
lines = user_file.readlines()
user_file.close()
# 循环遍历出 txt 文件中每一行的数据,在这里对不同的用户名进行登录
for line in lines:
username = line.split(',')[0]
password = line.split(',')[1]
print(username,password)
脚本执行结果:
/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/bin/python3.6 /Users/guxuecheng/Desktop/selenium脚本/user_infor.py
zhangsan 123
lisi 456
wangwu 789
zhaoliu 678
Process finished with exit code 0
3、读取 CSV 文件
现在我们更多的习惯使用 Excel 表格存储数据。我们只需要将数据保存在 Excel 表格中再保存为 CSV 格式的文件, 就可以使用 Python 读取这些数据。
import csv
# 读取本地 CSV 文件(文件内含有中文)
users = csv.reader(open('user_info.csv','r',encoding='gbk'))
# 读取本地 CSV 文件(文件内不含有中文)
# users = csv.reader(open('user_info.csv','r'))
# 遍历每一行数据
for user_info in users:
print(user_info)
读取结果:
如果我们只想获取用户的信息的用户名和手机号,略加修改即可:
import csv
# 读取本地 CSV 文件(文件内含有中文)
users = csv.reader(open('user_info.csv','r',encoding='gbk'))
# 读取本地 CSV 文件(文件内不含有中文)
# users = csv.reader(open('user_info.csv','r'))
# 遍历每一行数据
for user_info in users:
print(user_info[0],user_info[3])
结果如下:
如果需要填写姓名和手机号,则直接填写user_info[0]和user_info[3]就可以了。