Python+seleunim+unittest+ddt自动化数据驱动测试心得二
前一篇文章写了安装的支持插件现在我们来写入具体的实现代码(中有很多代码与网上大同,或者我做的时候就是使用的人家的原创,谢谢晚上其他大佬的分享)
三、代码
1、在config包下创建config.ini配置文件,存储配置的浏览器与访问网站,代码如下:
[browserType]
;browserName = Firefox
browserName = Chrome
;drowserName = IE
[testServer]
URL = https://www.baidu.com
;URl = www.goole.com
2、dataSase包下放execl文件,execl文件如下
Id:编号 title:标题 module:当前测试页面(影响pageobjects中的选择脚本)
data:一个用例中需要填充的数据,使用英文”,”号隔开 result:想要的结果
3、重点的核心文件framework包
1)browerengine用于打开指定浏览器,代码如下:
#coding=utf-8
import configparser
from selenium import webdriver
from framework import loggers
import os
"""
浏览器引擎类
"""
logger = loggers.Logger(logger="browerengine_BrowserEngine").getlog()
class BrowserEngine(object):
def __init__(self, driver):
self.driver = driver
#浏览器操作
def open_browser(self):
#读取配置文件中的浏览器设置与访问网址
config = configparser.ConfigParser()
file_path = os.path.abspath('.')+"/config/config.ini"
# 但是对于有BOM(如Windows下用记事本指定为utf-8)的文件,需要使用 utf-8-sig, 使用utf-8没办法。
# 【我没试过,http://blog.csdn.net/liujingqiu/article/details/77677256】
# 对于我写的代码 此处是utf-8-sig 或者 utf-8 都可以
config.read(file_path,encoding="utf-8")
browser = config.get("browserType", "browserName")
logger.info("You had select %s browser." % browser)
url = config.get("testServer", "URL")
logger.info("The test server url is: %s" % url)
#根据配置文件加载驱动
if browser == "Firefox":
self.driver = webdriver.Firefox()
logger.info("firefox.")
elif browser == "Chrome":
self.driver = webdriver.Chrome()
logger.info("Chrome.")
elif browser == "IE":
self.driver = webdriver.Ie()
logger.info("Ie.")
self.driver.get(url)
logger.info("Open url: %s" % url)
self.driver.maximize_window()
logger.info("Maximize the current window.")
self.driver.implicitly_wait(10)
logger.info("Set implicitly wait 10 seconds.")
# self.driver = driver
return self.driver
#退出浏览器
def quit_browser(self):
self.driver.quit()
logger.info("Now, Close and quit the browser.")
2)elementUtils对seleunim中的方法封装作为基类使用,代码如下:
#coding=utf-8
import time
from selenium.common.exceptions import NoSuchElementException
from framework import loggers
import os
#创建logger实例
logger = loggers.Logger(logger="BasePage").getlog()
'''
seleunim操作基类
'''
class BasePage(object):
"""
页面基类
"""
def __init__(self, driver):
self.driver = driver
# 退出浏览器 结束操作
def quit_browser(self):
self.driver.quit()
# 浏览器前进
def forward(self):
self.driver.forward()
logger.info("在页面上点击前进.")
# 浏览器后退
def back(self):
self.driver.back()
logger.info("在页面上点击后退.")
# 隐式等待
def wait(self, seconds):
self.driver.implicitly_wait(seconds)
logger.info("等待 %d s."%seconds)
# 点击关闭当前窗口
def close(self):
try:
self.driver.close()
logger.info("关闭并退出浏览器.")
except NameError as e:
logger.error("退出浏览器失败: %s."%e)
# 保存图片
def get_windows_img(self):
"""
将图片直接保存到项目下.\screenshots下
:return:
"""
file_path = os.path.abspath(".")+ '/ErrorImg/' \
+ time.strftime('%Y%m%d%H%M', time.localtime()) + '.png'
try:
self.driver.get_screenshot_as_file(file_path)
logger.info("已经截图并保存到文件: %s"%file_path)
except NameError as e:
logger.error("截图失败: %s."%e)
# 截图失败重新调用此方法
self.Get_windows_img()
# 定位元素
def find_element(self, selector):
"""
根据 => 切割字符串
页面里定位元素的方法:
submit_btn = "id=>su"
login_lnk = "xpath=>//*[@id='u1']/a[7]"
:param selector:
:return:
"""
element = ''
if '=>' not in selector:
return self.driver.find_element_by_id(selector)
selector_by = selector.split('=>')[0]
selector_value = selector.split('=>')[1]
try:
if selector_by == 'i' or selector_by == 'id':
element = self.driver.find_element_by_id(selector_value)
elif selector_by == 'n' or selector_by == 'name':
element = self.driver.find_element_by_name(selector_value)
elif selector_by == 'l' or selector_by == 'link_text':
element = self.driver.find_element_by_link_text(selector_value)
elif selector_by == 'p' or selector_by == 'partial_link_text':
element = self.driver.find_element_by_partial_link_text(selector_value)
elif selector_by == 't' or selector_by == 'tag_name':
element = self.driver.find_element_by_tag_name(selector_by)
elif selector_by == 'x' or selector_by == 'xpath':
element = self.driver.find_element_by_xpath(selector_value)
elif selector_by == "s" or selector_by == 'selector_selector':
element = self.driver.find_element_by_css_selector(selector_value)
else:
raise NameError("请输入一个有效类型的目标元素.")
logger.info("已经通过 %s 成功找到元素: %s ."%(selector_by,selector_value))
except NoSuchElementException as e:
self.get_windows_img()
logger.error("没有找到这样的元素 : %s"%e)
return element
# 输入
def type(self, selector, text):
el = self.find_element(selector)
el.clear()
try:
el.send_keys(text)
logger.info("在输入框已经输入: %s ." % text)
except NameError as e:
logger.error("输入失败: %s" %e)
self.get_windows_img()
# 点击
def click(self, selector):
el = self.find_element(selector)
try:
# 输出el.text不能写在el.click()后,若写其后,当点击链接跳转页面时会报错,显示元素丢失。
logger.info("%s 元素被点击." %el.text)
el.click()
logger.info("点击成功.")
except NameError as e:
logger.error("点击失败: %s"%e)
#获取标题
def get_page_title(self):
logger.info("当前页标题: %s" % self.driver.title)
return self.driver.title
#休眠等待
@staticmethod
def sleep(seconds):
time.sleep(seconds)
logger.info("休眠 %d s" % seconds)
# 获取窗口句柄
def get_now_window_handle(self):
handle = self.driver.current_window_handle
logger.info("获取当前窗口: %s"%handle)
return handle
#切换页面
def set_new_handle(self,oldhandle):
#获取当前浏览器所有句柄
handles=self.driver.window_handles
logger.info("获取当前浏览器所有窗口: %s"%handles)
#将所有句柄循环读取出来,获取到一个没有使用的句柄
for hd in handles:
if hd != oldhandle:
newHandle=hd
self.driver.close()
logger.info("关闭原来的窗口: %s"%oldhandle)
#切换到一个新的句柄
self.driver.switch_to.window(newHandle)
logger.info("切换新的窗口: %s"%newHandle)
#通过","符号对字符进行分割
def str_split(self,str):
list=str.split(",")
return list
3)loggers包主要用于书写日志文件,代码如下:
#coding=utf-8
import logging
import time
import os
class Logger(object):
def __init__(self, logger):
'''
指定保存日志的文件路径,日志级别以及调用文件
将日志存入到指定的文件中
:param logger:
'''
# 创建一个logger
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.DEBUG)
# 创建handler,写入日志文件
rq = time.strftime('%Y%m%d%H%M',time.localtime(time.time()))
log_path = os.path.abspath(".") + '/logs/' + rq + '.log'
fh = logging.FileHandler(log_path)
fh.level = logging.INFO
# 创建handler, 用于输出控制台
ch = logging.StreamHandler()
ch.level = logging.INFO
# 定义handler的输出格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.formatter = formatter
ch.formatter = formatter
self.logger.addHandler(fh)
self.logger.addHandler(ch)
def getlog(self):
return self.logger
4)readExcel包主要用于读取dataSase中Excel的数据,代码如下:
#coding=utf-8
import xlrd
from framework import loggers
'''
excel的读取,应为有专用的报告文件所以不需要写入操作
'''
#创建logger实例
logger = loggers.Logger(logger="readExcel_ExcelMethod").getlog()
class ExcelMethod(object):
def __init__(self,sasepath,index):
self.sasepath =sasepath
self.index=index
#打开文件
def open_excel(self):
excelfile=xlrd.open_workbook(self.sasepath)
return excelfile
#获取表单
def get_sheet(self,):
datasheet=self.open_excel().sheet_by_index(self.index)
return datasheet
#获取用例数据
def get_case(self):
logger.info("读取excel数据")
datasheet = self.get_sheet()
list=[]
for row in range(1 , datasheet.nrows):
list.append(datasheet.row_values(row))
return list
5)saseSelect包,一个简单的工厂设计模式来返回pageobjects的脚本类,主要通过dataSase文件下execl中module中数据来确认脚本,代码如下:
from pageobjects.searchValueSase import SearchValue
from pageobjects.baiduSase import HomePage
from framework import loggers
'''
通过一个简单的工厂设计模式,来确定我们需要的是那个用例类
'''
#创建logger实例
logger = loggers.Logger(logger="saseSelect_SaseSelect").getlog()
class SaseSelect(object):
def __init__(self,module,browser):
self.module=module
self.browser=browser
#返回直接的脚本类
def sase_Select(self):
logger.info("执行用例模块:"+self.module)
if self.module == "百度搜索":
return SearchValue(self.browser)
elif self.module == "查看NBA":
return HomePage(self.browser)
4、pageobjects包,在一个页面会进行的操作脚本
1)baiduSase脚本,我这里做的一个在百度中页面跳转的用例,不存在多数据的更换,代码如下:
#coding=utf-8
from framework import elementUtils
"""
在新闻页面点击体育
"""
class HomePage(elementUtils.BasePage):
# 点击新闻链接
def news_click(self):
self.click("xpath=>//*[@id='s-top-left']/a[1]")
#获取原窗口句柄
oldihandle=self.get_now_window_handle()
#切换为新弹窗
self.set_new_handle(oldihandle)
self.click("link_text=>体育")
self.click("xpath=>//*[@id='col_focus']/div[1]/div[2]/div/div[2]/div/ul/li[1]/a")
oldihandle=self.get_now_window_handle()
#切换为新弹窗
self.set_new_handle(oldihandle)
return self.get_page_title()
2)searchValueSase脚本,主要在百度中进行搜索一些数据,代码如下:
#coding=utf-8
'''
搜索数据
'''
from framework import elementUtils
class SearchValue(elementUtils.BasePage):
def search_Value(self,data):
self.type("id=>kw",data)
self.click("id=>su")
self.sleep(2)
return self.get_page_title()
5、testuits包中,主要放通过unittest生成的每个脚本的单元测试
本包创建的单元测试文件名称必须以test开头,后面加载testuits包中的文件是以test*.py来读取的
1)test_baidu_search文件是pageobject中baiduSase文件的单元测试,代码如下
"""
测试脚本
"""
import unittest
from framework.browerengine import BrowserEngine
from framework.saseSelect import SaseSelect
class ViewNBANews(unittest.TestCase):
def setUp(self):
self.engin = BrowserEngine.__new__(BrowserEngine)
self.driver = self.engin.open_browser()
def tearDown(self):
self.engin.quit_browser()
def test_view_nba_news(self):
# 初始化百度首页,点击“新闻”链接
baiduhome = SaseSelect("查看NBA",self.driver).sase_Select()
self.assertEqual(baiduhome.news_click(),"NBA赛程表_百度搜索")
if __name__ == '__main__':
unittest.main()
2)test_BaiduSearchValue文件是pageobject中searchValueSase文件的单元测试,代码如下:
import unittest
from framework.browerengine import BrowserEngine
from framework.readExcel import ExcelMethod
from framework.saseSelect import SaseSelect
import os
from ddt import ddt,data,unpack
"""
百度搜索数据
"""
#获取用例的数据
excels=ExcelMethod(os.path.abspath(".")+"/dataSase/testsase.xls",0)
dataSase=excels.get_case()
@ddt
class SearchValue(unittest.TestCase):
def setUp(self):
#浏览器加载
self.engin = BrowserEngine.__new__(BrowserEngine)
self.driver = self.engin.open_browser()
def tearDown(self):
#浏览器退出
self.engin.quit_browser()
@data(*dataSase)
@unpack
def test_view_nba_news(self,id,title,module,data,result):
#通过pageobject包的SaseSelect类来确认当前应执行的用例
Search = SaseSelect(module,self.driver).sase_Select()
#获取当前页面需要的excel中data数据,并处理转化成list
strList=Search.str_split(data)
self.assertEqual(Search.search_Value(strList[0]),result)
if __name__ == '__main__':
unittest.main()
注:
@ddt注解表示当前类中采用了数据驱动方式进行。
@data(*dataSase)注解表示当前测试中数据源于dataSase的数据,当前数据是直接于execl读取生成的一个可迭代的list数据,每一次代表迭代exec中的一行数据
@unpack用于对当前一行数据进行详细的拆分成一列
当进行了unpack数据拆分之后在传入当前测试方法中,当前行拆分了多少数据,就需要在方法中传入多少数据。
在①中,传入module数据来确定通过framework包中的saseSelect返回的执行脚本。
在②中,传入的data数据是通过execl中data的一个字符串(胡歌,numb2,numb3),需要对起拆分成list传入脚本中使用
在③中,主要用于与execl中的返回数据进行对比,判断是否正确
6、runn执行主文件
当前文件的主要方式是生成测试报告,代码如下:
#coding=utf-8
import HTMLTestRunner
import unittest
import time
import os
testPath=os.path.abspath(".")+'\\testsuits'
#加载testsuits文件下指定文件名类型是test*的,所有测试文件
suite = unittest.TestLoader().discover(testPath, "test*.py")
#设置报告文件保存路径
report_path = os.path.abspath(".") + '/test_report/' + time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time())) + "HTMLtemplate.html"
if __name__=='__main__':
fp = open(report_path, 'wb')
# 执行用例时生成测试报告
runner =HTMLTestRunner.HTMLTestRunner(
stream=fp,
title=u"搜索功能测试报告",
description=u"用例执行情况"
)
# runner=unittest.TextTestRunner()
runner.run(suite)
#记得关闭文件
fp.close()
每句代码意思看上面的注释
最后会产生的结果如下(我的HTMLTestRunner是网上美化的了的,大概意思没变):