python3+requests+unittest接口自動(dòng)化測(cè)試框架

關(guān)鍵詞: 接口自動(dòng)化測(cè)試

接口測(cè)試流程

確定測(cè)試接口的工具 —> 配置需要的接口參數(shù) —> 進(jìn)行測(cè)試 —> 檢查測(cè)試結(jié)果(有的需要數(shù)據(jù)庫(kù)輔助) —> 生成測(cè)試報(bào)告(html報(bào)告)

搭建框架的目的:做到業(yè)務(wù)和數(shù)據(jù)的分離,使框架更靈活。

結(jié)構(gòu)的劃分如下:

common:存放一些共通的方法

result:執(zhí)行過程中生成的文件夾,里面存放每次測(cè)試的結(jié)果

testCase:用于存放具體的測(cè)試case

testFile:存放測(cè)試過程中用到的文件,包括上傳的文件,測(cè)試用例以及? ? 數(shù)據(jù)庫(kù)的sql語句

caselist:txt文件,配置每次執(zhí)行的case名稱

config:配置一些常量,例如數(shù)據(jù)庫(kù)的相關(guān)信息,接口的相關(guān)信息等

readConfig: 用于讀取config配置文件中的內(nèi)容

runAll:用于執(zhí)行case

首先,分析config.inireadConfig.py兩個(gè)文件。

config.ini

[DATABASE]

host =50.23.190.57

username = xxxxxx

password = ******

port =3306

database = databasename

[HTTP]

# 接口的url

scheme = http

baseurl = http://xx.xxxx.xx

port =8080

timeout =10.0

[EMAIL]

mail_host = smtp.163.com

mail_user = xxx@163.com

mail_pass = *********

mail_port =25

sender = xxx@163.com

receiver = xxxx@qq.com/xxxx@qq.com

subject = python

content ="All interface test has been complited\nplease read the report file about the detile of result in the attachment."

testuser = Someone

on_off =1

這個(gè)配置文件保存所有一成不變的東西。

那么,當(dāng)我們需要使用config.ini時(shí),就需要readConfig.py文件了。

readConfig.py

import os

import codecs

import configparser

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_db(self, name):

value = self.cf.get("DATABASE", name)

return value

我們定義的方法,根據(jù)名稱取對(duì)應(yīng)的值,是不是so easy?!當(dāng)然了,這里我們只用到了get方法,還有其他的,例如set方法,關(guān)于讀取配置文件的博文

接下來,分析common到底有哪些東西?

common里是共通方法,首先看Log.py,對(duì)于這個(gè)log文件,給它單獨(dú)啟用了一個(gè)線程,這樣在整個(gè)運(yùn)行過程中,我們?cè)趯憀og的時(shí)候也會(huì)比較方便,它定義了對(duì)輸出的日志的所有操作,包括對(duì)輸出格式的規(guī)定,輸出等級(jí)的定義以及其他一些輸出的定義等等。總之,你想對(duì)log做的任何事情,都可以放到這里來。

Log.py

import logging from datetime

import datetime

import threading

class Log:

def__init__(self):

global logPath, resultPath, proDir? ? ?

proDir = readConfig.proDir? ? ?

resultPath = os.path.join(proDir,"result")

# create result file if it doesn't exist

if not os.path.exists(resultPath):? ? ? ? ?

os.mkdir(resultPath)

# defined test result file name by localtime

logPath = os.path.join(resultPath, str(datetime.now().strftime("%Y%m%d%H%M%S")))

# create test result file if it doesn't exist

if not os.path.exists(logPath):? ? ? ? ?

os.mkdir(logPath)

# defined logger

self.logger = logging.getLogger()

# defined log level

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')

# defined formatter

handler.setFormatter(formatter)

# add handler

self.logger.addHandler(handler)

我們創(chuàng)建了上面的Log類,在__init__初始化方法中,我們進(jìn)行了log的相關(guān)初始化操作。接下來,把它放進(jìn)一個(gè)線程內(nèi):

class MyLog:

log =Nonemutex = 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

關(guān)于python中線程的學(xué)習(xí)。

接下來是configHttp.py。沒錯(cuò),我們開始配置接口文件啦!

import requests

import readConfig as readConfig

from common.Log? import? MyLogasLog

localReadConfig = readConfig.ReadConfig()

class ConfigHttp:

def __init__(self):

globalhost, port, timeout? ? ?

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 =Noneself.files = {}

def set_url(self, url):

self.url = host + url

def set_headers(self, header):

self.headers = header

def set_params(self, param):

self.params = param

def set_data(self, data):

self.data = data

def set_files(self, file):

self.files = file

# defined http get method

def get(self):

try:? ? ? ? ?

response = requests.get(self.url, params=self.params, headers=self.headers, timeout=float(timeout))

# response.raise_for_status()

return response

except TimeoutError:? ? ? ? ?

self.logger.error("Time out!")

return None

# defined http post method

def post(self):

try:? ? ? ? ?

response = requests.post(self.url, headers=self.headers, data=self.data, files=self.files, timeout=float(timeout))

# response.raise_for_status()

return response

exceptTimeoutError:? ? ? ? ?

self.logger.error("Time out!")

return None

用python自帶的requests來進(jìn)行接口測(cè)試,python+requests這個(gè)模式是很好用的,它封裝好了測(cè)試接口的方法,用起來很方便。我們主要使用get和post兩個(gè)方法。(其他方法,大家可以仿照著自行擴(kuò)展)

get方法

接口測(cè)試中最多的就是get方法和post方法,其中,get方法用于獲取接口的測(cè)試,即使用get方法的接口,不會(huì)對(duì)后臺(tái)數(shù)據(jù)進(jìn)行更改,而且get方法在傳遞參數(shù)后,url的格式是這樣的:http://接口地址?key1=value1&&key2=value2。那我們要怎么使用它呢,請(qǐng)繼續(xù)往下看。

對(duì)于requests提供的get方法,有幾個(gè)常用的參數(shù):

url:顯而易見,就是接口的地址url啦

headers:定制請(qǐng)求頭(headers),例如:content-type = application/x-www-form-urlencoded

params:用于傳遞測(cè)試接口所要用的參數(shù),這里我們用python中的字典形式(key:value)進(jìn)行參數(shù)的傳遞。

timeout:設(shè)置接口連接的最大時(shí)間(超過該時(shí)間會(huì)拋出超時(shí)錯(cuò)誤)

舉個(gè)栗子:

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方法類似,只要設(shè)置好對(duì)應(yīng)的參數(shù),就可以了。下面就直接舉個(gè)栗子,直接上代碼:

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方法中的參數(shù),我們不在使用params進(jìn)行傳遞,而是改用data進(jìn)行參數(shù)傳遞。

post請(qǐng)求時(shí),參數(shù)包括以下幾種情況:1、常規(guī)參數(shù)(data)2、上傳文件(files)3、參數(shù)為json格式? 4、含有params參數(shù)(可拼接到url后,類比get參數(shù))同時(shí)含有body體參數(shù)(post常規(guī)參數(shù),url不顯示參數(shù)信息)。

下面我們來探(了)討(解)下接口的返回值:

text:獲取接口返回值的文本格式

json():獲取接口返回值的json()格式

status_code:返回狀態(tài)碼(成功為:200)

headers:返回完整的請(qǐng)求頭信息(headers['name']:返回指定的headers內(nèi)容)

encoding:返回字符編碼格式

url:返回接口的完整url地址

以上這些,就是常用的方法啦。

關(guān)于失敗請(qǐng)求拋出異常,我們可以使用“raise_for_status()”來完成,那么,當(dāng)我們的請(qǐng)求發(fā)生錯(cuò)誤時(shí),就會(huì)拋出異常。在這里提醒下各位朋友,如果你的接口,在地址不正確的時(shí)候,會(huì)有相應(yīng)的錯(cuò)誤提示(有時(shí)也需要進(jìn)行測(cè)試),這時(shí),千萬不能使用這個(gè)方法來拋出錯(cuò)誤,因?yàn)閜ython自己在鏈接接口時(shí)就已經(jīng)把錯(cuò)誤拋出,那么,后面你將無法測(cè)試期望的內(nèi)容。而且程序會(huì)直接在這里當(dāng)?shù)?,以錯(cuò)誤來計(jì)。

好了。那么恭喜大家,下面還有很長(zhǎng)的路要走~哈哈哈,就是這么任性。

來,學(xué)(看)習(xí)(看)common.py

common.py

import os

from xlrd import open_workbook

from xml.etree import ElementTreeasElementTree

from common.Log import MyLog as Log

localConfigHttp = configHttp.ConfigHttp()

log = Log.get_log()

logger = log.get_logger()

# 從excel文件中讀取測(cè)試用例

def get_xls(xls_name, sheet_name):

cls = []

# get xls file's path

xlsPath = os.path.join(proDir,"testFile", xls_name)

# open xls

filefile = 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))returncls

# 從xml文件中讀取sql語句

database = {}

def set_xml():

if len(database) ==0:? ? ?

sql_path = os.path.join(proDir,"testFile","SQL.xml")? ?

tree = ElementTree.parse(sql_path)fordbintree.findall("database"):? ? ? ? ?

db_name = db.get("name")

# print(db_name)

table = {}fortbindb.getchildren():? ? ? ? ? ? ?

table_name = tb.get("name")

# print(table_name)

sql = {}fordataintb.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):set_xml()?

database_dict = database.get(database_name).get(table_name)

return database_dictdefget_sql(database_name, table_name, sql_id):db = get_xml_dict(database_name, table_name)?

sql = db.get(sql_id)

return sql

上面就是我們common的兩大主要內(nèi)容了:

(1)利用xml.etree.Element來對(duì)xml文件進(jìn)行操作,然后通過我們自定義的方法,根據(jù)傳遞不同的參數(shù)取得不(想)同(要)的值。

(2)利用xlrd來操作excel文件,注意啦,我們是用excel文件來管理測(cè)試用例的。

備注:針對(duì)公司加密文件,可以使用python自動(dòng)生成Excel用例表格;或者外網(wǎng)傳入。切記不能打開,打開后就會(huì)解密的。

excel文件:

xml文件:

深入介紹的鏈接excel? 和? xml

接下來,看看數(shù)據(jù)庫(kù)發(fā)送郵件

小編這次使用的是MySQL數(shù)據(jù)庫(kù):

configDB.py

import pymysql

import readConfigasreadConfig from common.Log

import MyLog as Log

localReadConfig = readConfig.ReadConfig()

class MyDB:

globalhost, 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 =Noneself.cursor =None

def connectDB(self):

try:

# connect to DB

self.db = pymysql.connect(**config)

# create cursor

self.cursor = self.db.cursor()? ? ? ? ?

print("Connect DB successfully!")

except ConnectionErrorasex:? ? ? ? ?

self.logger.error(str(ex))

def executeSQL(self, sql, params):

self.connectDB()

# executing sqlself.cursor.execute(sql, params)

# executing by committing to DB

self.db.commit()

return self.cursor

def get_all(self, cursor):

value = cursor.fetchall()

return value

def get_one(self, cursor):

value = cursor.fetchone()

return value

def closeDB(self):

self.db.close()? ? ?

print("Database closed!")

這就是完整的數(shù)據(jù)庫(kù)的文件啦。因?yàn)樾【幍男枨髮?duì)數(shù)據(jù)庫(kù)的操作不是很復(fù)雜,所以這些已基本滿足要求啦。注意下啦,在此之前,請(qǐng)朋友們先把pymysql裝起來!pymysql裝起來!pymysql裝起來!(重要的事情說三遍),安裝的方法執(zhí)行以下命令即可:

pip install pymysql

小伙伴們發(fā)現(xiàn)沒,在整個(gè)文件中,我們并沒有出現(xiàn)具體的變量值哦,為什么呢?沒錯(cuò),因?yàn)榍懊嫖覀儗懥薱onfig.ini文件,所有的數(shù)據(jù)庫(kù)配置信息都在這個(gè)文件內(nèi)哦,是不是感覺很方便呢,以后就算變更數(shù)據(jù)庫(kù)了,也只要修改config.ini文件的內(nèi)容就可以了,結(jié)合前面測(cè)試用例的管理(excel文件),sql語句的存放(xml文件),還有接下來我們要說的,businessCommon.py和存放具體case的文件夾,那么我們就已經(jīng)將數(shù)據(jù)和業(yè)務(wù)分開啦,哈哈哈,想想以后修改測(cè)試用例內(nèi)容,sql語句神馬的工作,再也不用每個(gè)case都修改,只要改幾個(gè)固定的文件,是不是頓時(shí)開心了呢?(嗯,想笑就大聲的笑吧)

回歸上面的configDB.py文件,內(nèi)容很簡(jiǎn)單,相信大家都能看得懂,就是連接數(shù)據(jù)庫(kù),執(zhí)行sql,獲取結(jié)果,最后關(guān)閉數(shù)據(jù)庫(kù)學(xué)習(xí)鏈接。

接下來,談?wù)勦]件吧,你是不是也遇到過這樣的問題:每次測(cè)試完之后,都需要給開發(fā)一份測(cè)試報(bào)告。那么,對(duì)于我這樣的懶人,是不愿意老是找人家開發(fā)的,所以,通過email自動(dòng)發(fā)送測(cè)試報(bào)告到指定郵箱的功能,(詳情見)每次測(cè)試完,我們可以讓程序自己給開發(fā)人員發(fā)一封email,告訴他們,測(cè)試已經(jīng)結(jié)束了,并且把測(cè)試報(bào)告以附件的形式,通過email發(fā)送給開發(fā)者的郵箱,這樣豈不是爽哉!

所以,configEmail.py應(yīng)運(yùn)而生。當(dāng)當(dāng)當(dāng)當(dāng)……請(qǐng)看:

configEmail.py

import os

import smtplib frome mail.mime.multipart

import MIMEMultipart from email.mime.text

import MIMEText from datetime

import datetime

import threading

import readConfigasreadConfig from common.Log

import MyLog

import zipfile

import globlocal

ReadConfig = readConfig.ReadConfig()

classEmail:

def __init__(self):

globalhost, user, password, port, sender, title, content? ?

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")? ? ?

self.value = localReadConfig.get_email("receiver")? ? ?

self.receiver = []# get receiver listforninstr(self.value).split("/"):? ? ? ? ?

self.receiver.append(n)

# defined email subject

date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")? ? ?

self.subject = title +" "+ date? ? ?

self.log = MyLog.get_log()? ? ?

self.logger = self.log.get_logger()? ? ?

self.msg = MIMEMultipart('mixed')

def config_header(self):

self.msg['subject'] = self.subject? ? ?

self.msg['from'] = sender? ? ?

self.msg['to'] =";".join(self.receiver)

def config_content(self):

content_plain = MIMEText(content,'plain','utf-8')? ? ?

self.msg.attach(content_plain)

def config_file(self):

# 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)? ? ? ? ?

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):

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):

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 Exceptionasex:? ? ? ? ?

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()

這里就是完整的文件內(nèi)容了!關(guān)于python對(duì)email的操作,繼續(xù)學(xué)習(xí)

離成功不遠(yuǎn)了,簡(jiǎn)單說明下HTMLTestRunner.py文件,哈哈哈,這個(gè)文件是從網(wǎng)上下載的,大神寫好的,用于生成html格式的測(cè)試報(bào)告?想知道生成測(cè)試報(bào)告的樣子?好,這就滿足好奇的你:

看上去不錯(cuò)吧,嗯,聰明的你們,也可以自己去探索下這個(gè)文件,修改修改,變成你自己的style哦~

好了,重頭戲來了,就是我們的runAll.py啦。請(qǐng)看主角登場(chǎng)。

這是我們整個(gè)框架運(yùn)行的入口,上面內(nèi)容完成后,這是最后一步啦,寫完它,我們的框架就算是完成了。(鼓掌,撒花~)

runAll.py

import unittest

import HTMLTestRunner

def set_case_list(self):

fb = open(self.caseListFile)

for value in fb.readlines():? ? ? ? ?

data = str(value)

if data !=''andnotdata.startswith("#"):? ? ? ? ? ? ?

self.caseList.append(data.replace("\n",""))? ? ?

fb.close()defset_case_suite(self):self.set_case_list()? ? ?

test_suite = unittest.TestSuite()? ? ?

suite_model = []forcaseinself.caseList:? ? ? ? ?

case_file = os.path.join(readConfig.proDir,"testCase")? ? ? ? ?

print(case_file)? ? ? ? ?

case_name = case.split("/")[-1]? ? ? ? ?

print(case_name+".py")? ? ? ? ?

discover = unittest.defaultTestLoader.discover(case_file, pattern=case_name +'.py', top_level_dir=None)? ? ? ? ? ? suite_model.append(discover)

if len(suite_model) >0:

for suite in suite_model:

fortest_nameinsuite:? ? ? ? ? ? ? ? ?

test_suite.addTest(test_name)

else:

return None

return test_suite

def run(self):

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 Exceptionasex:? ? ? ?

logger.error(str(ex))

finally:? ? ? ? ?

logger.info("*********TEST END*********")

# send test report by email

if int(on_off) ==0:? ? ? ? ? ? ?

self.email.send_email()elifint(on_off) ==1:? ? ? ? ? ? ?

logger.info("Doesn't send report email to developer.")else:? ? ? ? ? ? ?

logger.info("Unknow state.")

上面我貼出了runAll里面的主要部分,首先我們要從caselist.txt文件中讀取需要執(zhí)行的case名稱,然后將他們添加到python自帶的unittest測(cè)試集中,最后執(zhí)行run()函數(shù),執(zhí)行測(cè)試集。關(guān)于python的unittest詳細(xì)的學(xué)習(xí)資料1資料2

終于呢,整個(gè)接口自動(dòng)化框架已經(jīng)講完了,大家是不是看明白了呢?什么?之前的之前貼出的目錄結(jié)構(gòu)中的文件還有沒說到的?嘿嘿,,,相信不用小編多說,大家也大概知道了,剩下文件夾的作用了。嗯~思索萬千,還是決定簡(jiǎn)單談?wù)劙?。直接上圖,簡(jiǎn)單明了:

result文件夾會(huì)在首次執(zhí)行case時(shí)生成,并且以后的測(cè)試結(jié)果都會(huì)被保存在該文件夾下,同時(shí)每次測(cè)試的文件夾都是用系統(tǒng)時(shí)間命名,里面包含了兩個(gè)文件,log文件和測(cè)試報(bào)告。

testCase文件夾下,存放我們寫的具體的測(cè)試case啦,上面這些就是小編寫的一些。注意嘍,所有的case名稱都要以test開頭來命名哦,這是因?yàn)?,unittest在進(jìn)行測(cè)試時(shí)會(huì)自動(dòng)匹配testCase文件夾下面所有test開頭的.py文件

testFile文件夾下,放置我們測(cè)試時(shí)用來管理測(cè)試用例的excel文件和用于數(shù)據(jù)庫(kù)查詢的sql語句的xml文件哦。

最后就是caselist.txt文件了,就讓你們瞄一眼吧:

凡是沒有被注釋掉的,都是要被執(zhí)行的case名稱啦。在這里寫上你要執(zhí)行的case名稱就可以啦。

自動(dòng)化框架的實(shí)施過程:
testLogin.py? ---> interfaceURL.xml? --->?
SQL.xml? --->? caselist.txt? --->? runAll.py

參考資料:

https://my.oschina.net/u/3041656/blog/820023?p=6

https://gitee.com/null_534_6629/interfacetest/tree/master? (源碼)

https://mp.weixin.qq.com/s/ikex_lNScqw8yzSTG--uvw(另一篇接口自動(dòng)化文章+內(nèi)含源碼鏈接)

https://blog.csdn.net/zglwy/article/details/54026674(郵件發(fā)收原理)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容