失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Python Unitest 自动化测试框架(V2.0)生成测试报告 发送邮件 excel用例数据

Python Unitest 自动化测试框架(V2.0)生成测试报告 发送邮件 excel用例数据

时间:2024-06-28 21:39:47

相关推荐

Python Unitest 自动化测试框架(V2.0)生成测试报告 发送邮件   excel用例数据

自动化测试框架说明

框架结构

当前自动化测试框架结构如下图所示:

自动化测试项目结构

目录与文件作用如下:

AutoTest:自动化测试项目 Lib:测试项目相关的公用代码Projects:按项目存放测试程序及所需数据、产生的报告等文件Projects目录: BaseTest:自动化测试的公共模块,如:登录、用户管理等。IDC:用于存放联通项目相关的测试数据、用例、报告等。......BaseTest目录:(同级的目录结构与此目录相同,如IDC目录) Data:用于存放测试相关的数据Pages:用于存放测试用例的页面对象Report:用于存放自动化测试产生的测试结果html文件TestCase:自动化测试用例目录TestPlan:项目测试用例- excel文件all_test.py:用例执行程序,可用于指定项目内需要执行的测试用例

功能包括:

接口自动化

界面自动化

报告生成

邮件发送

数据驱动方面:

unittest的TestCase类

excel 数据驱动

parameterized数据驱动

2. 自动化测试开发

接口自动化测试方法一:excel数据驱动

Excel数据驱动适用于接口测试的参数校验等场景,步骤如下。

A. 引入unittest模块,创建test类,继承unittest的TestCase类

import unittest, timefrom testgroup.AutoTest.Projects.SoSo.Pages import SoSoPagefrom selenium import webdriverclass WebUI(unittest.TestCase):def setUp(self):self.driver = webdriver.Firefox()self.driver.implicitly_wait(30)self.url = "http://192.168.142.145:8086/sousou/"# 网站启动正常def test_01_open_page(self):SoSoPage.SoSoPage(self.driver, self.url, "飕搜")time.sleep(4)def test_02_open_page(self):SoSoPage.SoSoPage(self.driver, self.url, "飕搜")def tearDown(self):self.driver.quit()

B. 引入ddt模块(数据驱动测试),在 unittest 框架中使用 ddt 进行迭代

#!/usr/bin/env python# -*- coding:utf-8 -*-# author : from __future__ import divisionimport unittestimport ddtfrom base.utils.log import *from testgroup.AutoTest.Lib.ExcelUtil import ExcelUtilfrom testgroup.AutoTest.Projects.BaseTest.all_test import *Logger().enableConsole(True)log = Logger().getLogger(Test000ParamSet().API_TEST_LOG_FILE_NAME, './{}.log'.format(Test000ParamSet().API_TEST_LOG_FILE_NAME))EXCEL = ExcelUtil(BaseParamSet().CASE_XLSX_PATH, Test000ParamSet().API_TEST_CASE_XLSX_SHEETNAME)@ddt.ddtclass APITest(unittest.TestCase):@classmethoddef setUpClass(cls):# 预设环境信息cls.url_base = Test000ParamSet().API_TEST_URL_BASEcls.result_path = BaseParamSet().PLAN_DIR + "{}_result.xls"cls.copy_book = EXCEL.copy_book()@classmethoddef tearDownClass(cls):# 保存结果cls.copy_book.save(cls.result_path.format(time.strftime("%Y-%m-%d %H_%M_%S")))@ddt.data(*EXCEL.next())def test_001_api_test(self, data):""" 测试接口"""if data["是否执行(T/F)"] == 'T':log.info("testCase:[{case}] processing.".format(case=data["用例编号"]))# 发送请求并整理返回结果ret_data, error_param = process_request(data, self.url_base)# 写入测试结果EXCEL.modify(self.copy_book, Test000ParamSet().API_TEST_CASE_XLSX_SHEETNAME, ret_data)for res in ret_data["实际结果"].split('\\'):self.assertIn(res, str(ret_data["预期结果"]).split('\\'),"返回信息:[{}], 问题参数:[{}]".format(ret_data["描述"], error_param))else:log.info("testCase:[{case}] skip.".format(case=data["是否执行(T/F)"]))# 发送请求,处理返回结果# 发送的请求经过param_parse()方法,自由组合(笛卡尔积:根据已有的参数列表构成有序对)得到参数键值对 key - valuedef process_request(data, url_base):actual_out_list = []error_param = {}# 发送请求for params in param_parse(data, "测试数据"):response_data = response_json_data(params, url_base + data["接口地址"], data["请求方式(POST/GET)"])# 处理返回值actual_out = str(response_data['result_code'])if str(actual_out) not in str(data["预期结果"]).split('\\'):data["是否通过(T/F)"] = 'F'data["描述"] = response_data['result_msg']data["实际结果"] = actual_outerror_param = paramsbreakelse:actual_out_list.append(actual_out)if error_param == {}:error_param = "测试通过,参数无需异常分析。"if data["是否通过(T/F)"] != 'F':data["是否通过(T/F)"] = 'T'data["实际结果"] = '\\'.join(list(set(actual_out_list)))return data, error_paramif __name__ == "__main__":# unittest.main()a = os.path.abspath('..') + "/Report/{}_result.xls".format(time.strftime("%Y-%m-%d %H_%M_%S"))print(a)

调整测试数据:根据测试用例设置的输入数据,调整项目下的基础测试数据,测试数据路径 -> ../项目名/TestCase/..方法二:parameterized数据驱动

parameterized数据驱动适用于功能、业务测试等场景。测试程序不需要读取excel测试用例,需要将待测试的数据预先设定好,程序直接读取,步骤如下。

数据测试用例准备测试数据

# 元祖参数说明:keyword, ip, location, start_index, fetch_num, result_codeSEARCH_LIST = [('西游记', '192.168.142.144', 1, 120, 1, 0),('变形金刚123', '192.168.142.145', 1, 120, 1, 0),('变形金刚', '192.168.142', 1, 120, 1, 102)]# 元祖参数说明:keyword, ip, location, start_index, fetch_num, result_code# keyword 字段不做仅中英文处理POSSIBLE_COPYRIGHT_LIST[10] 结果改为0POSSIBLE_COPYRIGHT_LIST = [('S', '127.0.0.1', '中国江苏', 0, 10, 0),('芝加哥医院', '127.0.0.1', '中国江苏', 0, 10, 0),('shine'*10, '127.0.0.1', '中国江苏', 0, 10, 0),('芝加哥医院', '127.0.0.1', '中国江苏L안녕...', 0, 10, 0),('芝加哥医院', '127.0.0.1', 'locat'*51, 0, 10, 0),('芝加哥医院', '127.0.0.1', '', 0, 10, 0),('芝加哥医院', '127.0.0.1', '中国江苏', 255, 10, 0),('芳华', '127.0.0.1', '中国江苏', 0, 10, 0)]

在测试用例前指定测试用例调用的测试数据

#!/usr/bin/env python# -*- coding:utf-8 -*-import unittestimport requestsfrom parameterized import parameterizedfrom testgroup.AutoTest.Projects.SoSo.Data import soso_datafrom testgroup.AutoTest.Projects.SoSo.all_test import *class Soso(unittest.TestCase):""" 飕搜接口自动化测试 """def setUp(self):# 预设环境信息self.url_search = Test101ParamSet().SOSO_API_URL_SEARCHself.url_possible = Test101ParamSet().SOSO_API_URL_PROSSIBLEself.url_copytight = Test101ParamSet().SOSO_API_URL_COPYRIGHTself.url_copyright_details = Test101ParamSet().SOSO_API_URL_COPYRIGHT_DETAILSself.report_url = Test101ParamSet().SOSO_API_URL_REPORTdef tearDown(self):# 清理环境信息pass@parameterized.expand(soso_data.SEARCH_LIST)def test_001_video_search(self, keyword, ip, location, start_index, fetch_num, result_code):""" 搜索接口 """params = {'keyword': keyword,'ip': ip, 'location': location, 'startIndex': start_index, 'fetchNum': fetch_num}data = response_json_data(params, self.url_search)print(data['result_code'])print(data)self.assertEqual(data['result_code'], result_code, data['result_msg'])@parameterized.expand(soso_data.POSSIBLE_COPYRIGHT_LIST)# 发送请求,解析json, return datadef response_json_data(params, url):# for key, value in params.items():#if value == '':# params.pop(key)requests.Session()response = requests.get(url=url, params=params, verify=False)data = response.json()return dataif __name__ == '__main__':unittest.main()

注意:测试数据列表中元祖数据需与测试用例所需参数对

2.1.3 执行测试用例

修改项目路径下的settings文件,需要关注的是:

1)指定需要爬取的测试用例:TEST_NUM

2)excel测试用例存放的路径:BASE_DIR

3)接口请求的IP地址:URL_BASE

#!/usr/bin/env python# -*- coding:utf-8 -*-from testgroup.AutoTest.Lib.UtilityTools import *"""说明:BaseParamSet:项目基础路径、测试用例目录等,仅需要修改CASE_XLSX_PATH->excel测试用例名称AllTestParamSet:待测试脚本、测试主题、邮箱列表(可根据实际需要修改)Test000ParamSet:请求地址、excel用例sheet页名、日志文件名Test001ParamSet:请求地址、excel用例sheet页名、日志文件名"""BASE_DIR = '.'class BaseParamSet:"""基础设置"""def __init__(self):self.BASE_DIR = BASE_DIR# 定义测试文件查找的目录self.TEST_DIR = self.BASE_DIR + r'/TestCase/'# 测试用例、测试结果excel存放目录self.PLAN_DIR = self.BASE_DIR + '/TestPlan/'# 测试用例存放路径self.CASE_XLSX_PATH = self.PLAN_DIR + 'testcase_module.xls'class AllTestParamSet:"""all_test.py"""def __init__(self):# 指定测试脚本self.TEST_NUM = '000_api'# 测试主题self.TEST_SUBJECT = 'soso接口自动化测试报告'# 测试报告发送邮箱列表self.RECEIVER_LIST = ['***@***.com']class Test000ParamSet:"""Test000_api_test.py"""def __init__(self):# 接口请求IP地址self.API_TEST_URL_BASE = 'http://192.168.142.59:8086'# sheetnameself.API_TEST_CASE_XLSX_SHEETNAME = '接口测试用例'# 日志文件self.API_TEST_LOG_FILE_NAME = 'api_test_model'class Test001ParamSet:"""Test001_login.py"""def __init__(self):# 请求IP地址self.LOGIN_BASE_URL = 'http://192.168.142.171:8080//index.html'# sheetnameself.LOGIN_TEST_CASE_SHEETNAME = '页面自动化测试用例'# 日志文件self.LOGIN_TEST_LOG_NAME = 'ui_test_model'if __name__ == "__main__":# 执行测试并发送邮件send_report(AllTestParamSet().RECEIVER_LIST, AllTestParamSet().TEST_SUBJECT,html_report(BASE_DIR, BaseParamSet().TEST_DIR, AllTestParamSet().TEST_NUM, AllTestParamSet().TEST_SUBJECT))

执行项目路径下的all_test.py

使用方法一:excel数据驱动程序运行完成后会生成两个文件,一个是html文件,存放于项目路径下的Report目录,一个是excel文件,存放于settings设置的BASE_DIR目录下。

使用方法二:parameterized数据驱动仅生成html报告,存放于项目路径下的Report目录。

2.2 页面自动化测试

方法一:编写Page Object

PageObject介绍

Page Object是Selenium自动化测试中的一种设计模式,主要是将每一个页面设计为一个Class,其中包含页面中需要测试的元素(按钮,输入框,标题等),这样在Selenium测试页面中可以通过调用页面类来获取页面元素,这样巧妙的避免了当页面元素id或者位置变化是,需要改测试页面代码的情况。当页面元素id变化时,只需要更改测试页Class中页面的属性即可。

Page Object模式将页面定位和业务操作分开,分离测试对象(元素对象)和测试脚本(用例脚本),提高用例的可维护性。

设计思路:

以登录页面为例:

定义基础类,封装公用的方法

BasePage:可包含根据不同定位(id,name,xpath,css等)来定位元素、点击操作、获取验证码等基本方法。

定义登录页面的基本操作方法

LoginPage:所有页面元素定位都在此层定义,UI一旦有更改,只需在修改这一层页面对象属性即可。

使用unittest框架编写测试用例

TestCase_Login:定义测试数据,针对不同场景设计测试用例,不需要关心元素定位,直接调用LoginPage类即可。

这样分层的好处是,不同层关心不同的问题。页面对象层只关心元素定位问题,测试用例只关系测试的数据。

项目使用:

1、测试用例

测试用例中定义场景中对应的定位点和所需测试测试。

2、执行程序

执行用例脚本时,LoginPage读取Excel中的定位点,TestCase读取Excel中的测试数据。

方法二:Selenium IDE录制

Selenium IDE使用场景:用于测试同事在浏览器端直接执行测试操作,将操作脚本导入至框架中,避免直接接触页面代码开发。

Selenium IDE 下载地址:(适用于FireFox 17.0~56.*)

/en-GB/firefox/addon/selenium-ide/versions/

录制步骤:

点击start recording 红色按钮

2.浏览器中直接操作

3.录制结束,编辑脚本

4.导出脚本

2.2.3 执行测试用例

执行单个测试用例:在TestCase下,选择需要执行的用例

执行所有测试用例:执行项目路径下的all_test.py

3. 结果输出

HTML测试报告简单说明

unittest里面是不能生成html格式报告的,需要导入一个第三方的模块:HTMLTestRunner,目前文件存放在自动化测试框架的Lib目录下,目前使用HTMLTestRunner_PY3.py:

在all_test.py文件中调用test_report方法生成HTML测试报告,需指定参数:

TEST_NUM:指定的测试脚本

TEST_SUBJECT:测试主题

3.1生成报告

1.test_report方法的HTMLTestRunner_PY3.HTMLTestRunner()主要有三个参数需要了解一下:

--stream:测试报告写入文件的存储区域

--title:测试报告的主题

--description:测试报告的描述

2.最后返回的report_dir是存放测试报告的地址

3.2 报告详情

1、找到测试报告文件,用浏览器打开,点开View里的Detail可以查看详情描述

2、通过在测试用例中添加注释可以在最后的测试报告中生成待测试描述的测试结果,如果使用入参的形式生成的报告还会有具体的参数说明:

4. 发送邮件

简单说明

发邮件需要用到python两个模块,smtplib和email,这俩模块是python自带的,只需import即可使用。smtplib模块主要负责发送邮件,email模块主要负责构造邮件。其中MIMEText()定义邮件正文,Header()定义邮件标题。MIMEMulipart模块构造带附件,

2.发送邮件

当前Sendmail公共方法已经比较完善,我们这里直接引入调用mail方法就可以了:

参考公共模块代码

当前的接收方的邮箱列表我们在自动化测试相应的项目下的settings文件中进行指定,如接收方只有一个人则可以不使用列表形式,可以使用字符串的形式。

3.4邮件详情

当前我们采用发送附件的形式,直接将自动生成的测试报告发送给相关人员。

4. 公共代码库

发送邮件

SendeEmail.py

#!/usr/bin/env python# -*- coding:utf-8 -*-import smtplibimport osimport timefrom email.header import Headerfrom email.mime.text import MIMETextfrom email.mime.multipart import MIMEMultipartfrom email.mime.application import MIMEApplicationclass Mail:"""发送邮件"""def __init__(self, mail_server="smtp.", user="88@", pwd="88",sender='88@', receiver='', subject=''):self.mail_server = mail_serverself.user = userself.pwd = pwdself.conn = Noneself.sender = senderself.receiver = receiverself.subject = subjectdef send_mail(self, msg):try:msg['From'] = Header("测试组", 'utf-8')self.conn = smtplib.SMTP()self.conn.connect(self.mail_server)self.conn.login(self.user, self.pwd)if type(self.receiver) == list:msg['To'] = Header(','.join(self.receiver), 'utf-8')for receiver_own in self.receiver:self.conn.sendmail(self.sender, receiver_own, msg.as_string())else:msg['To'] = Header(self.receiver, 'utf-8')self.conn.sendmail(self.sender, self.receiver, msg.as_string())self.conn.close()print("邮件已发出!注意查收!")return Trueexcept Exception as e:print(e)print("邮件发送失败,请检查!")return False# 定义发送附件邮件def send_file(self, file_new):sendfile = open(file_new, 'r', encoding='utf-8').read()att = MIMEText(sendfile, "base64", "utf-8")att.set_charset('utf8')att["Content-Type"] = "application/octet-stream"att.add_header('Content-Disposition', 'attachment', filename=('gbk', '', '%s.html' % self.subject))msg_root = MIMEMultipart('related')msg_root['Subject'] = Header(self.subject, 'utf-8')msg_root.attach(att)self.send_mail(msg_root)# 定义发送邮件HTMLdef send_html(self, file_new):f = open(file_new, 'rb')mail_body = f.read()f.close()msg = MIMEText(mail_body, 'html', 'utf-8')msg['Subject'] = Header(self.subject, 'utf-8')self.send_mail(msg)def send_report(self, file_new):msg = MIMEMultipart()now = time.strftime("%Y-%m-%d %H_%M_%S")msg['Subject'] = self.subject + now# xlsx类型的附件xlsxpart = MIMEApplication(open(file_new, 'rb').read())xlsxpart.add_header('Content-Disposition', 'attachment', filename='spider_result.xls')msg.attach(xlsxpart)return self.send_mail(msg)# 查找测试目录,找到最新生成的测试报告文件@staticmethoddef new_report(test_report):lists = os.listdir(test_report) # 列出目录的下所有文件和文件夹保存到listslists.sort(key=lambda fn: os.path.getmtime(test_report + "/" + fn)) # 按时间排序file_new = os.path.join(test_report, lists[-1]) # 获取最新的文件保存到file_newprint(file_new)return file_new

读取Excel

excel.py

#!/usr/bin/env python# _*_coding:utf-8_*_import xlrdimport xlwtimport osimport xlsxwriterfrom xlutils.copy import copyclass Operate_Excel(object):def __init__(self, filepath):xlrd.Book.encoding = "utf8" # 设置编码self.filepath = filepathdef write_sheet(self, sheetname):if not os.path.exists(self.filepath):# 创建工作簿self.workbook = xlwt.Workbook(encoding='utf-8', style_compression=0)self.worksheet = self.workbook.add_sheet(sheetname=sheetname, cell_overwrite_ok=True)self.workbook.save(self.filepath)else:self.wb = xlrd.open_workbook(self.filepath, formatting_info=True) # 打开需要操作的excel表, formatting_info 带格式导入self.workbook = copy(self.wb)# 复制原有表self.worksheet = self.workbook.add_sheet(sheetname)# 新增sheetself.workbook.save(self.filepath)def write_title_dict(self, title_list):self.value = title_list# 第一行,监控指标for i in range(1, len(self.value[0]) + 1):self.worksheet.write(0, i, self.value[0][i - 1])# 设置行宽,256为衡量单位,15表示15个字符宽度self.worksheet.col(i).width = 256 * 20# 第一列,网站for j in range(1, len(self.value[1]) + 1):self.worksheet.write(j, 0, self.value[1][j - 1])# 设置列宽,256为衡量单位,20表示20个字符宽度self.worksheet.col(0).width = 256 * 15self.workbook.save(self.filepath)def write_title_list(self, title_list):self.value = title_list# 第一行for i in range(0, len(self.value)):self.worksheet.write(0, i, self.value[i])# 设置行宽,256为衡量单位,15表示15个字符宽度self.worksheet.col(i).width = 256 * 25self.workbook.save(self.filepath)def write_data_dict(self, data, sheetname):self.book = xlrd.open_workbook(self.filepath)# self.sheet = self.book.sheet_by_index(0) # 取第一张工作簿self.sheet = self.book.sheet_by_name(sheetname) # 获取指定sheetname的工作簿rows = self.sheet.nrowscols = self.sheet.ncolsfor key, value in data.items():if value:for i in range(1, rows):if key == self.sheet.cell_value(i, 0):for j in range(1, cols):self.worksheet.write(i, j, value[j-1])self.workbook.save(self.filepath)def write_data_list(self, data, sheetname):self.book = xlrd.open_workbook(self.filepath)self.sheet = self.book.sheet_by_name(sheetname)rows = self.sheet.nrowscols = self.sheet.ncolsfor i in range(0, cols):self.worksheet.write(1, i, data[i])self.workbook.save(self.filepath)# -- 读指定 sheetname 的工作簿内容 by:panyuanyuan 0514def read_data_list(self, sheetname):self.book = xlrd.open_workbook(self.filepath)# self.sheet = self.book.sheet_by_index(0) # 取第一张工作簿self.sheet = self.book.sheet_by_name(sheetname) # 获取指定sheetname的工作簿rows = self.sheet.nrowsrows_data_list = []for i in range(1, rows):rows = self.sheet.row_values(i)rows_data_list.append(rows)return rows_data_listdef copy_book(self, filepath):book1 = xlrd.open_workbook(filepath, formatting_info=True) # 得到Excel文件的book对象,实例化对象book2 = copy(book1) # 拷贝一份原来的excelreturn book2def modify_table(self, book2, sheetname, data, row_num):sheet = book2.get_sheet(sheetname)for i in range(0, len(data)):sheet.write(row_num+1, i, data[i])

读取页面元素

BasePage.py

#!/usr/bin/env python# -*- coding:utf-8 -*-from PIL import Imagefrom pytesseract import pytesseractfrom selenium import webdriverfrom mon.exceptions import *from mon.action_chains import ActionChainsfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.select import Selectclass BasePage(object):"""BasePage封装所有页面都公用的方法,例如driver, url ,FindElement等"""# 初始化driver、url、pagetitle等# 实例化BasePage类时,最先执行的就是__init__方法,该方法的入参,其实就是BasePage类的入参。# __init__方法不能有返回值,只能返回None# self只实例本身,相较于类Page而言。def __init__(self, selenium_driver, base_url, pagetitle):self.driver = selenium_driverself.base_url = base_urlself.pagetitle = pagetitleself.timeout = 30@staticmethoddef browser(browser="Chrome"):"""打开浏览器函数,Firefox,chrome,IE,phantomjs默认Chrome浏览器"""try:if browser == "Chrome":driver = webdriver.Chrome()return driverelif browser == "firefox":driver = webdriver.Firefox()return driverelif browser == "IE":driver = webdriver.Ie()return driverelif browser == "phantomjs":driver = webdriver.PhantomJS()return driverelse:print("找不到驱动")except Exception as msg:print("%s" % msg)# 通过title断言进入的页面是否正确。# 使用title获取当前窗口title,检查输入的title是否在当前title中,返回比较结果(True 或 False)def on_page(self, pagetitle):return pagetitle in self.driver.titledef on_page_url(self, page_url):return page_url in self.driver.current_url# 打开页面,并校验页面链接是否加载正确# 以单下划线_开头的方法,在使用import *时,该方法不会被导入,保证该方法为类私有的。def _open(self, url, pagetitle):# 使用get打开访问链接地址self.driver.get(url)self.driver.maximize_window()# 使用assert进行校验,打开的窗口title是否与配置的title一致。调用on_page()方法assert self.on_page(pagetitle), u"打开页面失败 %s" % url# 定义open方法,调用_open()进行打开链接def open(self):self._open(self.base_url, self.pagetitle)self.driver.maximize_window()# 重写元素定位方法def find_element_by(self, *loc):try:# 确保元素是可见的。# 注意:以下入参为元组的元素,需要加*。Python存在这种特性,就是将入参放在元组里。# WebDriverWait(self.driver,10).until(lambda driver: driver.find_element(*loc).is_displayed())# 注意:以下入参本身是元组,不需要加*WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(loc))return self.driver.find_element(*loc)except Exception as e:print(e)print(u"%s 页面中未能找到 %s 元素" % (self, loc))# 重定义定位方法def find_elements(self, *element):try:WebDriverWait(self.driver, 30).until(EC.visibility_of_all_elements_located(element))return self.driver.find_elements(*element)except Exception as e:print(e)print("页面元素未能找到%s" % self, element)def base_open(self, url):try:self.driver = webdriver.Chrome()self.driver.maximize_window()self.driver.get(url)except Exception as e:print(e)# 定义close方法def close(self):try:self.driver.quit()except Exception as e:print(e)def find_element(self, name):try:element = self.driver.find_element_by_id(name)except Exception as e:try:element = self.driver.find_element_by_name(name)except Exception as e:try:element = self.driver.find_element_by_xpath(name)except Exception as e:try:element = self.driver.find_element_by_css_selector(name)except Exception as e:try:element = self.driver.find_element_by_class_name(name)except Exception as e:return Nonereturn element# 警告框def find_alert(self):try:alert = self.driver.switch_to_alert()alert.accept()except Exception as e:print(e)return Nonereturn alertdef input(self, tag, value):e = self.find_element(tag)if e:e.clear()e.send_keys(value)else:print("Not found: " + tag)# 点击操作def click(self, element):alink = self.find_element(element)if alink:alink.click()else:print("Not found: " + element)def href_click(self, tag):xpath_link = "//a[contains(@href,'" + tag + "')]"self.click(xpath_link)def select(self, tag, option):e = self.find_element(tag)if e:Select(e).select_by_value(option)else:print("Not found: " + tag)# 定义script方法,用于执行js脚本,范围执行结果def script(self, src):return self.driver.execute_script(src)# 重写定义send_keys方法def send_keys(self, loc, vaule, clear_first=True, click_first=True):try:loc = getattr(self, "_%s" % loc) # getattr相当于实现self.locif click_first:self.find_element(*loc).click()if clear_first:self.find_element(*loc).clear()self.find_element(*loc).send_keys(vaule)except AttributeError:print(u"%s 页面中未能找到 %s 元素" % (self, loc))# 定义截图方法def capture_page(self, imgfile):self.driver.save_screenshot(imgfile)def parse_verifycode(self, image_elementId):try:self.driver.save_screenshot('../Data/vert.png')e = self.find_element(image_elementId)rangle = (int(e.location['x']), int(e.location['y']), \int(e.location['x'] + e.size['width']), int(e.location['y'] + e.size['height']))Image.open('../Data/vert.png').crop(rangle).save('../Data/vc.png')vcode = pytesseract.image_to_string(Image.open('../Data/vc.png'), lang='eng')return vcodeexcept Exception as e:print(e)def is_located(self, element, timeout=10):"""判断元素有没被定位到(并不意味着可见),定位到返回element,没定位到返回False"""result = WebDriverWait(self.driver, timeout, 1).until(EC.presence_of_element_located(element))return result# --------------------------------------------------------------------------------------# 以下方法未验证def move_to_element(self, element):"""鼠标悬停操作Usage:element = ("id","xxx")driver.move_to_element(element)"""element = self.find_element(element)ActionChains(self.driver).move_to_element(element).perform()def back(self):"""浏览器返回窗口"""self.driver.back()def forward(self):"""浏览器前进下一个窗口"""self.driver.forward()def close(self):"""关闭浏览器"""self.driver.close()def quit(self):"""退出浏览器"""self.driver.quit()def get_title(self):"""获取title"""return self.driver.titledef get_text(self, element):"""获取文本"""element = self.find_element(element)return element.textdef get_attribute(self, element, name):"""获取属性"""element = self.find_element(element)return element.get_attribute(name)def js_execute(self, js):"""执行js"""return self.driver.execute_script(js)def js_focus_element(self, element):"""聚焦元素"""target = self.find_element(element)self.driver.execute_script("arguments[0].scrollIntoView();", target)def js_scroll_top(self):"""滚动到顶部"""js = "window.scrollTo(0,0)"self.driver.execute_script(js)def js_scroll_end(self):"""滚动到底部"""js = "window.scrollTo(0,document.body.scrollHeight)"self.driver.execute_script(js)def select_by_index(self, element, index):"""通过索引,index是索引第几个,从0开始"""element = self.find_element(element)Select(element).select_by_index(index)def select_by_value(self, element, value):"""通过value属性"""element = self.find_element(element)Select(element).select_by_value(value)def select_by_text(self, element, text):"""通过文本值定位"""element = self.find_element(element)Select(element).select_by_value(text)def is_text_in_element(self, element, text, timeout=10):"""判断文本在元素里,没定位到元素返回False,定位到元素返回判断结果布尔值"""try:result = WebDriverWait(self.driver, timeout, 1).until(EC.text_to_be_present_in_element(element, text))except TimeoutException:print("元素没有定位到:" + str(element))return Falseelse:return resultdef is_text_in_value(self, element, value, timeout=10):"""判断元素的value值,没定位到元素返回false,定位到返回判断结果布尔值result = driver.text_in_element(element, text):param element::param value::param timeout::return:"""try:result = WebDriverWait(self.driver, timeout, 1).until(EC.text_to_be_present_in_element_value(element, value))except TimeoutException:print("元素没定位到:" + str(element))return Falseelse:return resultdef is_title(self, title, timeout=10):"""判断title完全等于"""result = WebDriverWait(self.driver, timeout, 1).until(EC.title_is(title))return resultdef is_title_contains(self, title, timeout=10):"""判断title包含"""result = WebDriverWait(self.driver, timeout, 1).until(EC.title_contains(title))return resultdef is_selected(self, element, timeout=10):"""判断元素被选中,返回布尔值"""result = WebDriverWait(self.driver, timeout, 1).until(EC.element_located_to_be_selected(element))return resultdef is_selected_be(self, element, selected=True, timeout=10):"""判断元素的状态,selected是期望的参数true/False返回布尔值"""result = WebDriverWait(self.driver, timeout, 1).until(EC.element_located_selection_state_to_be(element,selected))return resultdef is_alert_present(self, timeout=10):"""判断页面是否有alert,有返回alert(注意这里是返回alert,不是True)没有返回False"""result = WebDriverWait(self.driver, timeout, 1).until(EC.alert_is_present())return resultdef is_visibility(self, element, timeout=10):"""元素可见返回本身,不可见返回Fasle"""result = WebDriverWait(self.driver, timeout, 1).until(EC.visibility_of_element_located(element))return resultdef is_invisibility(self, element, timeout=10):"""元素可见返回本身,不可见返回True,没找到元素也返回True"""result = WebDriverWait(self.driver, timeout, 1).until(EC.invisibility_of_element_located(element))return resultdef is_clickable(self, element, timeout=10):"""元素可以点击is_enabled返回本身,不可点击返回Fasle"""result = WebDriverWait(self.driver, timeout, 1).until(EC.element_to_be_clickable(element))return result

excel读取测试用例

#!/usr/bin/env python# -*- coding:utf-8 -*-# author : from __future__ import divisionimport xlrdfrom xlutils.copy import copyclass ExcelUtil(object):def __init__(self, excel_path, sheet_name):# 打开文件,在读取时要添加formatting_info=True参数,默认是False,表示原样读取,在读取时要添加formatting_info只支持xls文件self.book = xlrd.open_workbook(excel_path, formatting_info=True)self.table = self.book.sheet_by_name(sheet_name)# get titlesself.row = self.table.row_values(0)# get rows numberself.row_num = self.table.nrows# get columns numberself.col_num = self.table.ncols# the current columnself.cur_row_no = 1def next(self):r = []while self.has_next():s = {}col = self.table.row_values(self.cur_row_no)i = self.col_numfor x in range(i):s[self.row[x]] = col[x]r.append(s)self.cur_row_no += 1self.cur_row_no = 0return rdef has_next(self):if self.row_num == 0 or self.row_num <= self.cur_row_no:return Falseelse:return Truedef copy_book(self):return copy(self.book)def modify(self, book, sheetname, data):"""{'测试点': '', '实际结果': '0', '测试用例': '非法值校验(空、边界外值、负值、小数、非int型、None、NULL)','是否执行(T/F)': 'T', '测试模块': '', '描述': 'success', '测试数据': 'keyword:{STRING}\nip:{IP}\nlocation:{STRING}\nstartIndex:{INT_F}\nfetchNum:{INT}','是否通过(T/F)': 'F', '用例编号': 'case_006', '请求方式(POST/GET)': 'GET', '接口地址': '/xs_api/video/possible','预期结果': '104\\105\\106'}"""sheet = book.get_sheet(sheetname)while self.has_next():col = self.table.row_values(self.cur_row_no)if data["用例编号"] == col[0]:for i in range(self.col_num):for key, value in data.items():if self.row[i] == key:sheet.write(self.cur_row_no, i, value)self.cur_row_no += 1self.cur_row_no = 0def read_merged_excel(self):colspan = {}r = []if self.table.merged_cells:for item in self.table.merged_cells:# 通过循环进行组合,从而得出所有的合并单元格的坐标for row in range(item[0], item[1]):for col in range(item[2], item[3]):# 合并单元格的首格是有值的,所以在这里进行了去重if (row, col) != (item[0], item[2]):colspan.update({(row, col): (item[0], item[2])})# 开始循环读取excel中的每一行的数据for i in range(1, self.table.nrows):s = {}for j in range(self.table.ncols):# 假如碰见合并的单元格坐标,取合并的首格的值即可if colspan.get((i, j)):d = self.table.cell_value(*colspan.get((i, j)))else:d = self.table.cell_value(i, j)s[self.row[j]] = dr.append(s)return r@staticmethoddef get_merged_cells(sheet_name):"""获取所有的合并单元格,格式如下:[(4, 5, 2, 4), (5, 6, 2, 4), (1, 4, 3, 4)](4, 5, 2, 4) 的含义为:行 从下标4开始,到下标5(不包含) 列 从下标2开始,到下标4(不包含),为合并单元格:param sheet_name::return:"""return sheet_name.merged_cellsdef get_merged_cells_value(self, sheet_name, row_index, col_index):"""先判断给定的单元格,是否属于合并单元格;如果是合并单元格,就返回合并单元格的内容:return:"""merged = self.get_merged_cells(sheet_name)for (rlow, rhigh, clow, chigh) in merged:if rlow <= row_index < rhigh:if clow <= col_index < chigh:cell_value = sheet_name.cell_value(rlow, clow)# print('该单元格[%d,%d]属于合并单元格,值为[%s]' % (row_index, col_index, cell_value))return cell_valuebreakreturn Noneif __name__ == '__main__':# test: modify()aa = {'用例编号': 'case_001', '是否执行(T/F)': 'T', '测试模块': '疑似记录接口', '测试点': 'keyword参数值校验', '测试用例': 'keyword字段合法值校验(中文、英文、特殊字符、None、NULL)', '接口地址': '/xs_api/video/possible', '请求方式(POST/GET)': 'GET', '测试数据': 'keyword:{STRING}\nip:{IP}\nlocation:{STRING}\nstartIndex:{INT}\nfetchNum:{INT}', '预期结果': '0', '实际结果': '', '描述': 'aaa', '是否通过(T/F)': ''}# print([value for value in aa.values()])EXCEL = ExcelUtil("E:/测试用例.xls", "接口测试用例(手工+自动化)")qq = EXCEL.copy_book()EXCEL.modify(qq, "接口测试用例(手工+自动化)", aa)qq.save('E:/123.xls')# test end

测试报告模板

# -*- coding: utf-8 -*-"""A TestRunner for use with the Python unit testing framework. Itgenerates a HTML report to show the result at a glance.The simplest way to use this is to invoke its main method. E.g.import unittestimport HTMLTestRunner... define your tests ...if __name__ == '__main__':HTMLTestRunner.main()For more customization options, instantiates a HTMLTestRunner object.HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.# output to a filefp = file('my_report.html', 'wb')runner = HTMLTestRunner.HTMLTestRunner(stream=fp,title='My unit test',description='This demonstrates the report output by HTMLTestRunner.')# Use an external stylesheet.# See the Template_mixin class for more customizable optionsrunner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'# run the testrunner.run(my_test_suite)------------------------------------------------------------------------Copyright (c) -, Wai Yip TungAll rights reserved.Redistribution and use in source and binary forms, with or withoutmodification, are permitted provided that the following conditions aremet:* Redistributions of source code must retain the above copyright notice,this list of conditions and the following disclaimer.* Redistributions in binary form must reproduce the above copyrightnotice, this list of conditions and the following disclaimer in thedocumentation and/or other materials provided with the distribution.* Neither the name Wai Yip Tung nor the names of its contributors may beused to endorse or promote products derived from this software withoutspecific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "ASIS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITEDTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR APARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNEROR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, ORPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OFLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDINGNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THISSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."""# URL: http://tungwaiyip.info/software/HTMLTestRunner.html__author__ = "Wai Yip Tung"__version__ = "0.9.1""""Change HistoryVersion 0.9.1* 用Echarts添加执行情况统计图 (灰蓝)Version 0.9.0* 改成Python 3.x (灰蓝)Version 0.8.3* 使用 Bootstrap稍加美化 (灰蓝)* 改为中文 (灰蓝)Version 0.8.2* Show output inline instead of popup window (Viorel Lupu).Version in 0.8.1* Validated XHTML (Wolfgang Borgert).* Added description of test classes and test cases.Version in 0.8.0* Define Template_mixin class for customization.* Workaround a IE 6 bug that it does not treat <script> block as CDATA.Version in 0.7.1* Back port to Python 2.3 (Frank Horowitz).* Fix missing scroll bars in detail log (Podi)."""# TODO: color stderr# TODO: simplify javascript using ,ore than 1 class in the class attribute?import datetimeimport sysimport ioimport timeimport unittestfrom xml.sax import saxutils# ------------------------------------------------------------------------# The redirectors below are used to capture output during testing. Output# sent to sys.stdout and sys.stderr are automatically captured. However# in some cases sys.stdout is already cached before HTMLTestRunner is# invoked (e.g. calling logging.basicConfig). In order to capture those# output, use the redirectors for the cached stream.## e.g.# >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)# >>>class OutputRedirector(object):""" Wrapper to redirect stdout or stderr """def __init__(self, fp):self.fp = fpdef write(self, s):self.fp.write(s)def writelines(self, lines):self.fp.writelines(lines)def flush(self):self.fp.flush()stdout_redirector = OutputRedirector(sys.stdout)stderr_redirector = OutputRedirector(sys.stderr)# ----------------------------------------------------------------------# Templateclass Template_mixin(object):"""Define a HTML template for report customerization and generation.Overall structure of an HTML reportHTML+------------------------+|<html> || <head>|| || STYLESHEET || +----------------+ || || || +----------------+ || || </head>|| || <body>|| || HEADING || +----------------+ || || || +----------------+ || || REPORT|| +----------------+ || || || +----------------+ || || ENDING|| +----------------+ || || || +----------------+ || || </body>||</html> |+------------------------+"""STATUS = {0: u'通过',1: u'失败',2: u'错误',}DEFAULT_TITLE = 'Unit Test Report'DEFAULT_DESCRIPTION = ''# ------------------------------------------------------------------------# HTML TemplateHTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="/1999/xhtml"><head><title>%(title)s</title><meta name="generator" content="%(generator)s"/><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><link href="/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet"><script src="/echarts/3.8.5/mon.min.js"></script><!-- <script type="text/javascript" src="js/mon.min.js"></script> -->%(stylesheet)s</head><body><script language="javascript" type="text/javascript"><!--output_list = Array();/* level - 0:Summary; 1:Failed; 2:All */function showCase(level) {trs = document.getElementsByTagName("tr");for (var i = 0; i < trs.length; i++) {tr = trs[i];id = tr.id;if (id.substr(0,2) == 'ft') {if (level < 1) {tr.className = 'hiddenRow';}else {tr.className = '';}}if (id.substr(0,2) == 'pt') {if (level > 1) {tr.className = '';}else {tr.className = 'hiddenRow';}}}}function showClassDetail(cid, count) {var id_list = Array(count);var toHide = 1;for (var i = 0; i < count; i++) {tid0 = 't' + cid.substr(1) + '.' + (i+1);tid = 'f' + tid0;tr = document.getElementById(tid);if (!tr) {tid = 'p' + tid0;tr = document.getElementById(tid);}id_list[i] = tid;if (tr.className) {toHide = 0;}}for (var i = 0; i < count; i++) {tid = id_list[i];if (toHide) {document.getElementById('div_'+tid).style.display = 'none'document.getElementById(tid).className = 'hiddenRow';}else {document.getElementById(tid).className = '';}}}function showTestDetail(div_id){var details_div = document.getElementById(div_id)var displayState = details_div.style.display// alert(displayState)if (displayState != 'block' ) {displayState = 'block'details_div.style.display = 'block'}else {details_div.style.display = 'none'}}function html_escape(s) {s = s.replace(/&/g,'&amp;');s = s.replace(/</g,'&lt;');s = s.replace(/>/g,'&gt;');return s;}/* obsoleted by detail in <div>function showOutput(id, name) {var w = window.open("", //urlname,"resizable,scrollbars,status,width=800,height=450");d = w.document;d.write("<pre>");d.write(html_escape(output_list[id]));d.write("\n");d.write("<a href='javascript:window.close()'>close</a>\n");d.write("</pre>\n");d.close();}*/--></script><div id="div_base">%(heading)s%(report)s%(ending)s%(chart_script)s</div></body></html>""" # variables: (title, generator, stylesheet, heading, report, ending, chart_script)ECHARTS_SCRIPT = """<script type="text/javascript">// 基于准备好的dom,初始化echarts实例var myChart = echarts.init(document.getElementById('chart'));// 指定图表的配置项和数据var option = {title : {text: '测试执行情况',x:'center'},tooltip : {trigger: 'item',formatter: "{a} <br/>{b} : {c} ({d}%%)"},color: ['#95b75d', 'grey', '#b64645'],legend: {orient: 'vertical',left: 'left',data: ['通过','失败','错误']},series : [{name: '测试执行情况',type: 'pie',radius : '60%%',center: ['50%%', '60%%'],data:[{value:%(Pass)s, name:'通过'},{value:%(fail)s, name:'失败'},{value:%(error)s, name:'错误'}],itemStyle: {emphasis: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: 'rgba(0, 0, 0, 0.5)'}}}]};// 使用刚指定的配置项和数据显示图表。myChart.setOption(option);</script>""" # variables: (Pass, fail, error)# ------------------------------------------------------------------------# Stylesheet## alternatively use a <link> for external style sheet, e.g.# <link rel="stylesheet" href="$url" type="text/css">STYLESHEET_TMPL = """<style type="text/css" media="screen">body { font-family: Microsoft YaHei,Consolas,arial,sans-serif; font-size: 130%; }table { font-size: 150%; }pre { white-space: pre-wrap;word-wrap: break-word; }/* -- heading ---------------------------------------------------------------------- */h1 {font-size: 19pt;color: gray;}.heading {margin-top: 0ex;margin-bottom: 1ex;}.heading .attribute {margin-top: 1ex;margin-bottom: 0;}.heading .description {margin-top: 2ex;margin-bottom: 3ex;}/* -- css div popup ------------------------------------------------------------------------ */a.popup_link {}a.popup_link:hover {color: red;}.popup_window {display: none;position: relative;left: 0px;top: 0px;/*border: solid #627173 1px; */padding: 10px;/*background-color: #E6E6D6; */font-family: "Lucida Console", "Courier New", Courier, monospace;text-align: left;font-size: 11pt;/* width: 500px;*/}}/* -- report ------------------------------------------------------------------------ */#show_detail_line {margin-top: 3ex;margin-bottom: 1ex;}#result_table {width: 99%;}#header_row {font-weight: bold;color: #303641;background-color: #ebebeb;}#total_row { font-weight: bold; }.passClass { background-color: #bdedbc; }.failClass { background-color: #ffefa4; }.errorClass { background-color: #ffc9c9; }.passCase { color: #6c6; }.failCase { color: #FF6600; font-weight: bold; }.errorCase { color: #c00; font-weight: bold; }.hiddenRow { display: none; }.testcase { margin-left: 2em; }/* -- ending ---------------------------------------------------------------------- */#ending {}#div_base {position:absolute;top:0%;left:5%;right:5%;width: auto;height: auto;margin: -15px 0 0 0;}</style>"""# ------------------------------------------------------------------------# Heading#HEADING_TMPL = """<div class='page-header'><h1>%(title)s</h1>%(parameters)s</div><div style="float: left;width:50%%;"><p class='description'>%(description)s</p></div><div id="chart" style="width:50%%;height:400px;float:left;"></div>""" # variables: (title, parameters, description)HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>""" # variables: (name, value)# ------------------------------------------------------------------------# Report#REPORT_TMPL = u"""<div class="btn-group btn-group-sm"><button class="btn btn-default" onclick='javascript:showCase(0)'>总结</button><button class="btn btn-default" onclick='javascript:showCase(1)'>失败</button><button class="btn btn-default" onclick='javascript:showCase(2)'>全部</button></div><p></p><table id='result_table' class="table table-bordered"><colgroup><col align='left' /><col align='right' /><col align='right' /><col align='right' /><col align='right' /><col align='right' /></colgroup><tr id='header_row'><td>测试套件/测试用例</td><td>总数</td><td>通过</td><td>失败</td><td>错误</td><td>查看</td></tr>%(test_list)s<tr id='total_row'><td>总计</td><td>%(count)s</td><td>%(Pass)s</td><td>%(fail)s</td><td>%(error)s</td><td>&nbsp;</td></tr></table>""" # variables: (test_list, count, Pass, fail, error)REPORT_CLASS_TMPL = u"""<tr class='%(style)s'><td>%(desc)s</td><td>%(count)s</td><td>%(Pass)s</td><td>%(fail)s</td><td>%(error)s</td><td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详情</a></td></tr>""" # variables: (style, desc, count, Pass, fail, error, cid)REPORT_TEST_WITH_OUTPUT_TMPL = r"""<tr id='%(tid)s' class='%(Class)s'><td class='%(style)s'><div class='testcase'>%(desc)s</div></td><td colspan='5' align='center'><!--css div popup start--><a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >%(status)s</a><div id='div_%(tid)s' class="popup_window"><pre>%(script)s</pre></div><!--css div popup end--></td></tr>""" # variables: (tid, Class, style, desc, status)REPORT_TEST_NO_OUTPUT_TMPL = r"""<tr id='%(tid)s' class='%(Class)s'><td class='%(style)s'><div class='testcase'>%(desc)s</div></td><td colspan='5' align='center'>%(status)s</td></tr>""" # variables: (tid, Class, style, desc, status)REPORT_TEST_OUTPUT_TMPL = r"""%(id)s: %(output)s""" # variables: (id, output)# ------------------------------------------------------------------------# ENDING#ENDING_TMPL = """<div id='ending'>&nbsp;</div>"""# -------------------- The end of the Template class -------------------TestResult = unittest.TestResultclass _TestResult(TestResult):# note: _TestResult is a pure representation of results.# It lacks the output and reporting ability compares to unittest._TextTestResult.def __init__(self, verbosity=1):TestResult.__init__(self)self.stdout0 = Noneself.stderr0 = Noneself.success_count = 0self.failure_count = 0self.error_count = 0self.verbosity = verbosity# result is a list of result in 4 tuple# (# result code (0: success; 1: fail; 2: error),# TestCase object,# Test output (byte string),# stack trace,# )self.result = []self.subtestlist = []def startTest(self, test):TestResult.startTest(self, test)# just one buffer for both stdout and stderrself.outputBuffer = io.StringIO()stdout_redirector.fp = self.outputBufferstderr_redirector.fp = self.outputBufferself.stdout0 = sys.stdoutself.stderr0 = sys.stderrsys.stdout = stdout_redirectorsys.stderr = stderr_redirectordef complete_output(self):"""Disconnect output redirection and return buffer.Safe to call multiple times."""if self.stdout0:sys.stdout = self.stdout0sys.stderr = self.stderr0self.stdout0 = Noneself.stderr0 = Nonereturn self.outputBuffer.getvalue()def stopTest(self, test):# Usually one of addSuccess, addError or addFailure would have been called.# But there are some path in unittest that would bypass this.# We must disconnect stdout in stopTest(), which is guaranteed to be plete_output()def addSuccess(self, test):if test not in self.subtestlist:self.success_count += 1TestResult.addSuccess(self, test)output = plete_output()self.result.append((0, test, output, ''))if self.verbosity > 1:sys.stderr.write('ok ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('.')def addError(self, test, err):self.error_count += 1TestResult.addError(self, test, err)_, _exc_str = self.errors[-1]output = plete_output()self.result.append((2, test, output, _exc_str))if self.verbosity > 1:sys.stderr.write('E ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('E')def addFailure(self, test, err):self.failure_count += 1TestResult.addFailure(self, test, err)_, _exc_str = self.failures[-1]output = plete_output()self.result.append((1, test, output, _exc_str))if self.verbosity > 1:sys.stderr.write('F ')sys.stderr.write(str(test))sys.stderr.write('\n')else:sys.stderr.write('F')def addSubTest(self, test, subtest, err):if err is not None:if getattr(self, 'failfast', False):self.stop()if issubclass(err[0], test.failureException):self.failure_count += 1errors = self.failureserrors.append((subtest, self._exc_info_to_string(err, subtest)))output = plete_output()self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),self._exc_info_to_string(err, subtest)))if self.verbosity > 1:sys.stderr.write('F ')sys.stderr.write(str(subtest))sys.stderr.write('\n')else:sys.stderr.write('F')else:self.error_count += 1errors = self.errorserrors.append((subtest, self._exc_info_to_string(err, subtest)))output = plete_output()self.result.append((2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))if self.verbosity > 1:sys.stderr.write('E ')sys.stderr.write(str(subtest))sys.stderr.write('\n')else:sys.stderr.write('E')self._mirrorOutput = Trueelse:self.subtestlist.append(subtest)self.subtestlist.append(test)self.success_count += 1output = plete_output()self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))if self.verbosity > 1:sys.stderr.write('ok ')sys.stderr.write(str(subtest))sys.stderr.write('\n')else:sys.stderr.write('.')class HTMLTestRunner(Template_mixin):def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):self.stream = streamself.verbosity = verbosityif title is None:self.title = self.DEFAULT_TITLEelse:self.title = titleif description is None:self.description = self.DEFAULT_DESCRIPTIONelse:self.description = descriptionself.startTime = datetime.datetime.now()def run(self, test):"Run the given test case or test suite."result = _TestResult(self.verbosity)test(result)self.stopTime = datetime.datetime.now()self.generateReport(test, result)print('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)return resultdef sortResult(self, result_list):# unittest does not seems to run in any particular order.# Here at least we want to group them together by class.rmap = {}classes = []for n, t, o, e in result_list:cls = t.__class__if cls not in rmap:rmap[cls] = []classes.append(cls)rmap[cls].append((n, t, o, e))r = [(cls, rmap[cls]) for cls in classes]return rdef getReportAttributes(self, result):"""Return report attributes as a list of (name, value).Override this to add custom attributes."""startTime = str(self.startTime)[:19]duration = str(self.stopTime - self.startTime)status = []if result.success_count:status.append(u'通过 %s' % result.success_count)if result.failure_count:status.append(u'失败 %s' % result.failure_count)if result.error_count:status.append(u'错误 %s' % result.error_count)if status:status = ' '.join(status)else:status = 'none'return [(u'开始时间', startTime),(u'运行时长', duration),(u'状态', status),]def generateReport(self, test, result):report_attrs = self.getReportAttributes(result)generator = 'HTMLTestRunner %s' % __version__stylesheet = self._generate_stylesheet()heading = self._generate_heading(report_attrs)report = self._generate_report(result)ending = self._generate_ending()chart = self._generate_chart(result)output = self.HTML_TMPL % dict(title=saxutils.escape(self.title),generator=generator,stylesheet=stylesheet,heading=heading,report=report,ending=ending,chart_script=chart)self.stream.write(output.encode('utf8'))def _generate_stylesheet(self):return self.STYLESHEET_TMPLdef _generate_heading(self, report_attrs):a_lines = []for name, value in report_attrs:line = self.HEADING_ATTRIBUTE_TMPL % dict(name=saxutils.escape(name),value=saxutils.escape(value),)a_lines.append(line)heading = self.HEADING_TMPL % dict(title=saxutils.escape(self.title),parameters=''.join(a_lines),description=saxutils.escape(self.description),)return headingdef _generate_report(self, result):rows = []sortedResult = self.sortResult(result.result)for cid, (cls, cls_results) in enumerate(sortedResult):# subtotal for a classnp = nf = ne = 0for n, t, o, e in cls_results:if n == 0:np += 1elif n == 1:nf += 1else:ne += 1# format class descriptionif cls.__module__ == "__main__":name = cls.__name__else:name = "%s.%s" % (cls.__module__, cls.__name__)doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""desc = doc and '%s: %s' % (name, doc) or namerow = self.REPORT_CLASS_TMPL % dict(style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',desc=desc,count=np+nf+ne,Pass=np,fail=nf,error=ne,cid='c%s' % (cid+1),)rows.append(row)for tid, (n, t, o, e) in enumerate(cls_results):self._generate_report_test(rows, cid, tid, n, t, o, e)report = self.REPORT_TMPL % dict(test_list=''.join(rows),count=str(result.success_count+result.failure_count+result.error_count),Pass=str(result.success_count),fail=str(result.failure_count),error=str(result.error_count),)return reportdef _generate_chart(self, result):chart = self.ECHARTS_SCRIPT % dict(Pass=str(result.success_count),fail=str(result.failure_count),error=str(result.error_count),)return chartdef _generate_report_test(self, rows, cid, tid, n, t, o, e):# e.g. 'pt1.1', 'ft1.1', etchas_output = bool(o or e)tid = (n == 0 and 'p' or 'f') + 't%s.%s' % (cid+1, tid+1)name = t.id().split('.')[-1]doc = t.shortDescription() or ""desc = doc and ('%s: %s' % (name, doc)) or nametmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPLscript = self.REPORT_TEST_OUTPUT_TMPL % dict(id=tid,output=saxutils.escape(o+e),)row = tmpl % dict(tid=tid,Class=(n == 0 and 'hiddenRow' or 'none'),style=(n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none')),desc=desc,script=script,status=self.STATUS[n],)rows.append(row)if not has_output:returndef _generate_ending(self):return self.ENDING_TMPL############################################################################### Facilities for running tests from the command line############################################################################### Note: Reuse unittest.TestProgram to launch test. In the future we may# build our own launcher to support more specific command line# parameters like test title, CSS, etc.class TestProgram(unittest.TestProgram):"""A variation of the unittest.TestProgram. Please refer to the baseclass for command line parameters."""def runTests(self):# Pick HTMLTestRunner as the default test runner.# base class's testRunner parameter is not useful because it means# we have to instantiate HTMLTestRunner before we know self.verbosity.if self.testRunner is None:self.testRunner = HTMLTestRunner(verbosity=self.verbosity)unittest.TestProgram.runTests(self)main = TestProgram############################################################################### Executing this module from the command line##############################################################################if __name__ == "__main__":main(module=None)

操作数据库模块 支持mysql pgsql

sqlserver.sql

#!/usr/bin/env python# -*- coding: UTF-8 -*-from __future__ import print_functionimport pymysqlimport psycopg2class SqlService(object):def __init__(self, host, user, password, dbname=""):self.host = hostself.user = userself.passwd = passwordself.dbname = dbnamedef conn(self):passdef setconn(self, conn, cur):self.conn = connself.cur = curdef selectdb(self, dbname):self.conn.select_db(dbname)# insert, update, deletedef execute(self, statement, params=()):self.cur = self.conn.cursor()if len(params) == 0:self.cur.execute(statement)else:self.cur.execute(statement, params)mit()self.cur.close()# querydef select(self, statement, params = ()):self.cur = self.conn.cursor()if len(params) == 0:self.cur.execute(statement)else:self.cur.execute(statement, params)results = self.cur.fetchall()self.cur.close()return resultsdef commit(self):mit()def rollback(self):self.conn.rollback()# 批量的插入数据# 执行单条sql语句, 重复执行参数列表中参数def executemany(self, statement, params):if len(params) == 0:returnelse:self.cur = self.conn.cursor()self.cur.executemany(statement, params)mit()self.cur.close()# 关闭connection连接def close(self):self.conn.close()class MySqlService(SqlService):def __init__(self, host, user, password, dbname=""):super(MySqlService, self).__init__(host, user, password, dbname)def connect(self):try:self.conn = pymysql.connect(host=self.host, user=self.user, passwd=self.passwd, db=self.dbname, charset='utf8', use_unicode=True)self.cur = self.conn.cursor()super(MySqlService, self).setconn(self.conn, self.cur)return self.connexcept pymysql.Error as e:print("connect {} server, user: {} , passwd: {}, exception: {}, {}").format(self.host, self.user, self.passwd,e.args[0], e.args[1])return Noneclass PgSqlService(SqlService):def __init__(self, host, user, password, dbname=""):super(PgSqlService, self).__init__(host, user, password, dbname)def connect(self):try:self.conn = psycopg2.connect(host=self.host, user=self.user, password=self.passwd, database=self.dbname, port="5432")self.cur = self.conn.cursor()super(PgSqlService, self).setconn(self.conn, self.cur)return self.connexcept psycopg2.Error as e:print("connect {} server, user: {} , passwd: {}, exception: {}, {}").format(self.host, self.user, self.passwd,e.pgcode, e)return None

公共工具模块

UtilityTools.py

#!/usr/bin/env python# -*- coding:utf-8 -*-from __future__ import divisionimport requestsfrom itertools import productfrom base.mail import SendEmailimport timeimport unittestfrom testgroup.AutoTest.Lib import HTMLTestRunner_PY3from base.utils.log import *from testgroup.AutoTest.Projects.BaseTest.Data.base_data import *Logger().enableConsole(True)log = Logger().getLogger('UtilityTools', './{}.log'.format('UtilityTools'))def response_json_data(params, url, method, file_path=""):"""发送请求,解析json, return data author : panyuanyuan 0522:param params: 请求所需的参数,字典的形式:param url: 目的地址:param method: 请求方法:GET、POST、FILE,错写或不填默认POST:param file_path: method为FILE时,需指定上传的文件路径:return:"""requests.Session()if method == 'GET':response = requests.get(url=url, params=params, verify=False)elif method == 'FILE':files = {'file': open(file_path, 'rb')}response = requests.post(url=url, params=params, verify=False, files=files)else:response = requests.post(url=url, params=params, verify=False)data = response.json()return datadef param_parse(datas, col_name):"""根据参数个数生成需要传递的参数列表 author : panyuanyuan 0522:param datas: 从excel表中读出的一条数据:param col_name: 需要处理的单元格所在列的名称:return:"""# 表格数据转换成字典params_data = param_to_dict(datas, col_name)param_key_list = [param_key for param_key in params_data.keys()]param_value_list = [tuple(param_value) for param_value in params_data.values()]# itertools.product 计算笛卡尔积params_value = list(product(*param_value_list))# 将参数组合转换成对应的字典后以列表的形式返回params_list = []for param in params_value:params_dic = {}for param_count in range(len(param_key_list)):params_dic[param_key_list[param_count]] = param[param_count]params_list.append(params_dic)return params_listdef param_to_dict(datas, col_name):"""表格数据转换成字典 author : panyuanyuan 0522:param datas: 从excel表中读出的一条数据:param col_name: 需要处理的单元格所在列的名称:return:"""params_data = {}for data in datas[col_name].split('\n'):key_value = data.split(':')params_data[key_value[0]] = eval(key_value[1].replace('{', '').replace('}', ''))return params_datadef html_report(base_dir, case_dir, case_num, case_subject):"""执行测试:生成HTML报告:param base_dir:项目基础目录:param case_dir:测试用例目录:param case_num:测试用例文件名:param case_subject:测试主题:return:生成的报告路径"""# 获取当前时间now = time.strftime("%Y-%m-%d %H_%M_%S")# 指定生成报告存放目录report_dir = base_dir + r'/Report/'filename = report_dir + now + '_result.html'fp = open(filename, 'wb')runner = HTMLTestRunner_PY3.HTMLTestRunner(stream=fp,title=case_subject,description=u'测试用例执行结果')# 执行测试runner.run(creatsuite(case_dir, case_num))fp.close()return report_dirdef creatsuite(case_dir, case_num):"""构造测试集:根据测试用例创建一个测试套件:param case_dir: 测试用例路径:param case_num: 测试用例文件名:return:"""testunit = unittest.TestSuite()# discover标准加载测试用例并返回套件discover = unittest.defaultTestLoader.discover(case_dir,pattern='Test{}*.py'.format(str(case_num)),top_level_dir=None)# discover 方法筛选出来的用例,循环添加到测试套件中# 可以直接返回discover,下面主要用于调整测试用例的执行顺序for test_suite in discover:for test_case in test_suite:testunit.addTests(test_case)return testunitdef send_report(receiver_list, test_subject, report_path):"""自动发送邮件:param receiver_list: 接收者列表:param test_subject: 邮件主题:param report_path: 附件路径:return:"""send_email = SendEmail.Mail(user="88@", pwd="88", sender='88@',receiver=receiver_list, subject=test_subject)new_report = send_email.new_report(report_path)send_email.send_file(new_report)log.info('send email success...')

Python Unitest 自动化测试框架(V2.0)生成测试报告 发送邮件 excel用例数据驱动 接口自动化 Selenium 页面自动化 测试结果记录数据库

如果觉得《Python Unitest 自动化测试框架(V2.0)生成测试报告 发送邮件 excel用例数据》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。