若有任何學習問題,能夠添加做者微信:lockingfreecss
Python接口測試實戰1(上)- 接口測試理論
Python接口測試實戰1(下)- 接口測試工具的使用
Python接口測試實戰2 - 使用Python發送請求
Python接口測試實戰3(上)- Python操做數據庫
Python接口測試實戰3(下)- unittest測試框架
Python接口測試實戰4(上) - 接口測試框架實戰
Python接口測試實戰4(下) - 框架完善:用例基類,用例標籤,從新運行上次失敗用例
Python接口測試實戰5(上) - Git及Jenkins持續集成
Python接口測試實戰5(下) - RESTful、Web Service及Mock Serverhtml
更多學習資料請加QQ羣: 822601020獲取mysql
以前的用例中,數據直接寫在代碼文件裏,不利於修改和構造數據
這裏咱們使用Excel保存測試數據,實現代碼和數據的分離
新建Excel文件test_user_data.xlsx
包含兩個工做簿TestUserLogin
和TestUserReg
,並複製到項目根目錄下
sql
更新: excel表格中,增長一個headers列,內容爲json格式, 以下數據庫
TestUserLoginjson
case_name | url | method | headers | data | expect_res |
---|---|---|---|---|---|
test_user_login_normal | http://115.28.108.130:5000/api/user/login/ | POST | {} | {"name": "張三","password":"123456"} | <h1>登陸成功</h1> |
test_user_login_password_wrong | http://115.28.108.130:5000/api/user/login/ | POST | {} | {"name": "張三","password":"1234567"} | <h1>失敗,用戶名或密碼錯誤</h1> |
TestUserRegapi
case_name | url | method | headers | data | expect_res |
---|---|---|---|---|---|
test_user_reg_normal | http://115.28.108.130:5000/api/user/login/ | POST | {} | {"name": "范冰冰","password":"123456"} | "{"code: "100000","msg": "成功,"data": |
test_user_reg_exist | http://115.28.108.130:5000/api/user/login/ | POST | {} | {"name": "張三","password":"123456"} | "{"code": "100001","msg": "失敗,用戶已存在","data": {"name": "張三","password":"e10adc3949ba59abbe56e057f20f883e"}}" |
Excel讀取方法:
Python咱們使用三方庫xlrd來讀取Excel服務器
安裝方法: pip install xlrd微信
import xlrd wb = xlrd.open_workbook("test_user_data.xlsx") # 打開excel sh = wb.sheet_by_name("TestUserLogin") # 按工做簿名定位工做表 print(sh.nrows) # 有效數據行數 print(sh.ncols) # 有效數據列數 print(sh.cell(0, 0).value) # 輸出第一行第一列的值`case_name` print(sh.row_values(0)) # 輸出第1行的全部值(列表格式) # 將數據和標題組裝成字典,使數據更清晰 print(dict(zip(sh.row_values(0), sh.row_values(1)))) # 遍歷excel,打印全部的數據 for i in range(sh.nrows): print(sh.row_values(i))
結果:網絡
3 5 case_name ['case_name', 'url', 'method', 'data', 'expect_res'] {'case_name': 'test_user_login_normal', 'url': 'http://115.28.108.130:5000/api/user/login/', 'method': 'POST', 'data': '{"name": "張三","password":"123456"}', 'expect_res': '<h1>登陸成功</h1>'} ['case_name', 'url', 'method', 'data', 'expect_res'] ['test_user_login_normal', 'http://115.28.108.130:5000/api/user/login/', 'POST', '{"name": "張三","password":"123456"}', '<h1>登陸成功</h1>'] ['test_user_login_password_wrong', 'http://115.28.108.130:5000/api/user/login/', 'POST', '{"name": "張三","password":"1234567"}', '<h1>失敗,用戶不存在</h1>']
封裝讀取excel操做:
新建read_excel.py
咱們的目的是獲取某條用例的數據,須要3個參數,excel數據文件名(data_file),工做簿名(sheet),用例名(case_name)
若是咱們只封裝一個函數,每次調用(每條用例)都要打開一次excel並遍歷一次,這樣效率比較低。
咱們能夠拆分紅兩個函數,一個函數excel_to_list(data_file, sheet)
,一次獲取一個工做表的全部數據,另外一個函數get_test_data(data_list, case_name)
從全部數據中去查找到該條用例的數據。
import xlrd def excel_to_list(data_file, sheet): data_list = [] # 新建個空列表,來乘裝全部的數據 wb = xlrd.open_workbook(data_file) # 打開excel sh = wb.sheet_by_name(sheet) # 獲取工做簿 header = sh.row_values(0) # 獲取標題行數據 for i in range(1, sh.nrows): # 跳過標題行,從第二行開始取數據 d = dict(zip(header, sh.row_values(i))) # 將標題和每行數據組裝成字典 data_list.append(d) return data_list # 列表嵌套字典格式,每一個元素是一個字典 def get_test_data(data_list, case_name): for case_data in data_list: if case_name == case_data['case_name']: # 若是字典數據中case_name與參數一致 return case_data # 若是查詢不到會返回None if __name__ == '__main__': # 測試一下本身的代碼 data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin") # 讀取excel,TestUserLogin工做簿的全部數據 case_data = get_test_data(data_list, 'test_user_login_normal') # 查找用例'test_user_login_normal'的數據 print(case_data)
輸出結果:
{'case_name': 'test_user_login_normal', 'url': 'http://115.28.108.130:5000/api/user/login/', 'method': 'POST', 'data': '{"name": "張三","password":"123456"}', 'expect_res': '<h1>登陸成功</h1>'}
用例中使用方法
test_user_login.py 部分
import unittest import requests from read_excel import * # 導入read_excel中的方法 import json # 用來轉化excel中的json字符串爲字典 class TestUserLogin(unittest.TestCase): @classmethod def setUpClass(cls): # 整個測試類只執行一次 cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin") # 讀取該測試類全部用例數據 # cls.data_list 同 self.data_list 都是該類的公共屬性 def test_user_login_normal(self): case_data = get_test_data(self.data_list, 'test_user_login_normal') # 從數據列表中查找到該用例數據 if not case_data: # 有可能爲None print("用例數據不存在") url = case_data.get('url') # 從字典中取數據,excel中的標題也必須是小寫url data = case_data.get('data') # 注意字符串格式,須要用json.loads()轉化爲字典格式 expect_res = case_data.get('expect_res') # 指望數據 res = requests.post(url=url, data=json.loads(data)) # 表單請求,數據轉爲字典格式 self.assertEqual(res.text, expect_res) # 改成assertEqual斷言 if __name__ == '__main__': # 非必要,用於測試咱們的代碼 unittest.main(verbosity=2)
test_user_reg.py部分
import unittest import requests from db import * from read_excel import * import json class TestUserReg(unittest.TestCase): @classmethod def setUpClass(cls): cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserReg") # 讀取TestUserReg工做簿的全部數據 def test_user_reg_normal(self): case_data = get_test_data(self.data_list, 'test_user_reg_normal') if not case_data: print("用例數據不存在") url = case_data.get('url') data = json.loads(case_data.get('data')) # 轉爲字典,須要取裏面的name進行數據庫檢查 expect_res = json.loads(case_data.get('expect_res')) # 轉爲字典,斷言時直接斷言兩個字典是否相等 name = data.get("name") # 范冰冰 # 環境檢查 if check_user(name): del_user(name) # 發送請求 res = requests.post(url=url, json=data) # 用data=data 傳字符串也能夠 # 響應斷言(總體斷言) self.assertDictEqual(res.json(), expect_res) # 數據庫斷言 self.assertTrue(check_user(name)) # 環境清理(因爲註冊接口向數據庫寫入了用戶信息) del_user(name) if __name__ == '__main__': # 非必要,用於測試咱們的代碼 unittest.main(verbosity=2)
新建config.py文件
import logging logging.basicConfig(level=logging.DEBUG, # log level format='[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s', # log格式 datefmt='%Y-%m-%d %H:%M:%S', # 日期格式 filename='log.txt', # 日誌輸出文件 filemode='a') # 追加模式 if __name__ == '__main__': logging.info("hello")
運行後在當前目錄下生成log.txt
,內容以下:
[2018-09-11 18:08:17] INFO [<module>: config.py, 38] hello
Log Level:
優先級 CRITICAL > ERROR > WARNING > INFO > DEBUG
指定level = logging.DEBUG
全部等級大於等於DEBUG的信息都會輸出
若指定level = logging.ERROR
WARNING,INFO,DEBUG小於設置級別的信息不會輸出
日誌格式:
項目使用log
將全部print改成log,如db.py
部分
import pymysql from config import * # 封裝數據庫查詢操做 def query_db(sql): conn = get_db_conn() cur = conn.cursor() logging.debug(sql) # 輸出執行的sql cur.execute(sql) conn.commit() result = cur.fetchall() logging.debug(result) # 輸出查詢結果 cur.close() conn.close() return result # 封裝更改數據庫操做 def change_db(sql): conn = get_db_conn() cur = conn.cursor() logging.debug(sql) # 輸出執行的sql try: cur.execute(sql) conn.commit() except Exception as e: conn.rollback() logging.error(str(e)) # 輸出錯誤信息 finally: cur.close() conn.close()
用例中使用
import unittest import requests from read_excel import * # 導入read_excel中的方法 import json # 用來轉化excel中的json字符串爲字典 from config import * class TestUserLogin(unittest.TestCase): @classmethod def setUpClass(cls): # 整個測試類只執行一次 cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin") # 讀取該測試類全部用例數據 # cls.data_list 同 self.data_list 都是該類的公共屬性 def test_user_login_normal(self): case_data = get_test_data(self.data_list, 'test_user_login_normal') # 從數據列表中查找到該用例數據 if not case_data: # 有可能爲None logging.error("用例數據不存在") url = case_data.get('url') # excel中的標題也必須是小寫url data = case_data.get('data') # 注意字符串格式,須要用json.loads()轉化爲字典格式 expect_res = case_data.get('expect_res') # 指望數據 res = requests.post(url=url, data=json.loads(data)) # 表單請求,數據轉爲字典格式 logging.info("測試用例:{}".format('test_user_login_normal')) logging.info("url:{}".format(url)) logging.info("請求參數:{}".format(data)) logging.info("指望結果:{}".format(expect_res)) logging.info("實際結果:{}".format(res.text) self.assertEqual(res.text, expect_res) # 斷言 if __name__ == '__main__': unittest.main(verbosity=2)
項目下log.txt
輸出結果:
[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 8] 測試用例:test_user_login_normal [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 9] url:http://115.28.108.130:5000/api/user/login/ [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 10] 請求參數:{"name": "張三","password":"123456"} [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 11] 指望結果:<h1>登陸成功</h1> [2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 12] 實際結果:<h1>登陸成功</h1>
由於每一個用例都要輸出不少log信息,咱們封裝一個case_log
的函數
項目下新建case_log.py
from config import * import json def log_case_info(case_name, url, data, expect_res, res_text): if isinstance(data,dict): data = json.dumps(data, ensure_ascii=False) # 若是data是字典格式,轉化爲字符串 logging.info("測試用例:{}".format(case_name)) logging.info("url:{}".format(url)) logging.info("請求參數:{}".format(data)) logging.info("指望結果:{}".format(expect_res)) logging.info("實際結果:{}".format(res_text)
簡化後的用例log輸出
import unittest import requests from read_excel import * import json from config import * from case_log import log_case_info # 導入方法 class TestUserLogin(unittest.TestCase): @classmethod def setUpClass(cls): cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin") def test_user_login_normal(self): case_data = get_test_data(self.data_list, 'test_user_login_normal') if not case_data: logging.error("用例數據不存在") url = case_data.get('url') data = case_data.get('data') expect_res = case_data.get('expect_res') res = requests.post(url=url, data=json.loads(data)) log_case_info('test_user_login_normal', url, data, expect_res, res_text) # 輸出用例log信息 self.assertEqual(res.text, expect_res) if __name__ == '__main__': unittest.main(verbosity=2)
在生成報告後咱們但願框架能自動把報告發送到咱們的郵箱中。和outlook,foxmail等郵件客戶端同樣,Python中發送郵件須要經過Email的smtp服務發送。
首先須要確認用來發送郵件的郵箱是否啓用了smtp服務
發送郵件分3步
import smtplib # 用於創建smtp鏈接 from email.mime.text import MIMEText # 郵件須要專門的MIME格式 # 1. 編寫郵件內容(Email郵件須要專門的MIME格式) msg = MIMEText('this is a test email', 'plain', 'utf-8') # plain指普通文本格式郵件內容 # 2. 組裝Email頭(發件人,收件人,主題) msg['From'] = 'test_results@sina.com' # 發件人 msg['To'] = '2375247815@qq.com' # 收件人 msg['Subject'] = 'Api Test Report' # 郵件主題 # 3. 鏈接smtp服務器併發送郵件 smtp = smtplib.SMTP_SSL('smtp.sina.com') # smtp服務器地址 使用SSL模式 smtp.login('本身的郵箱地址', '本身的郵箱密碼') # 用戶名和密碼 smtp.sendmail("接收郵件地址1", "接收郵件地址2", msg.as_string()) smtp.quit()
中文郵件主題、HTML郵件內容,及附件
import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # 混合MIME格式,支持上傳附件 from email.header import Header # 用於使用中文郵件主題 # 1. 編寫郵件內容 with open('report.html', encoding='utf-8') as f: # 打開html報告 email_body = f.read() # 讀取報告內容 msg = MIMEMultipart() # 混合MIME格式 msg.attach(MIMEText(email_body, 'html', 'utf-8')) # 添加html格式郵件正文(會丟失css格式) # 2. 組裝Email頭(發件人,收件人,主題) msg['From'] = 'test_results@sina.com' # 發件人 msg['To'] = '2375247815@qq.com' # 收件人 msg['Subject'] = Header('接口測試報告', 'utf-8') # 中文郵件主題,指定utf-8編碼 # 3. 構造附件1,傳送當前目錄下的 test.txt 文件 att1 = MIMEText(open('report.html', 'rb').read(), 'base64', 'utf-8') # 二進制格式打開 att1["Content-Type"] = 'application/octet-stream' att1["Content-Disposition"] = 'attachment; filename="report.html"' # filename爲郵件中附件顯示的名字 msg.attach(att1) # 4. 鏈接smtp服務器併發送郵件 smtp = smtplib.SMTP_SSL('smtp.sina.com') # smtp服務器地址 使用SSL模式 smtp.login('test_results@sina.com', 'hanzhichao123') # 用戶名和密碼 smtp.sendmail("test_results@sina.com", "2375247815@qq.com", msg.as_string()) smtp.sendmail("test_results@sina.com", "superhin@126.com", msg.as_string()) # 發送給另外一個郵箱 smtp.quit()
封裝發送郵件方法
import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # 混合MIME格式,支持上傳附件 from email.header import Header # 用於使用中文郵件主題 from config import * def send_email(report_file): msg = MIMEMultipart() # 混合MIME格式 msg.attach(MIMEText(open(report_file, encoding='utf-8').read(), 'html', 'utf-8')) # 添加html格式郵件正文(會丟失css格式) msg['From'] = 'test_results@sina.com' # 發件人 msg['To'] = '2375247815@qq.com' # 收件人 msg['Subject'] = Header('接口測試報告', 'utf-8') # 中文郵件主題,指定utf-8編碼 att1 = MIMEText(open(report_file, 'rb').read(), 'base64', 'utf-8') # 二進制格式打開 att1["Content-Type"] = 'application/octet-stream' att1["Content-Disposition"] = 'attachment; filename="report.html"' # filename爲郵件中附件顯示的名字 msg.attach(att1) try: smtp = smtplib.SMTP_SSL('smtp.sina.com') # smtp服務器地址 使用SSL模式 smtp.login('test_results@sina.com', 'hanzhichao123') # 用戶名和密碼 smtp.sendmail("test_results@sina.com", "2375247815@qq.com", msg.as_string()) smtp.sendmail("test_results@sina.com", "superhin@126.com", msg.as_string()) # 發送給另外一個郵箱 logging.info("郵件發送完成!") except Exception as e: logging.error(str(e)) finally: smtp.quit()
run_all.py中結束後發送郵件
import unittest from HTMLTestReportCN import HTMLTestRunner from config import * from send_email import send_email logging.info("====================== 測試開始 =======================") suite = unittest.defaultTestLoader.discover("./") with open("report.html", 'wb') as f: # 改成with open 格式 HTMLTestRunner(stream=f, title="Api Test", description="測試描述", tester="卡卡").run(suite) send_email('report.html') # 發送郵件 logging.info("======================= 測試結束 =======================")
和項目的log配置同樣,數據庫服務器地址,郵件服務地址咱們通常放到配置文件config.py
中
import logging import os # 項目路徑 prj_path = os.path.dirname(os.path.abspath(__file__)) # 當前文件的絕對路徑的上一級,__file__指當前文件 data_path = prj_path # 數據目錄,暫時在項目目錄下 test_path = prj_path # 用例目錄,暫時在項目目錄下 log_file = os.path.join(prj_path, 'log.txt') # 也能夠天天生成新的日誌文件 report_file = os.path.join(prj_path, 'report.html') # 也能夠每次生成新的報告 # log配置 logging.basicConfig(level=logging.DEBUG, # log level format='[%(asctime)s] %(levelname)s [%(funcName)s: %(filename)s, %(lineno)d] %(message)s', # log格式 datefmt='%Y-%m-%d %H:%M:%S', # 日期格式 filename=log_file, # 日誌輸出文件 filemode='a') # 追加模式 # 數據庫配置 db_host = '127.0.0.1' # 本身的服務器地址 db_port = 3306 db_user = 'test' db_passwd = '123456' db = 'api_test' # 郵件配置 smtp_server = 'smtp.sina.com' smtp_user = 'test_results@sina.com' smtp_password = 'hanzhichao123' sender = smtp_user # 發件人 receiver = '2375247815@qq.com' # 收件人 subject = '接口測試報告' # 郵件主題
修改db.py
,send_email.py
,run_all.py
等對配置文件的引用
db.py
部分
import pymysql from config import * # 獲取鏈接方法 def get_db_conn(): conn = pymysql.connect(host=db_host, # 從配置文件中讀取 port=db_port, user=db_user, passwd=db_passwd, # passwd 不是 password db=db, charset='utf8') # 若是查詢有中文,須要指定測試集編碼
send_email.py
import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import Header from config import * def send_email(report_file): msg = MIMEMultipart() msg.attach(MIMEText(open(report_file, encoding='utf-8').read(), 'html', 'utf-8')) msg['From'] = 'test_results@sina.com' msg['To'] = '2375247815@qq.com' msg['Subject'] = Header(subject, 'utf-8') # 從配置文件中讀取 att1 = MIMEText(open(report_file, 'rb').read(), 'base64', 'utf-8') # 從配置文件中讀取 att1["Content-Type"] = 'application/octet-stream' att1["Content-Disposition"] = 'attachment; filename="{}"'.format(report_file) # 參數化一下report_file msg.attach(att1) try: smtp = smtplib.SMTP_SSL(smtp_server) # 從配置文件中讀取 smtp.login(smtp_user, smtp_password) # 從配置文件中讀取 smtp.sendmail(sender, receiver, msg.as_string()) logging.info("郵件發送完成!") except Exception as e: logging.error(str(e)) finally: smtp.quit()
run_all.py
import unittest from HTMLTestReportCN import HTMLTestRunner from config import * from send_email import send_email logging.info("==================== 測試開始 =======================") suite = unittest.defaultTestLoader.discover(test_path) # 從配置文件中讀取用例路徑 with open(report_file, 'wb') as f: # 從配置文件中讀取 HTMLTestRunner(stream=f, title="Api Test", description="測試描述").run(suite) send_email(report_file) # 從配置文件中讀取 logging.info("==================== 測試結束 =======================")
當前全部文件(配置文件,公共方法,測試用例,數據,報告,log)都在項目根目錄下,隨着用例的增長和功能的補充,文件會愈來愈多,不便於維護和管理,所以咱們要創建不一樣的文件夾,對文件進行分類組織
1.在項目中新建如下文件夾:
__init__.py
,這樣裏面的用例才能讀取到)2.將配置文件config.py
移動到config目錄下,將數據文件test_user_data.xlsx
移動到data目錄下,將公共方法db.py
send_email.py
case_log.py
read_excel.py
HTMLTestReportCN.py
移動到lib目錄下,將測試用例test_user_login.py
test_user_reg.py
移動到test/user目錄下,保留run_all.py
在項目根目錄下,如圖:
3.修改配置文件
config/config.py
部分
import logging import os # 項目路徑 prj_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 當前文件的上一級的上一級目錄(增長一級) data_path = os.path.join(prj_path, 'data') # 數據目錄 test_path = os.path.join(prj_path, 'test') # 用例目錄 log_file = os.path.join(prj_path, 'log', 'log.txt') # 更改路徑到log目錄下 report_file = os.path.join(prj_path, 'report', 'report.html') # 更改路徑到report目錄下
4.修改對配置文件及公共方法的引用
爲避免相對路徑導包出錯的問題,咱們統一把導包搜索路徑(sys.path)提高到項目根目錄下,如
lib/db.py
lib/db.py
部分
import pymysql import sys sys.path.append('..') # 提高一級到項目更目錄下 from config.config import * # 從項目根目錄下導入
測試用例test_user_login.py
部分
import unittest import requests import json import os # 增長了一個os,須要用來組裝路徑 import sys sys.path.append("../..") # 提高2級到項目根目錄下 from config.config import * # 從項目路徑下導入 from lib.read_excel import * # 從項目路徑下導入 from lib.case_log import log_case_info # 從項目路徑下導入 class TestUserLogin(unittest.TestCase): @classmethod def setUpClass(cls): # 整個測試類只執行一次 cls.data_list = excel_to_list(os.path.join(data_path, "test_user_data.xlsx"),"TestUserLogin") # 增長data路徑
run_all.py
import unittest from lib.HTMLTestReportCN import HTMLTestRunner # 修改導入路徑 from config.config import * # 修改導入路徑 from lib.send_email import send_email # 修改導入路徑 logging.info("================================== 測試開始 ==================================") suite = unittest.defaultTestLoader.discover(test_path) # 從配置文件中讀取 with open(report_file, 'wb') as f: # 從配置文件中讀取 HTMLTestRunner(stream=f, title="Api Test", description="測試描述").run(suite) send_email(report_file) # 從配置文件中讀取 logging.info("================================== 測試結束 ==================================")
- 若是同一文件夾下的方法相互引用(如
lib/read_excel.py
假如須要引用lib/db.py
),也須要採用這種從項目路徑下導入的方式run_all.py
直接在項目路徑下,不須要提高sys.path,無需相對導入咱們本身的包時,如read_excel.py
,不須要提高
5.運行run_all.py
,根據log和報告調試代碼,直至全部用例所有經過
源碼下載連接:https://pan.baidu.com/s/1RzwAlUMHwG4FQmeS-yB9rw 密碼:rvq1
此爲北京龍騰育才 Python高級自動化(接口測試部分)授課筆記
課程介紹
想要參加現場(北京)/網絡課程的能夠聯繫做者微信:lockingfree
- 高效學習,快速掌握Python自動化全部領域技能
- 同步快速解決各類問題
- 配套實戰項目練習