我又來了,來分享年前的unittest接口自動化實戰啦。這次自動化接口框架比較簡單,但是五臟俱全。(注:項目是針對我們公司內部系統的測試,我就不分享鏈接了。)
項目簡介
項目名稱:****名片系統
項目目的:實現系統項目自動化測試執行
項目版本:v1.0
項目目錄
tools #存放輔助方法
configEmail.py #發送測試報告電子郵件 HTMLTestRunner.py #第三方插件 log.py #輸出日志文件 mail_receiver.txt #存放接收人郵箱地址 read_json.py #讀取單一測試數據json文件 read_more_json #讀取more測試數據json文件
report #存放html測試報告

logs #存放輸入日志文件

data #存放參數化測試數據(json文件)

case #存放測試用例

api #存放封裝測試方法

caselist.txt #存放要執行的測試用例
getpathInfo.py #獲取當前路徑
runAll.py #運行caselist.txt中的測試用例,輸出測試報告
項目框架
unittest單元測試框架
項目設計
1.每一個用例組合在一個測試類里面生成一個py文件
2.一個模塊(被測項目功能)對應一個py文件及一個測試類(測試文件)
3.通過 parameterized 對參數進行參數化
項目目標
1. 生成測試用例執行結果報告
2.生成測試用例執行日志
3.用例執行失敗或者執行完成后自動發送郵件報告
4.數據驅動(讀取測試數據,減少腳本維護成本)
項目代碼
getpathInfo.py #獲取當前路徑

import os def get_Path(): #獲取上級路徑 #path = os.path.abspath(os.path.join(os.getcwd(), "..")) #獲取當前路徑 path = os.path.split(os.path.realpath(__file__))[0] return path if __name__ == '__main__':# 執行該文件,測試下是否OK print('測試路徑是否OK,路徑為:', get_Path())
caselist.txt #存放可執行測試用例
runAll.py #運行文件

import os import time from BeautifulReport import BeautifulReport import getpathInfo import tools.HTMLTestRunner as HTMLTestRunner import unittest from tools.configEmail import send_email, getReceiverInfo import tools.Log path = getpathInfo.get_Path() report_path = os.path.join(path, 'report') log = tools.Log.logger class AllTest:#定義一個類AllTest def __init__(self):#初始化一些參數和數據 global resultPath resultPath = os.path.join(report_path) self.caseListFile = os.path.join(path, "caselist.txt")#配置執行哪些測試文件的配置文件路徑 self.caseFile = os.path.join(path, "case")#真正的測試斷言文件路徑 self.caseList = [] log.info('resultPath'+resultPath)#將resultPath的值輸入到日志,方便定位查看問題 log.info('caseListFile'+self.caseListFile)#同理 log.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('else:') 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') currTime = time.strftime('%Y-%m-%d %H_%M_%S') filename = currTime + '.html' # currTime = time.strftime('%Y-%m-%d %H_%M_%S') # fileName = report_path + r'\report'+ currTime + '.html' result = BeautifulReport(suit) result.report(filename= filename, description='接口測試報告') # fp = open(fileName, 'wb') # runner = HTMLTestRunner.HTMLTestReportCN \ # (stream=fp, title='自動化接口測試報告', # description='處理器:Intel(R) Core(TM) ' # 'i5-5200U CPU @ 2.20GHz 2.20 GHz ' # '內存:8G 系統類型: 64位 版本: windows 10 專業版') # 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() #發送測試郵件 # read_msg = getReceiverInfo( # r'F:\python_test\Automation_interfaceTest\tools\mail_receiver.txt') # sendmail = send_email(read_msg) # sendmail.sendEmail(fileName) # 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()
tools #輔助方法
configEmail.py # 發送郵件

import os import smtplib import getpathInfo from tools.Log import Logger from email.mime.text import MIMEText from email.header import Header log = Logger(__name__) path = getpathInfo.get_Path() report_path = os.path.join(path, 'report') # 存放測試報告文件的路徑 mail_path = os.path.join(path,'tools')#存放收件人地址文件路徑 class send_email(): ''' 郵件配置信息 ''' def __init__(self, receiver, subject='*******', server='smtp.qq.com', fromuser='******', frompassword='yjkxwfmrbumrbbce', sender='*******'): """ :param receiver: :param subject: :param server: :param fromuser: :param frompassword: :param sender: """ self._server = server self._fromuser = fromuser self._frompassword = frompassword self._sender = sender self._receiver = receiver self._subject = subject def sendEmail(self, fileName): """ :param filename: :return: """ # 打開報告文件讀取文件內容 try: f = open(os.path.join(report_path, fileName), 'rb') fileMsg = f.read() except Exception: log.logger.exception( 'open or read file [%s] failed,No such file or directory: %s' % (fileName, report_path)) log.logger.info('open and read file [%s] successed!' % fileName) else: f.close() # 郵件主題 subject = 'Python test report' # # 郵件設置 msg = MIMEText(fileMsg, 'html', 'utf-8') msg['subject'] = Header(subject, 'utf-8') msg['from'] = self._sender # 連接服務器,登錄服務器,發送郵件 try: smtp = smtplib.SMTP() smtp.connect(self._server) smtp.login(self._fromuser, self._frompassword) except Exception: log.logger.exception('connect [%s] server failed or username and password incorrect!' % smtp) else: log.logger.info('email server [%s] login success!' % smtp) try: smtp.sendmail(self._sender, self._receiver, msg.as_string()) except Exception: log.logger.exception('send email failed!') else: log.logger.info('send email successed!') # 從文件中讀取郵件接收人信息 def getReceiverInfo(fileName): ''' :param filename: 讀取接收郵件人信息 :return: 接收郵件人信息 ''' try: openFile = open(os.path.join(mail_path, fileName)) except Exception: log.logger.exception('open or read file [%s] failed,No such file or directory: %s' % (fileName, mail_path)) else: log.logger.info('open file [%s] successed!' % fileName) for line in openFile: msg = [i.strip() for i in line.split(',')] log.logger.info('reading [%s] and got receiver value is [%s]' % (fileName, msg)) return msg if __name__ == '__main__':# 運營此文件來驗證寫的send_email是否正確 readMsg = getReceiverInfo('mail_receiver.txt') sendmail = send_email(readMsg) sendmail.sendEmail('report.html')
HTMLTestRunner.py #第三方插件
log.py #輸出日志文件

import os import logging import time from logging.handlers import TimedRotatingFileHandler import getpathInfo path = getpathInfo.get_Path() log_path = os.path.join(path, 'logs') # 存放log文件的路徑 class Logger(object): def __init__(self, logger_name='logs…'): self.logger = logging.getLogger(logger_name) logging.root.setLevel(logging.NOTSET) currTime = time.strftime("%Y-%m-%d") self.log_file_name = currTime+'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()
mail_receiver.txt #存放接受人郵箱地址
read_json.py # 讀取單一測試數據

import json import getpathInfo import os class ReadJson(object): def __init__(self,filename): path = getpathInfo.get_Path() self.filepath = os.path.join(path, 'data')+"/"+filename def read_json(self): with open(self.filepath, "r", encoding="utf-8")as f: # 調用load方法加載文件流 return json.load(f) if __name__ == '__main__': data = ReadJson("updateuserpwd.json").read_json() arrs = [] arrs.append((data.get("url"), data.get("userId"), data.get("data"), data.get("success"), data.get("message"))) print(arrs)
read_more_json.py # 讀取more測試數據

import json class ReadJson(object): def __init__(self,filename): self.filepath = '../data/' +filename def read_json(self): with open(self.filepath, "r", encoding="utf-8")as f: # 調用load方法加載文件流 return json.load(f) if __name__ == '__main__': datas = ReadJson("login_more.json").read_json() arrs = [] for data in datas.values(): arrs.append((data.get("url"), data.get("mobile"), data.get("code"), data.get("expect_result"), data.get("status_code"))) print(arrs)
#測試用例

import requests class ApiLogin(object): def api_post_login(self,data): #headers定義 headers = {"Content-Type": "application/x-www-form-urlencoded"} #url定義 url = "http://*******" #調用post返回相應對象 return requests.post(url, headers=headers,data = data)

#客戶經理登錄接口 import sys import unittest from api.api_login import ApiLogin from parameterized import parameterized from tools.read_more_json import ReadJson import tools.Log log = tools.Log.logger #讀取數據函數 def get_data(): datas = ReadJson('login.json').read_json() arrs = [] for data in datas.values(): arrs.append((data.get("data"), data.get("message"), data.get("desc"))) return arrs class TestLogin(unittest.TestCase): '''登錄接口 ''' @parameterized.expand(get_data()) # 參數化測試用例 def test_login(self,data,message,desc): #調用登錄方法 s = ApiLogin().api_post_login(data) #調試使用添加描述 self._testMethodDoc = desc #print('查看響應結果:',s.text) #斷言響應信息 self.assertIn(message,s.text) # 生成響應日志 log.info('[%s]響應數據為:[%s]' % (sys._getframe().f_code.co_name, s.text)) if __name__ == '__main__': unittest.main()
#測試數據

{ "login_001":{ "data":{"loginName":"admin", "password": "123456","memberPass": "on"}, "message": "名片管理系統", "desc": "正常登錄"}, "login_002": { "data":{"loginName":"admin", "password": "123456n","memberPass": "on"}, "message": "密碼不正確", "desc": "賬號錯誤登錄"}, "login_003": { "data":{"loginName":"admir", "password": "123456","memberPass": "on"}, "message": "賬號不存在", "desc": "密碼錯誤登錄"}, "login_004": { "data":{"loginName":" ", "password": "123456","memberPass": "on"}, "message": "帳號密碼登錄", "desc": "密碼為空登錄"}, "login_005": { "data":{"loginName":"admin", "password": " ","memberPass": "on"}, "message": "密碼不正確", "desc":"賬號為空登錄"}, "login_006": { "data":{"loginName":"adm in", "password": "123456","memberPass": "on"}, "message": "賬號不存在", "desc":"賬號存在空格登錄"}, "login_007": { "data":{"loginName":"admin", "password": "123 456","memberPass": "on"}, "message": "密碼不正確", "desc":"密碼存在空格登錄"}, "login_008": { "data":{"loginName":"admin.", "password": "123456","memberPass": "on"}, "message": "賬號不存在", "desc":"賬號存在特殊符號登錄"}, "login_009": { "data":{"loginName":"admin", "password": "123456.","memberPass": "on"}, "message": "密碼不正確", "desc":"密碼存在特殊符號登錄"}, "login_010": { "data":{"loginName":"admi", "password": "123456","memberPass": "on"}, "message": "賬號不存在", "desc":"賬號不完整登錄"}, "login_010": { "data":{"loginName":"admin", "password": "12345","memberPass": "on"}, "message": "密碼不正確", "desc":"密碼不完整登錄"} }
#測試報告
就這樣吧,其他測試用例與之類似就不展出了。