一、項目結構
1. 新建一個工程,工程名為:sales_interface_auto
2. 在工程的根目錄新建一個py腳本:runAll.py 執行接口自動化的入口,項目工程部署完畢后直接運行該文件即可
3. 在項目下創建幾個package包:
----common:這個包放置一些公共的方法,例如:讀取excel,讀取mysql,get和post請求的封裝,發送Email的封裝,讀取手機公共參數的封裝,Log.py是封裝日志的輸入
----config:這個包里是放置一些獲取根文件夾目錄,接口服務器地址,讀寫配置文件封裝的方法
----result: 該包里存放日志,截圖,HTML報告
----testCase:這個包放test開頭的測試用例,也可以放一些封裝接口的方法
----testFile/case:存放excel測試用例
----testModels :存放對應接口腳本的裝飾器提取器
----util/generator:接口測試中用到的一些數據的生成,如:身份證號,名字,手機號,隨機字符串等
----caselist.txt :順序存放testCase中test打頭的用例,切記注意順序,無需執行時,首位加#號即可
----config.ini :這里是配置文件,如郵箱的一些參數,數據庫,手機靜態參數,以及存放測試過程生成的參數
----config_url.ini:這里是配置文件,存放接口地址
二、詳細介紹個目錄
1.runAll.py
源碼如下:
import os import common.HTMLTestRunner as HTMLTestRunner from config import readConfig, getpathInfo import unittest from common.configEmail import send_email import common.Log from testCase.test_init_user_info import InitUserInfo from config import readConfig as readConfig, writeConfig as writeConfig, readExcel, geturlParams writeconfig = writeConfig.WriteConfig() send_mail = send_email() path = getpathInfo.get_Path() report_path = os.path.join(path, 'result') on_off = readConfig.ReadConfig().get_email('on_off') loggger = common.Log.logger #定義一個類AllTest class AllTest: # 初始化一些參數和數據 def __init__(self): global resultPath # result/report.html resultPath = os.path.join(report_path, "report.html") # 配置執行哪些測試文件的配置文件路徑 self.caseListFile = os.path.join(path, "caselist.txt") # 真正的測試斷言文件路徑 self.caseFile = os.path.join(path, "testCase") # 初始化一下測試中用到的數據,並存儲到配置文件 self.idno = InitUserInfo().generate_id_no() self.c_name = InitUserInfo().generate_c_name() self.c_mobile = InitUserInfo().generate_mobile() self.caseList = [] writeconfig.write_potentiall_user_info(self.idno,self.c_name,self.c_mobile) loggger.info('resultPath'+resultPath)# 將resultPath的值輸入到日志,方便定位查看問題 loggger.info('caseListFile'+self.caseListFile) loggger.info('caseList'+str(self.caseList)) def set_case_list(self): """ 讀取caselist.txt文件中的用例名稱,並添加到caselist元素組 :return: """ fb = open(self.caseListFile) for value in fb.readlines(): data = str(value) if data != '' and not data.startswith("#"):# 如果data非空且不以#開頭 self.caseList.append(data.replace("\n", ""))# 讀取每行數據會將換行轉換為\n,去掉每行數據中的\n fb.close() def set_case_suite(self): """ :return: """ self.set_case_list()# 通過set_case_list()拿到caselist元素組 test_suite = unittest.TestSuite() suite_module = [] for case in self.caseList:# 從caselist元素組中循環取出case case_name = case.split("/")[-1]# 通過split函數來將aaa/bbb分割字符串,-1取后面,0取前面 print(case_name+".py")# 打印出取出來的名稱 # 批量加載用例,第一個參數為用例存放路徑,第一個參數為路徑文件名 discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + '.py', top_level_dir=None) suite_module.append(discover)# 將discover存入suite_module元素組 print('suite_module:'+str(suite_module)) if len(suite_module) > 0:# 判斷suite_module元素組是否存在元素 for suite in suite_module:# 如果存在,循環取出元素組內容,命名為suite for test_name in suite:# 從discover中取出test_name,使用addTest添加到測試集 test_suite.addTest(test_name) else: print('測試套件中無可用的測試用例') return None return test_suite def run(self): """ run test :return: """ try: suit = self.set_case_suite()#調用set_case_suite獲取test_suite print('try') print(str(suit)) if suit is not None:#判斷test_suite是否為空 print('if-suit') fp = open(resultPath, 'wb')# 打開result/20181108/report.html測試報告文件,如果不存在就創建 # 調用HTMLTestRunner runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='Test Report', description='Test Description') runner.run(suit) else: print("Have no case to test.") except Exception as ex: print(str(ex)) # log.info(str(ex)) finally: print("*********TEST END*********") # log.info("*********TEST END*********") fp.close() # 判斷郵件發送的開關 if on_off == 'on': send_mail.outlook() else: print("郵件發送開關配置關閉,請打開開關后可正常自動發送測試報告") # pythoncom.CoInitialize() # scheduler = BlockingScheduler() # scheduler.add_job(AllTest().run, 'cron', day_of_week='1-5', hour=14, minute=59) # scheduler.start() if __name__ == '__main__': AllTest().run()
1.1 __init__ 初始化數據
# 定義全局變量
global resultPath
# 取到report.html絕對路徑
resultPath = os.path.join(report_path, "report.html")
# 拿到caselist.txt的絕對路徑
self.caseListFile = os.path.join(path, "caselist.txt")
# 拿到testCase的絕對路徑
self.caseFile = os.path.join(path, "testCase")
# 初始化一下測試中用到的數據,並存儲到配置文件
self.idno = InitUserInfo().generate_id_no()
self.c_name = InitUserInfo().generate_c_name()
self.c_mobile = InitUserInfo().generate_mobile()
self.caseList = []
writeconfig.write_potentiall_user_info(self.idno,self.c_name,self.c_mobile)
# 將resultPath等的值輸入到日志,方便定位查看問題
loggger.info('resultPath'+resultPath)
loggger.info('caseListFile'+self.caseListFile)
loggger.info('caseList'+str(self.caseList))
1.2 set_case_list :讀取caselist.txt文件中的用例名稱,並添加到caselist元素組
read() #一次性讀取文本中全部的內容,以字符串的形式返回結果 readline() #只讀取文本第一行的內容,以字符串的形式返回結果 readlines() #讀取文本所有內容,並且以數列的格式返回結果,一般配合for in使用
# 打開caselist.txt文件,
fb = open(self.caseListFile)
for value in fb.readlines():
data = str(value)
# 如果data非空且不以#開頭
if data != '' and not data.startswith("#"):
# 讀取每行數據會將換行轉換為\n,去掉每行數據中的\n
self.caseList.append(data.replace("\n", ""))
fb.close()
1.3 set_case_suite 設置測試套件
# 通過set_case_list()拿到caselist元素組
self.set_case_list()
test_suite = unittest.TestSuite()
suite_module = []
# 從caselist元素組中循環取出case
for case in self.caseList:
case_name = case
# 打印出取出來的名稱
print(case_name+".py")
# 批量加載用例,第一個參數self.caseFile為用例存放路徑,第二個參數case_name為路徑文件名
discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + '.py', top_level_dir=None)
# 將discover存入suite_module元素組
suite_module.append(discover)
輸出為此模式:找到測試用例文件中的test開頭的方法
suite_module:[<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<paramunittest.TestLogin_0 testMethod=test01case>]>]>]>]
print('suite_module:'+str(suite_module))
# 判斷suite_module元素組是否存在元素
if len(suite_module) > 0:
# 如果存在,循環取出元素組內容,命名為suite
for suite in suite_module:
# 從discover中取出test_name,使用addTest添加到測試集
for test_name in suite:
test_suite.addTest(test_name)
else:
print('測試套件中無可執行的測試用例')
return None
return test_suite
1.4 run 執行測試用例套件
try:
# 調用set_case_suite獲取test_suite
suit = self.set_case_suite()
# print('try')
# print(str(suit))
# 判斷test_suite是否為空
if suit is not None:
# print('if-suit')
# 打開result/report.html測試報告文件,如果不存在就創建
fp = open(resultPath, 'wb')
# 調用HTMLTestRunner
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='UU隊長 測試報告', description='測試用例執行結果')
# 通過HTMLTestRunner的run()方法來運行測試套件中的測試用例,並寫入測試報告
runner.run(suit)
else:
print("Have no case to test.")
except Exception as ex:
print(str(ex))
# log.info(str(ex))
finally:
print("*********TEST END*********")
# log.info("*********TEST END*********")
fp.close()
# 判斷郵件發送的開關
if on_off == 'on':
send_mail.outlook()
else:
print("郵件發送開關配置關閉,請打開開關后可正常自動發送測試報告")
2. readConfig.py 讀取配置文件
代碼如下:
import os import configparser from config import getpathInfo path = getpathInfo.get_Path()# 項目路徑 config_path = os.path.join(path, 'config.ini')# config.ini的絕對路徑 config_uri_path = os.path.join(path, 'config_uri.ini')# config_uri.ini的絕對路徑 config = configparser.ConfigParser()# 調用外部的讀取配置文件的方法 config_uri = configparser.ConfigParser()# 調用外部的讀取配置文件的方法 config.read(config_path, encoding='utf-8') config_uri.read(config_uri_path, encoding='utf-8') class ReadConfig(): def get_http(self, name): value = config.get('HTTP', name) return value def get_email(self, name): value = config.get('EMAIL', name) return value def get_mysql(self, name): value = config.get('DATABASE', name) return value def get_static_params(self,name): value = config.get("StaticParams",name) return value def get_interface_uri(self,name): value = config_uri.get("URI",name) return value def get_common_params(self,name): path = getpathInfo.get_Path() # 調用實例化,還記得這個類返回的路徑為 config_path = os.path.join(path, 'config.ini') # 這句話是在path路徑下再加一級,最后變成 config = configparser.ConfigParser() # 調用外部的讀取配置文件的方法 config.read(config_path, encoding='utf-8') value = config.get("CommonParams",name) print(value) # logger.info("=========readconfig===="+value) return value def get_personal_information(self,name): value = config.get("PersonalInformation", name) # print(value) # logger.info("=========readconfig===="+value) return value if __name__ == '__main__':# 測試一下,我們讀取配置文件的方法是否可用 print('HTTP中的baseurl值為:', ReadConfig().get_http('baseurl')) print('EMAIL中的開關on_off值為:', ReadConfig().get_email('on_off')) print('StaticParams中的開關clientIp值為:', ReadConfig().get_static_params('clientIp'))
2.1 靜態調用
# 項目路徑
path = getpathInfo.get_Path()
# config.ini的絕對路徑
config_path = os.path.join(path, 'config.ini')
# config_uri.ini的絕對路徑
config_uri_path = os.path.join(path, 'config_uri.ini')
# 調用外部的讀取配置文件的方法
config = configparser.ConfigParser()
config_uri = configparser.ConfigParser()
# utf-8的方式讀取配置文件
config.read(config_path, encoding='utf-8')
config_uri.read(config_uri_path, encoding='utf-8')
2.2 具體實現
[StaticParams] clientmac = 9e:ee:fb:0f:5b:b8 clientdensity = 3.0 latitude = 255 longitude = 255 isjailbroken = 0 jailreason = NO Jail clientversion = 3.2.5 deviceid = 9e:ee:fb:0f:5b:b8 platform = Android deviceinfo = {"deviceModel":"MI 5","deviceOs":"23_6.0.1"} network = wifi screensize = 1920*1080 clientip = 10.0.3.15
讀取代碼
class ReadConfig(): # 封裝讀取靜態參數 def get_static_params(self,name): value = config.get("StaticParams",name) return value
3. 寫入配置文件
import os,sys import configparser from config import getpathInfo path = getpathInfo.get_Path()# 調用實例化,還記得這個類返回的路徑為 config_path = os.path.join(path, 'config.ini')# 這句話是在path路徑下再加一級,最后變成 config = configparser.ConfigParser()# 調用外部的讀取配置文件的方法 config.read(config_path, encoding='utf-8') class WriteConfig(): def write_common_params(self,token,id,username,secret): # print(config.options("CommonParams")) config.set("CommonParams",'customer_token',token) config.set("CommonParams",'user_id',id) config.set("CommonParams",'username',username) config.set("CommonParams",'secret',secret) # logger.info("======writeconfig======"+token) with open(config_path,'w',encoding="utf-8") as f: config.write(f) # sys.stdout.flush() if __name__ == '__main__':# 測試一下,我們讀取配置文件的方法是否可用 WriteConfig().write_common_params("123","哈哈","1232323")
4. Log.py日志文件
代碼如下:
import os import logging from logging.handlers import TimedRotatingFileHandler from config import getpathInfo path = getpathInfo.get_Path() log_path = os.path.join(path, 'result') # 存放log文件的路徑 class Logger(object): def __init__(self, logger_name='logs…'): self.logger = logging.getLogger(logger_name) logging.root.setLevel(logging.NOTSET) self.log_file_name = 'logs' # 日志文件的名稱 self.backup_count = 5 # 最多存放日志的數量 # 日志輸出級別 self.console_output_level = 'WARNING' self.file_output_level = 'DEBUG' # 日志輸出格式 self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') def get_logger(self): """在logger中添加日志句柄並返回,如果logger已有句柄,則直接返回""" if not self.logger.handlers: # 避免重復日志 console_handler = logging.StreamHandler() console_handler.setFormatter(self.formatter) console_handler.setLevel(self.console_output_level) self.logger.addHandler(console_handler) # 每天重新創建一個日志文件,最多保留backup_count份 file_handler = TimedRotatingFileHandler(filename=os.path.join(log_path, self.log_file_name), when='D', interval=1, backupCount=self.backup_count, delay=True, encoding='utf-8') file_handler.setFormatter(self.formatter) file_handler.setLevel(self.file_output_level) self.logger.addHandler(file_handler) return self.logger logger = Logger().get_logger()
5. 裝飾器提取響應,類似Java中的getter和setter方法
__author__ = 'csjin' # 定義@property裝飾器 class Login(object): @property def code(self): return self.__code @code.setter def code(self,code): self.__code = code @property def msg(self): return self.__msg @msg.setter def msg(self,msg): self.__msg = msg @property def customer_token(self): return self.__token @customer_token.setter def customer_token(self,token): self.__token = token