本接口自動(dòng)化框架采用 python + unittest + request + openpyxl + myddt + pymysql 來實(shí)現(xiàn)接口自動(dòng)化。?
1、總體框架
2、單元測(cè)試框架 unittest
unittest 是 Python 自帶的一個(gè)單元測(cè)試框架
2.1 作用
-
管理用例
-
批量執(zhí)行用例
-
組織運(yùn)行結(jié)果/報(bào)告
-
讓代碼更穩(wěn)健
-
可拓展
2.2 unittest 框架中,有以下幾個(gè)組件:
TestCase:即測(cè)試用例,Unittest提供testCase類來編寫測(cè)試用例,一個(gè)TestCase的實(shí)例就是一個(gè)測(cè)試用例。一條測(cè)試用例就是一個(gè)完整的測(cè)試流程,包括測(cè)試前準(zhǔn)備環(huán)境的搭建(setUp),執(zhí)行測(cè)試代碼(run),以及測(cè)試后環(huán)境的還原(tearDown),通過運(yùn)行一條測(cè)試用例,可以對(duì)某一個(gè)問題進(jìn)行驗(yàn)證。
Fixture:即測(cè)試固件,用于測(cè)試用例環(huán)境的搭建和銷毀。在測(cè)試步驟執(zhí)行前需要為該測(cè)試用例準(zhǔn)備環(huán)境(SetUp),如啟動(dòng)app或打開瀏覽器,測(cè)試步驟執(zhí)行后需要恢復(fù)環(huán)境(TearDown),如關(guān)閉app或?yàn)g覽器,這時(shí)候就需要用到Fixture,使代碼更簡(jiǎn)潔。
TestSuite:即測(cè)試套件,把需要執(zhí)行的測(cè)試用例集合在一起就是TestSuite。使用TestLoader來加載TestCase到TestSuite中
TextTestRunner:即測(cè)試執(zhí)行器,用于執(zhí)行測(cè)試用例。該模塊中提供run方法執(zhí)行TestSuite中的測(cè)試用例,并返回測(cè)試用例的執(zhí)行結(jié)果,如運(yùn)行的用例總數(shù)、用例通過數(shù)、用例失敗數(shù)。
report:即測(cè)試報(bào)告。unittest框架沒有自帶的用于生成測(cè)試報(bào)告的模塊或接口,需要使用第三方的擴(kuò)展模塊HTMLTestRunner。
2.3?跳過執(zhí)行測(cè)試用例共有四種寫法
- @unittest.skip(reason) :跳過測(cè)試用例,reason ?為測(cè)試被跳過的原因。
- @unittest.skipIf(condition, reason) :當(dāng) condition 為真時(shí),跳過測(cè)試用例。
- @unittest.skipUnless(condition, reason) :跳過測(cè)試用例,除非 condition 為真。
2.4 斷言
2.5 報(bào)告
from BeautifulReport import BeautifulReport
from common.HTMLTestRunnerNew import HTMLTestRunner
# 4種測(cè)試報(bào)告
"""
1、生成 HTML 類型
2、生成 Br 類型
3、生成 txt 類型
"""
# ts0 = unittest.TestLoader().discover('test_cases')
# with open('reports/html_do接口自動(dòng)化.html','wb') as f:
# runner = HTMLTestRunner(f)
# runner.run(ts0)
#
# ts1 = unittest.TestLoader().discover('test_cases')
# br = BeautifulReport(ts1)
# br.report(description='DO',filename='br_do接口自動(dòng)化',report_dir='reports',theme='theme_memories')
#
#
# ts2 = unittest.TestLoader().discover('test_cases')
# with open('reports/txt_do接口自動(dòng)化.txt','w+') as f:
# unittest.TextTestRunner(f,2).run(ts2)
if __name__ == "__main__":
unittest.main()
3、基礎(chǔ)框架搭建
????????在項(xiàng)目根目錄下新建 common 文件夾,用來存儲(chǔ)公用方法。
????????在項(xiàng)目根目錄下新建 reports 文件夾,用來存儲(chǔ)項(xiàng)目報(bào)告。
????????在項(xiàng)目根目錄下新建 logs 文件夾,用來存儲(chǔ)結(jié)果日志。
????????在項(xiàng)目根目錄下新建 test_data 文件夾,用來存儲(chǔ)用例數(shù)據(jù)。
????????在項(xiàng)目根目錄下新建 test_cases 文件夾,用例存儲(chǔ)測(cè)試用例模塊。
????????在項(xiàng)目根目錄下新建 main.py 文件,作為入口函數(shù),方便項(xiàng)目調(diào)試。
3.1?common公用方法文件
????????3.1.1 init.py
# /usr/bin/env python
# __*__ coding: utf-8 __*__
# @Time : 2021/9/9 22:22
# @Author: 夜華
import settings
from common.log_handler import get_logger
from common.db_handler import DB
# 日志
logger = get_logger(**settings.LOG_CONFIG)
# 數(shù)據(jù)庫
db = DB(settings.DB_CONFIG)
????????3.1.2 http_requests.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 3:38 PM
@File : cliend_http_requests.py
@Project : PyCharm
"""
import requests
def cliend_http_requests(url,method,**kwargs):
method = method.lower()
return getattr(requests,method)(url,**kwargs)
if __name__ == "__main__":
case = {
'url' : 'http://10.21.5.74:33140/api/v1/login',
'method' : 'post',
'requests':{
'json' : {"email": "name", "password": "password"},
'headers' : {"Content-Type": "application/json;charset=UTF-8"}
}
}
response = cliend_http_requests(url=case['url'],method=case['method'],**case['requests'])
print(response.json())
????????3.1.3 data_handler.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 4:10 PM
@File : data_handler.py
@Project : PyCharm
"""
import json
from openpyxl import load_workbook
def get_test_data(filename,sheet_name):
wb = load_workbook(filename=filename)
sh = wb[sheet_name]
row = sh.max_row
column = sh.max_column
data = []
keys = []
for i in range(1,column+1):
keys.append(sh.cell(1,i).value)
for i in range(2,row+1):
temp = {}
for j in range(1,column+1):
temp[keys[j-1]] = sh.cell(i,j).value
try:
temp['request'] = json.loads(temp['request'])
temp['exportx_code'] = json.loads(temp['exportx_code'])
except json.decoder.JSONDecodeError:
raise ValueError('json數(shù)據(jù)轉(zhuǎn)換錯(cuò)誤')
data.append(temp)
return data
if __name__ == "__main__":
res = get_test_data(filename='../test_data/test_cases.xlsx',sheet_name='login')
print(res[0])
????????3.1.4 db_handler.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 2:58 PM
@File : db_config_handler.py
@Project : PyCharm
"""
import settings
import pymysql
class DB:
def __init__(self,db_config):
self.conn = pymysql.connect(**db_config)
def sql_one(self,sql):
with self.conn.cursor() as cursor:
cursor.execute(sql)
return cursor.fetchone()
def sql_many(self,sql,size=int):
with self.conn.cursor() as cursor:
cursor.execute(sql)
return cursor.fetchmany(size)
def sql_all(self,sql):
with self.conn.cursor() as cursor:
cursor.execute(sql)
return cursor.fetchall()
def exisx(self,sql):
with self.conn.cursor() as cursor:
cursor.execute(sql)
if cursor.fetchone():
return True
else:
return False
def sql_update(self,sql):
with self.conn.cursor() as cursor:
try:
cursor.execute(sql)
self.conn.commit()
except:
self.conn.rollback()
return cursor.fetchone()
def __del__(self):
self.conn.close()
if __name__ == "__main__":
db = DB(db_config=settings.DB_CONFIG)
print(db.sql_one("select * from help_category;"))
print(db.sql_many("select * from help_category;",2))
print(db.sql_all("select name from help_category;"))
print(db.exisx("select * from help_category where name = 'Contents';"))
print(db.sql_update("update help_category set url='' where name = 'Contents';"))
????????3.1.5 fixtrue
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 8:27 PM
@File : fixtrue.py
@Project : PyCharm
"""
import requests
import settings
from common import logger
def login(email,password):
data = {
'email': email,
'password': password
}
headers = {"Content-Type":"application/json;charset=UTF-8"}
url = settings.PROJECT_URL + settings.INTERFACE['login']
res = requests.post(url=url,json=data,headers=headers)
if res.status_code == 200:
logger.info('用戶登錄成功')
return res.json()
else:
logger.warning('用戶登錄失敗')
if __name__ == "__main__":
res = login(email='name',password='password')
????????3.1.6 logs_handler.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 3:28 PM
@File : log_handler.py
@Project : PyCharm
"""
import logging
def get_logger(name,filename,debug=False,fmt=None,mode='w',encoding='utf-8'):
logger = logging.getLogger(name=name)
logger.setLevel(level=logging.DEBUG)
if debug:
file_level = logging.DEBUG
console_level = logging.DEBUG
else:
file_level = logging.WARNING
console_level = logging.INFO
if fmt is None:
#fmt = '%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d] - %(message)s'
fmt = '%(asctime)s-[%(filename)s-->line:%(lineno)d]-%(levelname)s:%(message)s'
format = logging.Formatter(fmt)
file_handler = logging.FileHandler(filename=filename,mode=mode,encoding=encoding)
file_handler.setLevel(level=file_level)
console_handler = logging.StreamHandler()
console_handler.setLevel(level=console_level)
file_handler.setFormatter(format)
console_handler.setFormatter(format)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
if __name__ == "__main__":
logger = get_logger(name='do',filename='../logs/do.txt',debug=False,mode='a')
logger.debug(10)
logger.info(20)
logger.warning(30)
logger.error(40)
logger.critical(50)
????????3.1.7 reports_handler.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 3:42 PM
@File : reports_handler.py
@Project : PyCharm
"""
import os
from BeautifulReport import BeautifulReport
from common.HTMLTestRunnerNew import HTMLTestRunner
from datetime import datetime
def reports(ts,filename,report_dir,theme='theme_default',title=None,description=None,tester=None,_type='br'):
time_prefix=datetime.now().strftime('%Y-%m-%d_%H:%M')
filename = '{}_{}'.format(time_prefix,filename)
if _type == 'br':
br = BeautifulReport(ts)
br.report(description=description,filename=filename,report_dir=report_dir,theme=theme)
else:
with open(os.path.join(report_dir,filename),'wb') as f:
runner = HTMLTestRunner(f,title=title,description=description,tester=tester)
runner.run(ts)
4、config 配置文件夾
????????4.1 config_dev.ini
[URL]
api_url = http://10.21.5.74:33140
????????4.2 config_handler.py?
# /usr/bin/env python
# __*__ coding: utf-8 __*__
# @Time : 2021/9/10 21:00
# @Author: 夜華
"""
封裝配置文件
"""
import yaml
from configparser import ConfigParser
def get_config(filename,encoding='utf-8'):
# 根據(jù) . 獲取文件后綴,并獲取后面的內(nèi)容
suffix = filename.split('.')[-1]
if suffix in ['ini','cfg','cng']: # 判斷文件后綴是否存在列表內(nèi)
# 就是ini 配置
config = ConfigParser() # 實(shí)例
config.read(filename,encoding=encoding) # 讀取文件
data = {} #
for section in config.sections(): #獲取 文件里面的所有段名
data[section] = dict(config.items(section))
elif suffix in ['yaml','yml']:
# 就是 yaml 配置
with open(filename,'r',encoding=encoding) as f:
data = yaml.load(f,Loader=yaml.FullLoader)
else:
raise ValueError('不能識(shí)別的配置后綴')
return data
if __name__ == '__main__':
get = get_config('../config.ini')
print(get)
res = get_config('../config.yaml')
print(res)
????????4.3 init.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/6/1 2:18 PM
@File : __init__.py
@Project : PyCharm
"""
import os
import sys
from config.config_handler import get_config
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
if sys.argv[1] == "DEV":
Config = get_config(os.path.join(BASE_DIR, './config_dev.ini'))
else:
Config = get_config(os.path.join(BASE_DIR, './config_test.ini'))
5、 logs 文件夾
保存接口測(cè)試過程中輸出的日志
?6、reports 文件夾
保存接口測(cè)試報(bào)告
7、test_cases 文件夾
????????7.1 base_case.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 4:39 PM
@File : base_case.py
@Project : PyCharm
"""
import unittest
from common import logger,db
import requests
import settings
class Basic_test_case(unittest.TestCase):
name = '基類'
logger = logger
requests = requests
session = requests.session()
db = db
settings = settings
@classmethod
def setUpClass(cls) -> None:
cls.logger.info('---------------【{}】開始測(cè)試---------------'.format(cls.name))
@classmethod
def tearDownClass(cls) -> None:
cls.logger.info('---------------【{}】結(jié)束測(cè)試---------------'.format(cls.name))
def check(self,case):
self.logger.info('---------------【{}】開始測(cè)試---------------'.format(self.name))
self.case = case # 測(cè)試用例
self.step() # 測(cè)試步驟
self.assert_status_code() # 斷言狀態(tài)碼
self.assert_json() # 斷言響應(yīng)信息
self.assert_db() # 斷言數(shù)據(jù)庫是否存在數(shù)據(jù)
self.logger.info('---------------【{}】結(jié)束測(cè)試---------------'.format(self.name))
def step(self):
self.case['url'] = self.settings.PROJECT_URL + self.settings.INTERFACE[self.case['url']]
try:
self.resposen = self.http_requests(url=self.case['url'],method=self.case['method'],**self.case['request'])
except Exception as e:
self.logger.warning('用例【{}】發(fā)送請(qǐng)求錯(cuò)誤'.format(self.case['title']))
self.logger.debug('url:【{}】'.format(self.case['url']))
self.logger.debug('method:【{}】'.format(self.case['method']))
raise e
else:
self.logger.info('用例【{}】發(fā)送請(qǐng)求成功'.format(self.case['title']))
def assert_status_code(self):
try:
self.assertEqual(self.resposen.status_code,self.case['status_code'])
except AssertionError as e:
self.logger.warning('用例【{}】狀態(tài)碼斷言錯(cuò)誤'.format(self.case['title']))
self.logger.debug('預(yù)期狀態(tài)碼:【{}】'.format(self.case['status_code']))
self.logger.debug('實(shí)際狀態(tài)碼:【{}】'.format(self.resposen.status_code))
raise e
else:
self.logger.info('用例【{}】狀態(tài)碼斷言成功'.format(self.case['title']))
def assert_json(self):
res = self.resposen.json()
res_data = {
'phone':res.get('phone',None),
'roleType':res.get('roleType',None)
}
try:
self.assertEqual(res_data,self.case['exportx_code'])
except AssertionError as e:
self.logger.warning('用例【{}】響應(yīng)信息斷言錯(cuò)誤'.format(self.case['title']))
self.logger.debug('預(yù)期內(nèi)容:【{}】'.format(self.case['exportx_code']))
self.logger.debug('實(shí)際內(nèi)容:【{}】'.format(res_data))
self.logger.debug('響應(yīng)內(nèi)容:【{}】'.format(res))
raise e
else:
self.logger.info('用例【{}】響應(yīng)信息斷言成功'.format(self.case['title']))
def assert_db(self):
if self.case.get('sql'):
try:
db_res = self.db.exisx(self.case['sql'])
self.assertTrue(db_res)
except Exception as e:
self.logger.warning('用例【{}】數(shù)據(jù)庫查詢失敗'.format(self.case['title']))
self.logger.debug('sql:【{}】'.format(self.case['sql']))
raise e
else:
self.logger.info('用例【{}】數(shù)據(jù)庫查詢成功'.format(self.case['title']))
def http_requests(self,url,method,**kwargs)->requests.Response:
method = method.lower()
return getattr(self.session,method)(url=url,**kwargs)
if __name__ == "__main__":
unittest.main()
????????7.2 test_login.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 3:43 PM
@File : test_login.py
@Project : PyCharm
"""
import unittest
import settings
from test_cases.base_case import Basic_test_case
from common.data_handler import get_test_data
from common.myddt import data,ddt
cases = get_test_data(settings.TEST_DATA,sheet_name='login')
@ddt
class Test(Basic_test_case):
name = '登錄'
@data(*cases)
def test_01(self,case):
self.check(case)
if __name__ == "__main__":
unittest.main()
8、test_data 文件夾
????????8.1 使用Excel表格維護(hù)測(cè)試用例
用例:id、title、url、method、requests、status_code、exportx_code、sql
9、main.py
main.py 為 測(cè)試入口。文章來源:http://www.zghlxwxcb.cn/news/detail-702438.html
from BeautifulReport import BeautifulReport
from common.HTMLTestRunnerNew import HTMLTestRunner
import settings
import unittest
from common.reports_handler import reports
ts = unittest.TestLoader().discover('test_cases')
runner = reports(ts,**settings.REPORTS_CONFIG)
if __name__ == "__main__":
unittest.main()
10、settings.py
"""
-*- coding: utf-8 -*-
@Author : 夜華
@Time : 2023/5/14 2:53 PM
@File : settings.py
@Project : PyCharm
"""
import os
import project_api
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEST_DATA = os.path.join(BASE_DIR,'test_data/test_cases.xlsx')
PROJECT_URL = 'http://10.00.5.74:00000'
INTERFACE = {
'login' : '/api/v1/login',
'query' : '/api/v1/approve/query?page=1&size=4294967295'
}
# 數(shù)據(jù)庫配置
DB_CONFIG = {
'user': 'root',
'password': '123456',
'host': '127.0.0.1',
'database': 'mysql',
'port': 3306,
'autocommit': False
}
# 輸出日志
LOG_CONFIG = {
'name' : 'DPO',
'filename' : os.path.join(BASE_DIR,'logs/dpo.txt'),
'debug' : True,
'mode' : 'w',
'fmt' : None,
'encoding': 'utf-8'
}
# 報(bào)告
REPORTS_CONFIG = {
'filename':'do接口自動(dòng)化',
'report_dir' : os.path.join(BASE_DIR,'reports'),
'theme' : 'theme_default',
'description' : 'DO',
'title': 'DO1期',
'_type': 'br'
}
USER_LOGIN = {"email":"name","password":"password"}
11、終端內(nèi)執(zhí)行
注意:DEV表示開發(fā)環(huán)境,如果想在非開發(fā)環(huán)境進(jìn)行測(cè)試,就輸入TEST。也可以在4.3init.py 修改文章來源地址http://www.zghlxwxcb.cn/news/detail-702438.html
到了這里,關(guān)于從零搭建完整 Python 接口自動(dòng)化測(cè)試框架—持續(xù)更新的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!