项目中python使用的背景:
在实际项目开发中由于排期和开发进度等和不具备测试环境等原因需要单独进行模拟测试,甚至对一些特定的需求需要编写测试脚本给测试组做测试参考;此时python撰写脚本进行测试环境模拟是一种比较方便的方式。以下是一些测试中常用的脚本总结。
一、http对接测试中请求和服务模拟
Python 标准库中的urllib模块,主要用于处理 URL相关的操作,如解析URL、打开URL和发送数据等等。以下是测试中常用的get和post请求,相对于postman的方便,python脚本可以做更复杂的操作,如压力测试和接口的联动等等。
1.1 客户端post和get测试脚本
import os
import urllib.request
import json
import time
class Client_Agent:
@staticmethod # 静态方法,不需要实例化类就可以调用
def urllib_get(url):
"""测试标准库中urllib"""
response = urllib.request.urlopen(url)
html = response.read()
print(html)
return html
@staticmethod
def urllib_post(url,data):
# 将数据转换为JSON格式 dumps将 Python 对象(如字典、列表等)序列化为 JSON 格式的字符串
json_data = json.dumps(data).encode('utf-8')
print(json_data)
headers = {
'Content-Type': 'application/json'
}
# 发送POST请求
#with 语句用于简化资源管理,如文件操作、网络连接等。
# 它确保了即使在发生异常的情况下,资源也能被正确地释放或清理
# with 语句依赖于上下文管理器(context manager),上下文管理器是实现了特殊方法 __enter__ 和 __exit__ 的对象。
# 当进入 with 块时,会调用 __enter__ 方法;当退出 with 块时,无论是否发生异常,都会调用 __exit__ 方法。
try:
#创建requse对象 指定url、数据和headers 关键字参数,指定对应参数(默认是位置参数)
req = urllib.request.Request(url, data=json_data, headers=headers, method='POST')
with urllib.request.urlopen(req,timeout = 10) as response:
response_data = response.read()
print(response_data.decode('utf-8'))
return response_data
except urllib.error.URLError as e:
print(f'Error occurred: {e.reason}')
def test_post():
methodArry = ["getCharge","autoPay","uploadPassedVeh","uploadParklots"]
data = [
{
'method': methodArry[0],
'data': {
'plateNo':'浙A10086',
'inTime':'2023-08-01 11:10:13',
'outTime':'2023-08-02 01:09:30'
}
},
{
'method': methodArry[1],
'data': {
'plateNo':'浙A10086',
}
},
{
'method': methodArry[2],
'data': {
'plateNo':'浙A10086',
'inTime':'2023-08-01 11:10:13',
'outTime':'2023-08-02 01:09:30'
}
},
{
'method': methodArry[3],
'data': {
'parkLots':3,
}
},
]
url = "http://10.10.101.36:8888/SmartCouldPlatform"
for it in data:
Client_Agent.urllib_post(url,it)
time.sleep(1) #调用time模块中sleep函数
def test_get():
url = "http://www.baidu.com"
Client_Agent.urllib_get(url)
def main():
print(f"模块的 __name__ 值: {__name__}")
test_post()
test_get()
if __name__ == "__main__":
main()
1.2 http服务端脚本
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import datetime
#字符串 (str): 字符串是由Unicode字符组成的序列,用于表示文本数据。
#字节串 (bytes): 字节串是由字节(8位)组成的序列,通常用于处理二进制数据或非文本数据(网络,文件I/0等)。
# 转换:'hello'.encode('utf-8') 会返回字节串 b'hello'。 b'hello'.decode('utf-8') 会返回字符串 'hello'
#继承BaseHTTPRequestHandler 处理http请求基础类
#可重写do_GET do_POST do_HEAD do_PUT do_DELET等方法极为方便
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self): #重写do_GET方法 self 是一个约定俗成的命名,用于表示类的实例。
# 设置响应状态码
self.send_response(200)
# 设置响应头
self.send_header('Content-type', 'text/html')
self.end_headers()
# 写入响应内容
self.wfile.write(b'Hello, world!') #b表示后面字符串是字节类型而非默认Unicode字符串
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
try:
#json.load 将json字符串加载为python对象
data = json.loads(post_data)
now = datetime.datetime.now()
print(f"{now:%Y-%m-%d %H:%M:%S}:收到数据:{data}")
self.send_response(200)
self.send_header('Content-type','application/json')
self.end_headers()#表示响应头已发完接下来可以发响应体
responseDict = self.do_processRequestContent(data)
#jumps序列化到字符串 jump序列化写到文件
responseStr = json.dumps(responseDict, indent = 4).encode('utf-8')
# 发送响应体
self.wfile.write(responseStr)
except json.JSONDecodeError as e:
print("JSON Decode Error:",e)
self.send_response(400)
self.send_header('Content-type', 'application/json')
self.end_headers()
response = {'status': 'error', 'message': 'Invalid JSON'}
self.wfile.write(json.dumps(response).encode('utf-8'))
#处理请求的json内容
def do_processRequestContent(self,dataDict):
retDict = {}
while(1):
ret = -1
errMsg = "UnKnown"
method = dataDict.get("method","UnKnown")
if method == "UnKnown":
errMsg = "检查method字段"
break
bodyDict = dataDict.get("data",{})
if bodyDict == {}:
errMsg = "检查data字段"
break
if method == "getCharge":
plateNo = bodyDict.get('plateNo')
inTime = bodyDict.get('inTime')
outTime = bodyDict.get('outTime')
ret = 0
errMsg = "处理成功"
retDict = { "plateNo":plateNo,"Amount":1000}
break
if method == "autoPay":
plateNo = bodyDict.get('plateNo')
ret = 0
errMsg = "处理成功"
retDict = { "plateNo":plateNo}
break
if method == "uploadPassedVeh":
plateNo = bodyDict.get('plateNo')
inTime = bodyDict.get('inTime')
outTime = bodyDict.get('outTime')
ret = 0
errMsg = "处理成功"
break
if method == "uploadParklots":
parkLots = bodyDict.get('parkLots')
ret = 0
errMsg = "处理成功"
break
else:
errMsg = "检查method字段"
break
retDict = {"code":ret,"msg":errMsg,"body":retDict}
return retDict
#启动服务器函数
def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8888):
server_address = ('', port) #使用HTTPServer 设置服务器地址和监听8888端口
httpd = server_class(server_address, handler_class)
print(f'Starting http server on port {port}...')
httpd.serve_forever()#启动服务器,开始监听请求
if __name__ == '__main__':
run()
二、Ftp对接测试中的请求和服务
在实际项目对接中会有一些文件上传的接口(如图片数据等上传)采用ftp的方式,此时在开发前测试平台服务器很有必要,ftplib库是一个很方便的ftp库,以下是ftp测试的脚本常用脚本。
2.1 Ftp 客户端脚本
from ftplib import FTP
import os
class Ftp_Client:
def __init__(self,host,port,username,password):
self.host = host
self.port = port
self.username = username
self.password = password
self.ftp = FTP()
#登录连接ftp服务器
def connect_ftp(self):
ftp = FTP()
ftp.connect(self.host,self.port)
ftp.login(self.username, self.password)
self.ftp = ftp
#列举目录
def list_ftp_directory(self):
self.ftp.retrlines('LIST')
print(60*'-')
#ftp.retrlines(command) 用于执行FTP命令,并处理服务器响应的每一行
#LIST 表示列出当前目录文件和子目录
def pwd(self):
return self.ftp.pwd()
#上传文件
def upload_file(self,local_file,remote_file):
with open(local_file, 'rb') as file:
self.ftp.storbinary(f'STOR {remote_file}', file)
#storbinary 以二进制模式上传文件(STOR 加上远程文件名,上传文件对象)
#下载文件
def download_file(self,remote_file, local_file):
with open(local_file, 'wb') as file:
self.ftp.retrbinary(f'RETR {remote_file}', file.write)
# retrbinary 方法下载文件(RETR 加上远程文件名,下载文件对象)
#删除文件
def delete_file(self, remote_file):
self.ftp.delete(remote_file)
#创建目录
def create_directory(self, directory_name):
self.ftp.mkd(directory_name)
#删除目录
def remove_directory(self, directory_name):
self.ftp.rmd(directory_name)
#断开连接
def disconnect_ftp(self):
self.ftp.quit()
#启动服务器函数
def test_ftpClient():
host = '10.11.117.20'
port = 21
username = 'admin'
password = '12345'
ftpClient = Ftp_Client(host,port,username,password)
ftpClient.connect_ftp()
ftpClient.list_ftp_directory()
#创建图片目录
ftpClient.create_directory('./Pic')
#上传图片
#r 可以避免转义字符 类似于C++中R"(...)"
localFilePath = r"G:\ProjectCode\2022\PythonProject\Test_1\Plate.jpg"
remoteFilePath = r"Pic\vehPic.jpg"
ftpClient.upload_file(localFilePath,remoteFilePath)
ftpClient.list_ftp_directory()
#下载图片文件
ftpClient.download_file(remoteFilePath, r"G:\ProjectCode\2022\PythonProject\Test_1\Plate_down.jpg")
#删除文件
ftpClient.delete_file(remoteFilePath)
ftpClient.list_ftp_directory()
#删除目录
ftpClient.remove_directory(r"Pic")
ftpClient.list_ftp_directory()
ftpClient.disconnect_ftp()
if __name__ == '__main__':
test_ftpClient()
测试结果:
06-18-2022 02:58PM <DIR> FtpConfigPath------
06-18-2022 02:58PM 0 ReadMe.txt
------------------------------------------------------------
06-18-2022 02:58PM <DIR> FtpConfigPath
06-18-2022 03:10PM <DIR> Pic
06-18-2022 02:58PM 0 ReadMe.txt
------------------------------------------------------------
06-18-2022 02:58PM <DIR> FtpConfigPath
06-18-2022 03:10PM <DIR> Pic
06-18-2022 02:58PM 0 ReadMe.txt
------------------------------------------------------------
06-18-2022 02:58PM <DIR> FtpConfigPath
06-18-2022 02:58PM 0 ReadMe.txt
------------------------------------------------------------
三、数据库SqlLite相关操作
SqlLite轻量级支持事务的关系型开源数据库,所有数据存储在单一文件中无需配置,管理方便且跨平台,性能良好易于集成到项目中。
以下是以存储过车数据为例的SqlLite的增删查改的python脚本,以sqlLite数据库和csv文件分别进行存储和读取。
3.1 sqlLite封装类
import sqlite3
import string
import random
import datetime
from datetime import datetime, timedelta
import os
import csv
#sqllite3 解释器自带 无需下载 文件存存储 支持sql操作
#SqlLite 封装创增删改查过车数据等操作
class SqlLiteDb:
# 初始化方法(构造函数)
def __init__(self,DbName):
self.DbName = DbName # 实例变量
self.conn = self.Db_Init() # 实例变量
def Db_Init(self):
# 连接到SQLite数据库
# 如果文件不存在,会自动在当前目录创建一个数据库文件
try:
conn = sqlite3.connect(self.DbName)
print("连接SqlLite3数据库建立")
except sqlite3.Error as e:
print(e)
return conn
def Db_CreatePassVehicleInfoTable(self):
try:
sql_create_table = '''
CREATE TABLE IF NOT EXISTS passvehicleinfo (
id INTEGER PRIMARY KEY,
plateNo CHAR(32) NOT NULL,
passTime CHAR(32) NOT NULL,
direction INT NOT NULL
)
'''
cursor = self.conn.cursor()
cursor.execute(sql_create_table)
print("passvehicleinfo Table created.")
except sqlite3.Error as e:
print(e)
def AddPassVehicleInfo(self,plateNo, passTime, direction):
try:
sql_addPassVehicleInfo = f"INSERT INTO passvehicleinfo (plateNo, passTime,direction) VALUES ('{plateNo}', '{passTime}',{direction})"
cursor = self.conn.cursor()
cursor.execute(sql_addPassVehicleInfo)
self.conn.commit()
print("add passvehicleinfo success.")
except sqlite3.Error as e:
print(e)
def DeletePassVehicleInfo(self,plateNo):
try:
sql_deletePassVehicleInfo = f"DELETE FROM passvehicleinfo WHERE PlateNo = '{plateNo}'"
cursor = self.conn.cursor()
cursor.execute(sql_deletePassVehicleInfo)
self.conn.commit()
print("delete passvehicleinfo success.")
except sqlite3.Error as e:
print(e)
def UpdatePassVehicleInfo(self,plateNo,passTime,direction):
try:
sqlupdatePassVehicleInfo = f"update passvehicleinfo set passTime = '{passTime}',direction={direction} WHERE PlateNo = '{plateNo}'"
cursor = self.conn.cursor()
cursor.execute(sqlupdatePassVehicleInfo)
self.conn.commit()
print("update passvehicleinfo success.")
except sqlite3.Error as e:
print(e)
def SelectPassVehicleInfo(self):
try:
sqlSelectPassVehicleInfo = f"select * from passvehicleinfo"
cursor = self.conn.cursor()
cursor.execute(sqlSelectPassVehicleInfo)
rows = cursor.fetchall()
dictdataList = []
for row in rows:
dictdataList.append(
{
"plateNo":row[1],
"passTime":row[2],
"direction":row[3]
})
print("update passvehicleinfo success.")
return dictdataList
except sqlite3.Error as e:
print(e)
3.2 csv等文件读写和数据差生工具类
#数据制造
class CommonTool:
def __init__(self):
pass #空操作,占符作用
@staticmethod # 静态方法,不需要实例化类就可以调用
def generate_random_plate():
# 定义车牌的组成部分
letters = string.ascii_uppercase # 大写字母
digits = string.digits # 数字
provice = '浙苏沪陕晋鲁京黑鄂津宁冀粤辽云北京琼皖川新豫贵湘闽蒙藏'
provice_letter = ''.join(random.choices(provice))
# 随机选择两个字母
letter_part = ''.join(random.choices(letters, k=2))
# 随机选择三个数字
digit_part = ''.join(random.choices(digits, k=3))
# 拼接成车牌
plate = f"{provice_letter}{letter_part}{digit_part}"
return plate
@staticmethod
def generate_random_datetime():
# 定义时间范围
start_date = datetime(2020, 1, 1)
end_date = datetime(2025, 12, 31)
# 计算时间差
delta = end_date - start_date
# 生成随机的时间差
random_days = random.randint(0, delta.days)
random_seconds = random.randint(0, 86400) # 一天有86400秒
# 生成随机日期时间
random_datetime = start_date + timedelta(days=random_days, seconds=random_seconds)
# 格式化为指定字符串格式
random_datetime_str = random_datetime.strftime('%Y-%m-%d %H:%M:%S')
return random_datetime_str
@staticmethod
def printDictList(dictList):
speatorLine = '*' * 20
print(speatorLine)
for row in dictList:
print(row)
print(speatorLine)
#文件操作类csv 文件读写
class FileOperator:
def __init__(self):
self.file_path = ""
self.dictDataList = []
def read_file_txt(self):
with open(self.file_path, 'r') as file:
lineStr = file.readline()
dictList = []
while lineStr:
lineLetterList =lineStr.split(",")
lineStr = file.readline()
dictData = {
"plateNo":lineLetterList[0],
"passTime":lineLetterList[1],
"direction":int(lineLetterList[2].strip())
}
dictList.append(dictData)
return dictList
def save_file_txt(self):
with open(self.file_path, 'w') as file:
for row in self.dictDataList:
lineStr = row['plateNo'] + "," + row['passTime'] + "," +str(row['direction']) +"\n" #str(int)<->int(str)
file.write(lineStr)
def save_file_csv(self):
with open(self.file_path, 'w', newline='') as file: # newline=''确保csv模块不会自动换行
writer = csv.writer(file)
for row in self.dictDataList:
lineList = [row['plateNo'],row['passTime'], +row['direction']] #str(int)<->int(str)
writer.writerow(lineList)
def read_file_csv(self):
with open(self.file_path, 'r', newline='') as file:
reader = csv.reader(file)
dictList = []
for row in reader:
#squares = [x**2 for x in range(10)] 列表推导式,一行代码实现平方计算
if any(field.strip() for field in row):
#any()对象至少存在一个元素为真strip() 方法会移除字符串开头和结尾的所有空白字符(包括空格、制表符等)
#如果一个字段在去除空白字符后仍然有内容,则说明该字段不是空的。
dictData = {
"plateNo":row[0],
"passTime":row[1],
"direction":row[2]
}
dictList.append(dictData)
return dictList
3.3 测试脚本代码部分
def test_sqllite3():
#数据库增删改查和csv等文件的读写
sqlLiteObj = SqlLiteDb('MySqlLite3dB.db')
sqlLiteObj.Db_CreatePassVehicleInfoTable()
for i in range(10):
sqlLiteObj.AddPassVehicleInfo(CommonTool.generate_random_plate(),CommonTool.generate_random_datetime(),random.choice([0,1]))
dictdataList = sqlLiteObj.SelectPassVehicleInfo()
CommonTool.printDictList(dictdataList)
plateNo = dictdataList[0]['plateNo']
sqlLiteObj.UpdatePassVehicleInfo(dictdataList[0]['plateNo'],'2022-05-18 09:34:56',0)
sqlLiteObj.DeletePassVehicleInfo(dictdataList[1]['plateNo'])
dictdataList = sqlLiteObj.SelectPassVehicleInfo()
#file txt读写
current_directory = os.getcwd()#当前目录
print("当前工作目录:", current_directory)
# 在当前目录下创建一个新的文件
file_path_txt = os.path.join(current_directory, "passvehicleinfo.txt")
# 打开文件(如果文件不存在则创建)
fileOperatorObj = FileOperator()
setattr(fileOperatorObj,'file_path',file_path_txt)
setattr(fileOperatorObj,'dictDataList',dictdataList)
CommonTool.printDictList(dictdataList)
fileOperatorObj.save_file_txt()
print(f"保存到文件{getattr(fileOperatorObj,'file_path')}")
dictdataList_txt = fileOperatorObj.read_file_txt()
CommonTool.printDictList(dictdataList_txt)
#file csv保存读取 每行,隔开作为一条记录
file_path_csv = os.path.join(current_directory, "passvehicleinfo.csv")
setattr(fileOperatorObj,'file_path',file_path_csv)
setattr(fileOperatorObj,'dictDataList',dictdataList_txt)
fileOperatorObj.save_file_csv()
print(f"保存到文件{getattr(fileOperatorObj,'file_path')}")
dictdataList_csv = fileOperatorObj.read_file_csv()
CommonTool.printDictList(dictdataList_csv)
def main():
test_sqllite3()
if __name__ == "__main__":
main()
四、编码摘要认证等
在平台对接过程中为了数据安全常常需要一些摘要认证,对于图片数据等传输也常需要进行base64编解码,以下是测试验证这些的一些相关脚本。
import hashlib
import base64
def test_hashAndBase64():
#hashlib 生成哈希值
passPlate = "浙A12345"
#MD5
hashObj = hashlib.md5()
hashObj.update(passPlate.encode('utf-8'))#更新hash对象中的数据
print(f"{passPlate}的MD5值为:",hashObj.hexdigest())
#SHA256
sha256 = hashlib.sha256()
sha256.update(passPlate.encode('utf-8'))
print(f"{passPlate}的SHA256值为:",sha256.hexdigest())
#SHA1
sha1 = hashlib.sha1()
sha1.update(passPlate.encode('utf-8'))
print(f"{passPlate}的SHA1值为:",sha1.hexdigest())
#Base64编解码
#转换为字节对象
byte_passPlate = passPlate.encode('utf-8')
base64_plate = base64.b64encode(byte_passPlate).decode('utf-8')
print(f'{passPlate}的base64编码后:{base64_plate}')
#base64解码
base64_decodePlate = base64.b64decode(base64_plate.encode('utf-8'))
print(f'{base64_plate}的base64解码后:{base64_decodePlate.decode("utf-8")}')
if __name__ == '__main__':
test_hashAndBase64()
浙A12345的MD5值为: df4432c97d9d20e7ed52e1006d47f430
浙A12345的SHA256值为: 4eff50520986266f3bb69019b67e4065d23ea6112ec57c611c4e826975bc0860
浙A12345的SHA1值为: 20d30ac155e3ed796fcdc32d2db744fbba7e898b
浙A12345的base64编码后:5rWZQTEyMzQ1
5rWZQTEyMzQ1的base64解码后:浙A12345

被折叠的 条评论
为什么被折叠?



