「手把手教python3接口自动化」: 搭建接口自动化测试框架
zhezhongyun 2024-12-16 17:34 77 浏览
「第二十六章」 搭建接口自动化测试框架
26.1 为什么要开发Python3 接口测试框架
先看教育局招生管理系统登录接口用例。
import requests
from bs4 import BeautifulSoup
import xlrd
import json
def readExcel(rowx, filePath='data.xls'):
'''
读取excel中数据并且返回
:parameter filePath:xlsx文件名称
:parameter rowx:在excel中的行数
'''
book = xlrd.open_workbook(filePath)
sheet = book.sheet_by_index(0)
return sheet.row_values(rowx)
#提取第一个测试用例数据
print("第一行数据内容:",readExcel(2))
#查看数据的类型
print("数据类型:{0}:".format(type(readExcel(2))))
# 从列表中提取url
url = readExcel(2)[3]
print(url)
# 从列表中提取data参数
# 由于JSON中,标准语法中,不支持单引号,属性或者属性值,都必须是双引号括起来,用字符串方法replace("'",'\"')进行处理
data1 =readExcel(2)[4].replace("'",'\"')
#因为请求参数数据类型是字典,所以进行了反序列化的处理。
data = json.loads(data1)
print(data)
print(type(data))
print("---------------------------------------------")
#招生系统接口例子
headers = {
"Connection": "keep-alive",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36",
"Referer": "http://127.0.0.1:8090/recruit.students/login/view",
}
# URL参数
payload = data
# 发送get请求
response = requests.get(url,headers = headers,params=payload)
# 打印请求response后的URL
print(response.url)
# 查看响应内容,response.text 返回的是Unicode格式的数据
print(response.text)
# 查看响应码
print(response.status_code)
从教育局招生管理系统登录接口用例中,我们看到,要完成一个接口测试,需要写很多行的代码,一个完整的系统测试,测试用例可能有100~1000个,那么如果每个用例都一个一个这样写,那么编写用例和维护用例的成本就很高,而且代码很多,不直观。
随着自动化测试的不断推广,各家软件科技公司也不断的引进了专门的自动化测试人员,一个完整的系统测试,测试用例中有许多共用的模块和方法,不需要每个人都重复去写,这就需要有资深的测试专家去规划一个项目的自动化测试框架。
同时,接口测试生成测试报告,发送给相关的领导,也是需要一个完整的接口自动化测试框架。
26.2 Python3 接口自动化测试框架介绍
a) 基于 python 脚本编写,不需要借助其他工具,环境配置简单。
b) 采用数据驱动测试的方式,后期仅仅只需要维护一个测试数据。
c) 测试用例可以分模块编写,方便对用例的管理维护。
d) 测试用例和测试执行分离,实现执行测试时用例可以任意配置。
e) 实现多线程执行,可以同时运行多个测试集。
f) 测试结束后直接生成测试报告,统计测试执行情况,执行情况详细,能够快速定位问题,并且容易扩展优化。
26.3 框架流程说明
26.4 框架的目录结构
【common】:存放公共的方法。
【result】:执行过程中生成的文件夹,里面存放每次测试的结果。
【testCase】:用于存放具体的测试case。
【testFile】:存放测试过程中用到的文件,包括上传的文件,测试用例数据驱动以及数据库的sql语句。
【caselist】:caselist.txt文件,配置每次执行的case名称。
【config】:配置一些常量,例如数据库的相关信息,接口的相关信息等。
【readConfig】:用于读取config配置文件中的内容
【runAll】:用于执行case,生成测试报告。
上面是整个Python3接口测试框架的目录结构,下面对每一块进行介绍。
26.5 config.ini 和 readConfig.py
ini 文件其实就是所谓的初始化配置文件,一般的格式为:
[SECTION0]
key0 = value0
key1 = value1
config.ini 文件的内容:
[EMAIL]
mail_host = smtp.163.com
mail_user = abc@163.com
mail_pass = 123456
mail_port = 25
sender = abc@163.com
receiver = 123456@qq.com/1254367@qq.com
subject = Interface Test Report
content = "All interface test has been complited\nplease read the report file about the detile of result in the attachment."
testuser = Someone
on_off = off
[HTTP]
scheme = http
baseurl = www.baidu.com
port = 8080
timeout = 10.0
[HEADERS]
siteuid = all
clientid = 100
token_v = 213612368807_5811a363c85b19.22399915_75e4ee2761d60f2d7597eaec2579297f1cd7f6e3
token_u = 320012369021_586b1c65c3b3c1.51719831_e76832dc51f7ec8de0ba6ecdd69c8b7658dee93c
[DATABASE]
host = localhost
username = root
password = root
port = 3306
database = test
这个文件是整个Python3接口测试框架的配置文件,主要用来配置一些固定不变的参数。我们在测试用例中使用这些配置文件的内容,就需要使用readConfig.py去读取。
readConfig.py文件的代码。
import os
#导入codecs模块,使用codecs模块进行文件操作
import codecs
#导入configparser模块,python3 用ConfigParser包处理 ini文件
import configparser
#获取config.ini 文件路径
proDir = os.path.split(os.path.realpath(__file__))[0]
configPath = os.path.join(proDir, "config.ini")
class ReadConfig:
def __init__(self):
fd = open(configPath)
data = fd.read()
# remove BOM
if data[:3] == codecs.BOM_UTF8:
data = data[3:]
file = codecs.open(configPath, "w")
file.write(data)
file.close()
fd.close()
self.cf = configparser.ConfigParser()
self.cf.read(configPath)
def get_email(self, name):
value = self.cf.get("EMAIL", name)
return value
def get_http(self, name):
value = self.cf.get("HTTP", name)
return value
def get_headers(self, name):
value = self.cf.get("HEADERS", name)
return value
def set_headers(self, name, value):
self.cf.set("HEADERS", name, value)
with open(configPath, 'w+') as f:
self.cf.write(f)
def get_url(self, name):
value = self.cf.get("URL", name)
return value
def get_db(self, name):
value = self.cf.get("DATABASE", name)
return value
readConfig.py文件定义了相应的方法,根据名称取对应的值。
26.6 common 文件夹
配置文件和读取配置文件已经介绍,接下来就可以写common里的共通方法。
【Log.py】:Log 模块是一个独立的模块,对输出的日志的所有操作。
【configHttp.py】:configHttp 模块是接口配置文件。
【common】:common 模块 是数据驱动的配置文件。
【configDB 模块】:configDB 模块主要是封装了操作mysql数据的相关操作方法。
【configEmail 模块】:configEmail 模块主要是封装了发送邮件的相关方法。
【businessCommon 模块】:businessCommon 模块封装了登录和登录退出的方法。
26.6.1 Log.py 模块
Log.py 模块主要是对输出格式的规定,输出等级的定义以及其他一些输出的定义等。
import os,sys
import readConfig as readConfig
import logging
from datetime import datetime
import threading
localReadConfig = readConfig.ReadConfig()
class Log:
def __init__(self):
global logPath, resultPath, proDir
proDir = readConfig.proDir
resultPath = os.path.join(proDir, "result")
if not os.path.exists(resultPath):
os.mkdir(resultPath)
logPath = os.path.join(resultPath, str(datetime.now().strftime("%Y%m%d%H%M%S")))
if not os.path.exists(logPath):
os.mkdir(logPath)
self.logger = logging.getLogger()
self.logger.setLevel(logging.INFO)
# defined handler
handler = logging.FileHandler(os.path.join(logPath, "output.log"))
# defined formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
self.logger.addHandler(handler)
def get_logger(self):
"""
get logger
:return:
"""
return self.logger
def build_start_line(self, case_no):
"""
write start line
:return:
"""
self.logger.info("--------" + case_no + " START--------")
def build_end_line(self, case_no):
"""
write end line
:return:
"""
self.logger.info("--------" + case_no + " END--------")
def build_case_line(self, case_name, code, msg):
"""
write test case line
:param case_name:
:param code:
:param msg:
:return:
"""
self.logger.info(case_name+" - Code:"+code+" - msg:"+msg)
def get_report_path(self):
"""
get report file path
:return:
"""
report_path = os.path.join(logPath, "report.html")
return report_path
def get_result_path(self):
"""
get test result path
:return:
"""
return logPath
def write_result(self, result):
"""
:param result:
:return:
"""
result_path = os.path.join(logPath, "report.txt")
fb = open(result_path, "wb")
try:
fb.write(result)
except FileNotFoundError as ex:
logger.error(str(ex))
class MyLog:
log = None
mutex = threading.Lock()
def __init__(self):
pass
@staticmethod
def get_log():
if MyLog.log is None:
MyLog.mutex.acquire()
MyLog.log = Log()
MyLog.mutex.release()
return MyLog.log
if __name__ == "__main__":
log = MyLog.get_log()
logger = log.get_logger()
logger.debug("test debug")
logger.info("test info")
26.6.2 configHttp.py 模块
configHttp.py 模块代码:
import requests
import readConfig as readConfig
from common.Log import MyLog as Log
import json
localReadConfig = readConfig.ReadConfig()
class ConfigHttp:
def __init__(self):
global scheme, host, port, timeout
scheme = localReadConfig.get_http("scheme")
host = localReadConfig.get_http("baseurl")
port = localReadConfig.get_http("port")
timeout = localReadConfig.get_http("timeout")
self.log = Log.get_log()
self.logger = self.log.get_logger()
self.headers = {}
self.params = {}
self.data = {}
self.url = None
self.files = {}
self.state = 0
def set_url(self, url):
"""
set url
:param: interface url
:return:
"""
self.url = scheme+'://'+host+url
def set_headers(self, header):
"""
set headers
:param header:
:return:
"""
self.headers = header
def set_params(self, param):
"""
set params
:param param:
:return:
"""
self.params = param
def set_data(self, data):
"""
set data
:param data:
:return:
"""
self.data = data
def set_files(self, filename):
"""
set upload files
:param filename:
:return:
"""
if filename != '':
file_path = 'F:/AppTest/Test/interfaceTest/testFile/img/' + filename
self.files = {'file': open(file_path, 'rb')}
if filename == '' or filename is None:
self.state = 1
# defined http get method
def get(self):
"""
defined get method
:return:
"""
try:
response = requests.get(self.url, headers=self.headers, params=self.params, timeout=float(timeout))
# response.raise_for_status()
return response
except TimeoutError:
self.logger.error("Time out!")
return None
# defined http post method
# include get params and post data
# uninclude upload file
def post(self):
"""
defined post method
:return:
"""
try:
response = requests.post(self.url, headers=self.headers, params=self.params, data=self.data, timeout=float(timeout))
# response.raise_for_status()
return response
except TimeoutError:
self.logger.error("Time out!")
return None
# defined http post method
# include upload file
def postWithFile(self):
"""
defined post method
:return:
"""
try:
response = requests.post(self.url, headers=self.headers, data=self.data, files=self.files, timeout=float(timeout))
return response
except TimeoutError:
self.logger.error("Time out!")
return None
# defined http post method
# for json
def postWithJson(self):
"""
defined post method
:return:
"""
try:
response = requests.post(self.url, headers=self.headers, json=self.data, timeout=float(timeout))
return response
except TimeoutError:
self.logger.error("Time out!")
return None
if __name__ == "__main__":
print("ConfigHTTP")
configHttp.py 模块说明:
接口测试框架用python3自带的requests来进行接口请求测试。里面封装了get和post两个方法。
【get方法】
对于requests提供的get方法,有几个常用的参数:
url:接口的url地址。
headers:定制请求头(headers),例如:content-type = application/x-www-form-urlencoded。
params:用于传递测试接口所要用的参数,这里我们用python中的字典形式(key:value)进行参数的传递。
timeout:设置接口连接的最大时间(超过该时间会抛出超时错误)
例子:
url=‘http://api.shein.com/v2/member/logout’
header={‘content-type’: application/x-www-form-urlencoded}
param={‘user_id’: 123456,‘email’: 123456@163.com}
timeout=0.5
requests.get(url, headers=header, params=param, timeout=timeout)
【post方法】
与get方法类似,只要设置好对应的参数,就可以了
例子:
url=‘http://api.shein.com/v2/member/login’
header={‘content-type’: application/x-www-form-urlencoded}
data={‘email’: 123456@163.com,‘password’: 123456}
timeout=0.5
requests.post(url, headers=header, data=data, timeout=timeout)
备注:post 方法中的参数,我们不是使用 params 进行传递,而是改用 data 进行传递。
26.6.3 common.py 模块
common.py 模块代码:
import requests
import readConfig as readConfig
import os
from xlrd import open_workbook
from xml.etree import ElementTree as ElementTree
from common import configHttp as configHttp
from common.Log import MyLog as Log
import json
localReadConfig = readConfig.ReadConfig()
proDir = readConfig.proDir
localConfigHttp = configHttp.ConfigHttp()
log = Log.get_log()
logger = log.get_logger()
caseNo = 0
def get_visitor_token():
"""
create a token for visitor
:return:
"""
host = localReadConfig.get_http("BASEURL")
response = requests.get(host+"/v2/User/Token/generate")
info = response.json()
token = info.get("info")
logger.debug("Create token:%s" % (token))
return token
def set_visitor_token_to_config():
"""
set token that created for visitor to config
:return:
"""
token_v = get_visitor_token()
localReadConfig.set_headers("TOKEN_V", token_v)
def get_value_from_return_json(json, name1, name2):
"""
get value by key
:param json:
:param name1:
:param name2:
:return:
"""
info = json['info']
group = info[name1]
value = group[name2]
return value
def show_return_msg(response):
"""
show msg detail
:param response:
:return:
"""
url = response.url
msg = response.text
print("\n请求地址:"+url)
# 可以显示中文
print("\n请求返回值:"+'\n'+json.dumps(json.loads(msg), ensure_ascii=False, sort_keys=True, indent=4))
# ****************************** read testCase excel ********************************
def get_xls(xls_name, sheet_name):
"""
get interface data from xls file
:return:
"""
cls = []
# get xls file's path
xlsPath = os.path.join(proDir, "testFile", 'case', xls_name)
# open xls file
file = open_workbook(xlsPath)
# get sheet by name
sheet = file.sheet_by_name(sheet_name)
# get one sheet's rows
nrows = sheet.nrows
for i in range(nrows):
if sheet.row_values(i)[0] != u'case_name':
cls.append(sheet.row_values(i))
return cls
# ****************************** read SQL xml ********************************
database = {}
def set_xml():
"""
set sql xml
:return:
"""
if len(database) == 0:
sql_path = os.path.join(proDir, "testFile", "SQL.xml")
tree = ElementTree.parse(sql_path)
for db in tree.findall("database"):
db_name = db.get("name")
# print(db_name)
table = {}
for tb in db.getchildren():
table_name = tb.get("name")
# print(table_name)
sql = {}
for data in tb.getchildren():
sql_id = data.get("id")
# print(sql_id)
sql[sql_id] = data.text
table[table_name] = sql
database[db_name] = table
def get_xml_dict(database_name, table_name):
"""
get db dict by given name
:param database_name:
:param table_name:
:return:
"""
set_xml()
database_dict = database.get(database_name).get(table_name)
return database_dict
def get_sql(database_name, table_name, sql_id):
"""
get sql by given name and sql_id
:param database_name:
:param table_name:
:param sql_id:
:return:
"""
db = get_xml_dict(database_name, table_name)
sql = db.get(sql_id)
return sql
# ****************************** read interfaceURL xml ********************************
def get_url_from_xml(name):
"""
By name get url from interfaceURL.xml
:param name: interface's url name
:return: url
"""
url_list = []
url_path = os.path.join(proDir, 'testFile', 'interfaceURL.xml')
tree = ElementTree.parse(url_path)
for u in tree.findall('url'):
url_name = u.get('name')
if url_name == name:
for c in u.getchildren():
url_list.append(c.text)
url = '/v2/' + '/'.join(url_list)
return url
if __name__ == "__main__":
print(get_xls("login"))
set_visitor_token_to_config()
common.py 模块主要是封装了数据驱动的各种方法(读取xml文件、读取excel文件,读取mysql数据库数据),测试用例就是通过 excel 文件来管理测试用例的。
26.6.4 configDB 模块
configDB 模块代码:
import pymysql
import readConfig as readConfig
from common.Log import MyLog as Log
localReadConfig = readConfig.ReadConfig()
class MyDB:
global host, username, password, port, database, config
host = localReadConfig.get_db("host")
username = localReadConfig.get_db("username")
password = localReadConfig.get_db("password")
port = localReadConfig.get_db("port")
database = localReadConfig.get_db("database")
config = {
'host': str(host),
'user': username,
'passwd': password,
'port': int(port),
'db': database
}
def __init__(self):
self.log = Log.get_log()
self.logger = self.log.get_logger()
self.db = None
self.cursor = None
def connectDB(self):
"""
connect to database
:return:
"""
try:
# connect to DB
self.db = pymysql.connect(**config)
# create cursor
self.cursor = self.db.cursor()
print("Connect DB successfully!")
except ConnectionError as ex:
self.logger.error(str(ex))
def executeSQL(self, sql, params):
"""
execute sql
:param sql:
:return:
"""
self.connectDB()
# executing sql
self.cursor.execute(sql, params)
# executing by committing to DB
self.db.commit()
return self.cursor
def get_all(self, cursor):
"""
get all result after execute sql
:param cursor:
:return:
"""
value = cursor.fetchall()
return value
def get_one(self, cursor):
"""
get one result after execute sql
:param cursor:
:return:
"""
value = cursor.fetchone()
return value
def closeDB(self):
"""
close database
:return:
"""
self.db.close()
print("Database closed!")
configDB 模块主要是封装了操作mysql数据的相关操作方法(连接数据库,执行sql,获取结果,最后关闭数据库)。
26.6.5 configEmail 模块
configEmail 模块代码:
import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from datetime import datetime
import threading
import readConfig as readConfig
from common.Log import MyLog
import zipfile
import glob
localReadConfig = readConfig.ReadConfig()
class Email:
def __init__(self):
global host, user, password, port, sender, title
host = localReadConfig.get_email("mail_host")
user = localReadConfig.get_email("mail_user")
password = localReadConfig.get_email("mail_pass")
port = localReadConfig.get_email("mail_port")
sender = localReadConfig.get_email("sender")
title = localReadConfig.get_email("subject")
# content = localReadConfig.get_email("content")
# get receiver list
self.value = localReadConfig.get_email("receiver")
self.receiver = []
for n in str(self.value).split("/"):
self.receiver.append(n)
# defined email subject
date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.subject = "接口测试报告" + " " + date
self.log = MyLog.get_log()
self.logger = self.log.get_logger()
self.msg = MIMEMultipart('related')
def config_header(self):
"""
defined email header include subject, sender and receiver
:return:
"""
self.msg['subject'] = self.subject
self.msg['from'] = sender
self.msg['to'] = ";".join(self.receiver)
def config_content(self):
"""
write the content of email
:return:
"""
f = open(os.path.join(readConfig.proDir, 'testFile', 'emailStyle.txt'))
content = f.read()
f.close()
content_plain = MIMEText(content, 'html', 'UTF-8')
self.msg.attach(content_plain)
self.config_image()
def config_image(self):
"""
config image that be used by content
:return:
"""
# defined image path
image1_path = os.path.join(readConfig.proDir, 'testFile', 'img', '1.png')
fp1 = open(image1_path, 'rb')
msgImage1 = MIMEImage(fp1.read())
# self.msg.attach(msgImage1)
fp1.close()
# defined image id
msgImage1.add_header('Content-ID', '<image1>')
self.msg.attach(msgImage1)
image2_path = os.path.join(readConfig.proDir, 'testFile', 'img', 'logo.jpg')
fp2 = open(image2_path, 'rb')
msgImage2 = MIMEImage(fp2.read())
# self.msg.attach(msgImage2)
fp2.close()
# defined image id
msgImage2.add_header('Content-ID', '<image2>')
self.msg.attach(msgImage2)
def config_file(self):
"""
config email file
:return:
"""
# if the file content is not null, then config the email file
if self.check_file():
reportpath = self.log.get_result_path()
zippath = os.path.join(readConfig.proDir, "result", "test.zip")
# zip file
files = glob.glob(reportpath + '\*')
f = zipfile.ZipFile(zippath, 'w', zipfile.ZIP_DEFLATED)
for file in files:
# 修改压缩文件的目录结构
f.write(file, '/report/'+os.path.basename(file))
f.close()
reportfile = open(zippath, 'rb').read()
filehtml = MIMEText(reportfile, 'base64', 'utf-8')
filehtml['Content-Type'] = 'application/octet-stream'
filehtml['Content-Disposition'] = 'attachment; filename="test.zip"'
self.msg.attach(filehtml)
def check_file(self):
"""
check test report
:return:
"""
reportpath = self.log.get_report_path()
if os.path.isfile(reportpath) and not os.stat(reportpath) == 0:
return True
else:
return False
def send_email(self):
"""
send email
:return:
"""
self.config_header()
self.config_content()
self.config_file()
try:
smtp = smtplib.SMTP()
smtp.connect(host)
smtp.login(user, password)
smtp.sendmail(sender, self.receiver, self.msg.as_string())
smtp.quit()
self.logger.info("The test report has send to developer by email.")
except Exception as ex:
self.logger.error(str(ex))
class MyEmail:
email = None
mutex = threading.Lock()
def __init__(self):
pass
@staticmethod
def get_email():
if MyEmail.email is None:
MyEmail.mutex.acquire()
MyEmail.email = Email()
MyEmail.mutex.release()
return MyEmail.email
if __name__ == "__main__":
email = MyEmail.get_email()
configEmail 模块主要是封装了发送邮件的相关方法(每次测试完之后,都会生成一份测试报告,并且把测试报告以附件的形式,通过email发送给相关的邮箱)。
26.6.6 businessCommon 模块
businessCommon 模块代码:
from common import common
from common import configHttp
import readConfig as readConfig
localReadConfig = readConfig.ReadConfig()
localConfigHttp = configHttp.ConfigHttp()
localLogin_xls = common.get_xls("userCase.xlsx", "login")
localAddAddress_xls = common.get_xls("userCase.xlsx", "addAddress")
# login
def login():
"""
login
:return: token
"""
# set url
url = common.get_url_from_xml('login')
localConfigHttp.set_url(url)
# set header
token = localReadConfig.get_headers("token_v")
header = {"token": token}
localConfigHttp.set_headers(header)
# set param
data = {"email": localLogin_xls[0][3],
"password": localLogin_xls[0][4]}
localConfigHttp.set_data(data)
# login
response = localConfigHttp.post().json()
token = common.get_value_from_return_json(response, "member", "token")
return token
# logout
def logout(token):
"""
logout
:param token: login token
:return:
"""
# set url
url = common.get_url_from_xml('logout')
localConfigHttp.set_url(url)
# set header
header = {'token': token}
localConfigHttp.set_headers(header)
# logout
localConfigHttp.get()
businessCommon 模块封装了登录和登录退出的方法,因为每个测试用例都用到。
26.7 runAll 文件
runAll 文件代码:
import os
import unittest
import time as t
from common.Log import MyLog as Log
import readConfig as readConfig
import HTMLTestRunner
from common.configEmail import MyEmail
localReadConfig = readConfig.ReadConfig()
class AllTest:
def __init__(self):
global log, logger, resultPath, on_off
log = Log.get_log()
logger = log.get_logger()
resultPath = log.get_report_path()
on_off = localReadConfig.get_email("on_off")
self.caseListFile = os.path.join(readConfig.proDir, "caselist.txt")
self.caseFile = os.path.join(readConfig.proDir, "testCase")
# self.caseFile = None
self.caseList = []
self.email = MyEmail.get_email()
def set_case_list(self):
"""
set case list
:return:
"""
fb = open(self.caseListFile)
for value in fb.readlines():
data = str(value)
if data != '' and not data.startswith("#"):
self.caseList.append(data.replace("\n", ""))
fb.close()
def set_case_suite(self):
"""
set case suite
:return:
"""
self.set_case_list()
test_suite = unittest.TestSuite()
suite_module = []
for case in self.caseList:
case_name = case.split("/")[-1]
print(case_name+".py")
discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + '.py', top_level_dir=None)
suite_module.append(discover)
if len(suite_module) > 0:
for suite in suite_module:
for test_name in suite:
test_suite.addTest(test_name)
else:
return None
return test_suite
def run(self):
"""
run test
:return:
"""
try:
suit = self.set_case_suite()
if suit is not None:
logger.info("********TEST START********")
fp = open(resultPath, 'wb')
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='Test Report', description='Test Description')
runner.run(suit)
else:
logger.info("Have no case to test.")
except Exception as ex:
logger.error(str(ex))
finally:
logger.info("*********TEST END*********")
fp.close()
# send test report by email
if on_off == 'on':
self.email.send_email()
elif on_off == 'off':
logger.info("Doesn't send report email to developer.")
else:
logger.info("Unknow state.")
if __name__ == '__main__':
obj = AllTest()
obj.run()
runAll的执行原理,首先我们要从 caselist.txt 文件中读取需要执行的 case 名称,然后将他们添加到 python3 自带的 unittest 测试集的测试套件中,最后执行run()函数,执行测试集。
26.8 result 文件
result文件夹会在首次执行case时生成,并且以后的测试结果都会被保存在该文件夹下,同时每次测试的文件夹都是用系统时间命名,里面包含了两个文件,log文件和测试报告。
26.9 caselist.txt文件
caselist.txt文件存放了要执行的测试用例名称,凡是没有被注释掉的,都是要被执行的case名称。
26.10 testFile文件夹
testFile文件夹下,case文件夹下放置我们测试时用来管理测试用例, img下放用于数据库查询的sql语句的xml文件、邮件的样式等。
26.11 testCase文件夹
testCase文件夹下,存放我们写的具体的测试用例,测试用例目录可以根据模块来划分,也可以根据其他特定需求来划分。
注意:所有的 case 名称都要以 test 开头来命名,这是因为 Python3 unittest 在进行测试时会自动匹配 testCase 文件夹下面所有 test 开头的 .py 文件。
26.12 汇总
对 Python3 的接口测试框架简单介绍了一下,其实大家要学习的是封装的思想,如何把一些公共的模块抽象出来,达到数据与业务流程的分离,在上面的框架中,我把常用固定的参数放到 config.ini 文件中,测试用例的管理(excel文件),sql 语句的存放(xml文件),每个公司的项目不同,要求的不一样,自己可以根据自己项目的需求去封装自己的接口测试框架。
相关推荐
- JavaScript做个贪吃蛇小游戏(过关-加速),无需网络直接玩。
-
JavaScript做个贪吃蛇小游戏(过关-则加速)在浏览器打开文件,无需网络直接玩。<!DOCTYPEhtml><htmllang="en"><...
- 大模型部署加速方法简单总结(大模型 ai)
-
以下对大模型部署、压缩、加速的方法做一个简单总结,为后续需要备查。llama.cppGithub:https://github.com/ggerganov/llama.cppLLaMA.cpp项...
- 安徽医大第一医院应用VitaFlow Liberty(R)Flex为患者焕然一“心”
-
近日,在安徽医科大学第一附属医院心血管内科负责人暨北京安贞医院安徽医院业务副院长喻荣辉教授的鼎力支持和卓越带领下,凭借着先进的VitaFlowLiberty(R)Flex经导管主动脉瓣可回收可...
- 300 多行代码搞定微信 8.0 的「炸」「裂」特效!
-
微信8.0更新的一大特色就是支持动画表情,如果发送的消息只有一个内置的表情图标,这个表情会有一段简单的动画,一些特殊的表情还有全屏特效,例如烟花表情有全屏放烟花的特效,炸弹表情有爆炸动画并且消息和...
- 让div填充屏幕剩余高度的方法(div填充20px)
-
技术背景在前端开发中,经常会遇到需要让某个div元素填充屏幕剩余高度的需求,比如创建具有固定头部和底部,中间内容区域自适应填充剩余空间的布局。随着CSS技术的发展,有多种方法可以实现这一需求。实现步骤...
- css之div内容居中(css中div怎么居中)
-
div中的内容居中显示,包括水平和垂直2个方向。<html><head><styletype="text/css">...
- 使用uniapp开发小程序遇到的一些问题及解决方法
-
1、swiper组件自定义知识点swiper组件的指示点默认是圆圈,想要自己设置指示点,需要获得当前索引,然后赋给当前索引不同的样式,然后在做个动画就可以了。*关键点用change方法,然后通过e.d...
- 微信小程序主页面排版(怎样设置小程序的排版)
-
开发小程序的话首先要了解里面的每个文件的作用小程序没有DOM对象,一切基于组件化小程序的四个重要的文件*.js*.wxml--->view结构---->html*.wxss--...
- Vue动态组件的实践与原理探究(vue动态组件component原理)
-
我司有一个工作台搭建产品,允许通过拖拽小部件的方式来搭建一个工作台页面,平台内置了一些常用小部件,另外也允许自行开发小部件上传使用,本文会从实践的角度来介绍其实现原理。ps.本文项目使用VueCLI...
- 【HarmonyOS Next之旅】兼容JS的类Web开发(四) -> tabs
-
目录1->创建Tabs2->设置Tabs方向3->设置样式4->显示页签索引5->场景示例编辑1->创建Tabs在pages/index目录...
- CSS:前端必会的flex布局,我把布局代码全部展示出来了
-
进入我的主页,查看更多CSS的分享!首先呢,先去看文档,了解flex是什么,这里不做赘述。当然,可以看下面的代码示例,辅助你理解。一、row将子元素在水平方向进行布局:1.垂直方向靠顶部,水平方向靠...
- 【HarmonyOS Next之旅】兼容JS的类Web开发(四) -> swiper
-
目录1->创建Swiper组件2->添加属性3->设置样式4->绑定事件5->场景示例编辑1->创建Swiper组件在pages/index...
- CSS:Flex布局,网页排版神器!(css3 flex布局)
-
还在为网页排版抓狂?别担心,CSS的flex布局来了,让你轻松玩转各种页面布局,实现网页设计自由!什么是Flex布局?Flex布局,也称为弹性布局,是CSS中的一种强大布局方式,它能够让你...
- 移动WEB开发之flex布局,附携程网首页案例制作
-
一、flex布局体验传统布局兼容性好布局繁琐局限性,不能再移动端很好的布局1.1flex弹性布局:操作方便,布局极为简单,移动端应用很广泛PC端浏览器支持情况较差IE11或更低版本,不支持或仅部...
- 2024最新升级–前端内功修炼 5大主流布局系统进阶(mk分享)
-
2024最新升级–前端内功修炼5大主流布局系统进阶(mk分享)获课》789it.top/14658/前端布局是网页设计中至关重要的一环,它决定了网页的结构和元素的排列方式。随着前端技术的不断发展,现...
- 一周热门
- 最近发表
-
- JavaScript做个贪吃蛇小游戏(过关-加速),无需网络直接玩。
- 大模型部署加速方法简单总结(大模型 ai)
- 安徽医大第一医院应用VitaFlow Liberty(R)Flex为患者焕然一“心”
- 300 多行代码搞定微信 8.0 的「炸」「裂」特效!
- 让div填充屏幕剩余高度的方法(div填充20px)
- css之div内容居中(css中div怎么居中)
- 使用uniapp开发小程序遇到的一些问题及解决方法
- 微信小程序主页面排版(怎样设置小程序的排版)
- Vue动态组件的实践与原理探究(vue动态组件component原理)
- 【HarmonyOS Next之旅】兼容JS的类Web开发(四) -> tabs
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)
- CSS 水平对齐 (Horizontal Align) (30)