Python接口測試實(shí)戰(zhàn)4(上) - 接口測試框架實(shí)戰(zhàn)

簡書已停更付翁,歡迎轉(zhuǎn)到個人博客查看對應(yīng)教程:https://www.cnblogs.com/superhin/p/10339002.html

課程目錄

Python接口測試實(shí)戰(zhàn)1(上)- 接口測試?yán)碚?/a>
Python接口測試實(shí)戰(zhàn)1(下)- 接口測試工具的使用
Python接口測試實(shí)戰(zhàn)2 - 使用Python發(fā)送請求
Python接口測試實(shí)戰(zhàn)3(上)- Python操作數(shù)據(jù)庫
Python接口測試實(shí)戰(zhàn)3(下)- unittest測試框架
Python接口測試實(shí)戰(zhàn)4(上) - 接口測試框架實(shí)戰(zhàn)
Python接口測試實(shí)戰(zhàn)4(下) - 框架完善:用例基類箩张,用例標(biāo)簽小渊,重新運(yùn)行上次失敗用例
Python接口測試實(shí)戰(zhàn)5(上) - Git及Jenkins持續(xù)集成
Python接口測試實(shí)戰(zhàn)5(下) - RESTful、Web Service及Mock Server

更多學(xué)習(xí)資料請加添加作者微信:superz-han獲取

本節(jié)內(nèi)容

  • 數(shù)據(jù)分離 - 從Excel中讀取數(shù)據(jù)
  • 增加log功能
  • 發(fā)送郵件
  • 使用配置文件
  • 框架整理
上節(jié)課的框架雛形

數(shù)據(jù)分離 - 從Excel中讀取數(shù)據(jù)

之前的用例中坏为,數(shù)據(jù)直接寫在代碼文件里,不利于修改和構(gòu)造數(shù)據(jù)
這里我們使用Excel保存測試數(shù)據(jù),實(shí)現(xiàn)代碼和數(shù)據(jù)的分離
新建Excel文件test_user_data.xlsx包含兩個工作簿TestUserLoginTestUserReg逛腿,并復(fù)制到項(xiàng)目根目錄下

test_user_data.xlsx

更新: excel表格中,增加一個headers列,內(nèi)容為json格式, 如下

TestUserLogin

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>

TestUserReg

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": {""name": "范冰冰,"password": "e10adc3949ba59abbe56e057f20f883e"}}"
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)  # 有效數(shù)據(jù)行數(shù)
print(sh.ncols)  # 有效數(shù)據(jù)列數(shù)
print(sh.cell(0, 0).value)  # 輸出第一行第一列的值`case_name`
print(sh.row_values(0))  # 輸出第1行的所有值(列表格式)

# 將數(shù)據(jù)和標(biāo)題組裝成字典单默,使數(shù)據(jù)更清晰
print(dict(zip(sh.row_values(0), sh.row_values(1))))

# 遍歷excel,打印所有的數(shù)據(jù)
for i in range(sh.nrows):
    print(sh.row_values(i))

結(jié)果:

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

封裝讀取excel操作

我們的目的是獲取某條用例的數(shù)據(jù)灵莲,需要3個參數(shù)雕凹,excel數(shù)據(jù)文件名(data_file),工作簿名(sheet),用例名(case_name)
如果我們只封裝一個函數(shù)枚抵,每次調(diào)用(每條用例)都要打開一次excel并遍歷一次线欲,這樣效率比較低。
我們可以拆分成兩個函數(shù)汽摹,一個函數(shù)excel_to_list(data_file, sheet)李丰,一次獲取一個工作表的所有數(shù)據(jù),另一個函數(shù)get_test_data(data_list, case_name)從所有數(shù)據(jù)中去查找到該條用例的數(shù)據(jù)逼泣。

import xlrd

def excel_to_list(data_file, sheet):
    data_list = []  # 新建個空列表趴泌,來乘裝所有的數(shù)據(jù)
    wb = xlrd.open_workbook(data_file)  # 打開excel
    sh = wb.sheet_by_name(sheet)  # 獲取工作簿
    header = sh.row_values(0)  # 獲取標(biāo)題行數(shù)據(jù)
    for i in range(1, sh.nrows):  # 跳過標(biāo)題行,從第二行開始取數(shù)據(jù)
        d = dict(zip(header, sh.row_values(i)))  # 將標(biāo)題和每行數(shù)據(jù)組裝成字典
        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']:  # 如果字典數(shù)據(jù)中case_name與參數(shù)一致
            return case_data
            # 如果查詢不到會返回None

if __name__ == '__main__':   # 測試一下自己的代碼
    data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin")  # 讀取excel嗜憔,TestUserLogin工作簿的所有數(shù)據(jù)
    case_data = get_test_data(data_list, 'test_user_login_normal')  # 查找用例'test_user_login_normal'的數(shù)據(jù)
    print(case_data)

輸出結(jié)果:

{'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 *  # 導(dǎo)入read_excel中的方法
import json  # 用來轉(zhuǎn)化excel中的json字符串為字典

class TestUserLogin(unittest.TestCase):
    @classmethod
    def setUpClass(cls):   # 整個測試類只執(zhí)行一次
        cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin")  # 讀取該測試類所有用例數(shù)據(jù)
        # cls.data_list 同 self.data_list 都是該類的公共屬性

    def test_user_login_normal(self):
        case_data = get_test_data(self.data_list, 'test_user_login_normal')   # 從數(shù)據(jù)列表中查找到該用例數(shù)據(jù)
        if not case_data:   # 有可能為None
            print("用例數(shù)據(jù)不存在")
        url = case_data.get('url')   # 從字典中取數(shù)據(jù),excel中的標(biāo)題也必須是小寫url
        data = case_data.get('data')  # 注意字符串格式氏仗,需要用json.loads()轉(zhuǎn)化為字典格式
        expect_res = case_data.get('expect_res')  # 期望數(shù)據(jù)

        res = requests.post(url=url, data=json.loads(data))  # 表單請求吉捶,數(shù)據(jù)轉(zhuǎn)為字典格式
        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工作簿的所有數(shù)據(jù)

    def test_user_reg_normal(self):
        case_data = get_test_data(self.data_list, 'test_user_reg_normal')
        if not case_data:
            print("用例數(shù)據(jù)不存在")
        url = case_data.get('url')
        data = json.loads(case_data.get('data'))  # 轉(zhuǎn)為字典皆尔,需要取里面的name進(jìn)行數(shù)據(jù)庫檢查
        expect_res = json.loads(case_data.get('expect_res'))  # 轉(zhuǎn)為字典呐舔,斷言時直接斷言兩個字典是否相等
        name = data.get("name")  # 范冰冰

        # 環(huán)境檢查
        if check_user(name):
            del_user(name)
        # 發(fā)送請求
        res = requests.post(url=url, json=data)  # 用data=data 傳字符串也可以
        # 響應(yīng)斷言(整體斷言)
        self.assertDictEqual(res.json(), expect_res)
        # 數(shù)據(jù)庫斷言
        self.assertTrue(check_user(name))
        # 環(huán)境清理(由于注冊接口向數(shù)據(jù)庫寫入了用戶信息)
        del_user(name)

if __name__ == '__main__':    # 非必要,用于測試我們的代碼
    unittest.main(verbosity=2)  

增加log功能

新建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")

運(yùn)行后在當(dāng)前目錄下生成log.txt慷蠕,內(nèi)容如下:

[2018-09-11 18:08:17] INFO [<module>: config.py, 38] hello

Log Level:

  • CRITICAL: 用于輸出嚴(yán)重錯誤信息
  • ERROR: 用于輸出錯誤信息
  • WARNING: 用于輸出警示信息
  • INFO: 用于輸出一些提升信息
  • DEBUG: 用于輸出一些調(diào)試信息

優(yōu)先級 CRITICAL > ERROR > WARNING > INFO > DEBUG
指定level = logging.DEBUG所有等級大于等于DEBUG的信息都會輸出
若指定level = logging.ERROR WARNING,INFO,DEBUG小于設(shè)置級別的信息不會輸出

日志格式:

  • %(levelno)s: 打印日志級別的數(shù)值
  • %(levelname)s: 打印日志級別名稱
  • %(pathname)s: 打印當(dāng)前執(zhí)行程序的路徑珊拼,其實(shí)就是sys.argv[0]
  • %(filename)s: 打印當(dāng)前執(zhí)行程序名
  • %(funcName)s: 打印日志的當(dāng)前函數(shù)
  • %(lineno)d: 打印日志的當(dāng)前行號
  • %(asctime)s: 打印日志的時間
  • %(thread)d: 打印線程ID
  • %(threadName)s: 打印線程名稱
  • %(process)d: 打印進(jìn)程ID
  • %(message)s: 打印日志信息
    項(xiàng)目使用log
    將所有print改為log,如db.py 部分
import pymysql
from config import *

# 封裝數(shù)據(jù)庫查詢操作
def query_db(sql):
    conn = get_db_conn()
    cur = conn.cursor()  
    logging.debug(sql)    # 輸出執(zhí)行的sql
    cur.execute(sql)
    conn.commit()
    result = cur.fetchall() 
    logging.debug(result)  # 輸出查詢結(jié)果
    cur.close() 
    conn.close() 
    return result 

# 封裝更改數(shù)據(jù)庫操作
def change_db(sql):
    conn = get_db_conn() 
    cur = conn.cursor()
    logging.debug(sql)  # 輸出執(zhí)行的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 *  # 導(dǎo)入read_excel中的方法
import json  # 用來轉(zhuǎn)化excel中的json字符串為字典
from config import *

class TestUserLogin(unittest.TestCase):
    @classmethod
    def setUpClass(cls):   # 整個測試類只執(zhí)行一次
        cls.data_list = excel_to_list("test_user_data.xlsx", "TestUserLogin")  # 讀取該測試類所有用例數(shù)據(jù)
        # cls.data_list 同 self.data_list 都是該類的公共屬性

    def test_user_login_normal(self):
        case_data = get_test_data(self.data_list, 'test_user_login_normal')   # 從數(shù)據(jù)列表中查找到該用例數(shù)據(jù)
        if not case_data:   # 有可能為None
            logging.error("用例數(shù)據(jù)不存在")
        url = case_data.get('url')   # excel中的標(biāo)題也必須是小寫url
        data = case_data.get('data')  # 注意字符串格式流炕,需要用json.loads()轉(zhuǎn)化為字典格式
        expect_res = case_data.get('expect_res')  # 期望數(shù)據(jù)

        res = requests.post(url=url, data=json.loads(data))  # 表單請求澎现,數(shù)據(jù)轉(zhuǎn)為字典格式
        logging.info("測試用例:{}".format('test_user_login_normal'))
        logging.info("url:{}".format(url))
        logging.info("請求參數(shù):{}".format(data))
        logging.info("期望結(jié)果:{}".format(expect_res))
        logging.info("實(shí)際結(jié)果:{}".format(res.text)
        self.assertEqual(res.text, expect_res)  # 斷言

if __name__ == '__main__':
    unittest.main(verbosity=2)

項(xiàng)目下log.txt輸出結(jié)果:

[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] 請求參數(shù):{"name": "張三","password":"123456"}
[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 11] 期望結(jié)果:<h1>登錄成功</h1>
[2018-09-13 10:34:49] INFO [log_case_info: case_log.py, 12] 實(shí)際結(jié)果:<h1>登錄成功</h1>

因?yàn)槊總€用例都要輸出很多l(xiāng)og信息,我們封裝一個case_log的函數(shù)
項(xiàng)目下新建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是字典格式浪感,轉(zhuǎn)化為字符串
    logging.info("測試用例:{}".format(case_name))
    logging.info("url:{}".format(url))
    logging.info("請求參數(shù):{}".format(data))
    logging.info("期望結(jié)果:{}".format(expect_res))
    logging.info("實(shí)際結(jié)果:{}".format(res_text)

簡化后的用例log輸出

import unittest
import requests
from read_excel import *  
import json
from config import *
from case_log import log_case_info  # 導(dǎo)入方法

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("用例數(shù)據(jù)不存在")
        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)

發(fā)送郵件

在生成報告后我們希望框架能自動把報告發(fā)送到我們的郵箱中昔头。和outlook,foxmail等郵件客戶端一樣影兽,Python中發(fā)送郵件需要通過Email的smtp服務(wù)發(fā)送揭斧。

首先需要確認(rèn)用來發(fā)送郵件的郵箱是否啟用了smtp服務(wù)

發(fā)送郵件分3步

  1. 編寫郵件內(nèi)容(Email郵件需要專門的MIME格式)
  2. 組裝Email頭(發(fā)件人,收件人峻堰,主題)
  3. 連接smtp服務(wù)器并發(fā)送郵件
import smtplib  # 用于建立smtp連接
from email.mime.text import MIMEText  # 郵件需要專門的MIME格式

# 1. 編寫郵件內(nèi)容(Email郵件需要專門的MIME格式)
msg = MIMEText('this is a test email', 'plain', 'utf-8')  # plain指普通文本格式郵件內(nèi)容

# 2. 組裝Email頭(發(fā)件人讹开,收件人,主題)
msg['From'] = 'test_results@sina.com'  # 發(fā)件人
msg['To'] = '2375247815@qq.com'  # 收件人
msg['Subject'] = 'Api Test Report'  # 郵件主題

# 3. 連接smtp服務(wù)器并發(fā)送郵件
smtp = smtplib.SMTP_SSL('smtp.sina.com')  # smtp服務(wù)器地址 使用SSL模式
smtp.login('自己的郵箱地址', '自己的郵箱密碼')  # 用戶名和密碼
smtp.sendmail("接收郵件地址1", "接收郵件地址2", msg.as_string())
smtp.quit()

中文郵件主題捐名、HTML郵件內(nèi)容旦万,及附件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart  # 混合MIME格式,支持上傳附件
from email.header import Header  # 用于使用中文郵件主題

# 1.  編寫郵件內(nèi)容
with open('report.html', encoding='utf-8') as f:  # 打開html報告
    email_body = f.read()  # 讀取報告內(nèi)容

msg = MIMEMultipart()  # 混合MIME格式
msg.attach(MIMEText(email_body, 'html', 'utf-8'))  # 添加html格式郵件正文(會丟失css格式)

# 2. 組裝Email頭(發(fā)件人镶蹋,收件人成艘,主題)
msg['From'] = 'test_results@sina.com'  # 發(fā)件人
msg['To'] = '2375247815@qq.com'  # 收件人
msg['Subject'] = Header('接口測試報告', 'utf-8')  # 中文郵件主題赏半,指定utf-8編碼

# 3. 構(gòu)造附件1,傳送當(dāng)前目錄下的 test.txt 文件
att1 = MIMEText(open('report.html', 'rb').read(), 'base64', 'utf-8')  # 二進(jìn)制格式打開
att1["Content-Type"] = 'application/octet-stream'
att1["Content-Disposition"] = 'attachment; filename="report.html"'  # filename為郵件中附件顯示的名字
msg.attach(att1)

# 4. 連接smtp服務(wù)器并發(fā)送郵件
smtp = smtplib.SMTP_SSL('smtp.sina.com')  # smtp服務(wù)器地址 使用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())  # 發(fā)送給另一個郵箱
smtp.quit()

封裝發(fā)送郵件方法

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'  # 發(fā)件人
    msg['To'] = '2375247815@qq.com'  # 收件人
    msg['Subject'] = Header('接口測試報告', 'utf-8')  # 中文郵件主題断箫,指定utf-8編碼

    att1 = MIMEText(open(report_file, 'rb').read(), 'base64', 'utf-8')  # 二進(jìn)制格式打開
    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服務(wù)器地址 使用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())  # 發(fā)送給另一個郵箱
        logging.info("郵件發(fā)送完成!")
    except Exception as e:
        logging.error(str(e))
    finally:
        smtp.quit()

run_all.py中結(jié)束后發(fā)送郵件

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')  # 發(fā)送郵件
logging.info("================================== 測試結(jié)束 ==================================")
測試郵件

使用配置文件

和項(xiàng)目的log配置一樣秋冰,數(shù)據(jù)庫服務(wù)器地址仲义,郵件服務(wù)地址我們一般放到配置文件config.py

import logging
import os

# 項(xiàng)目路徑
prj_path = os.path.dirname(os.path.abspath(__file__))  # 當(dāng)前文件的絕對路徑的上一級,__file__指當(dāng)前文件

data_path = prj_path  # 數(shù)據(jù)目錄剑勾,暫時在項(xiàng)目目錄下
test_path = prj_path  # 用例目錄埃撵,暫時在項(xiàng)目目錄下

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')  # 追加模式


# 數(shù)據(jù)庫配置
db_host = '127.0.0.1'   # 自己的服務(wù)器地址
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  # 發(fā)件人
receiver = '2375247815@qq.com'  # 收件人
subject = '接口測試報告'  # 郵件主題

修改db.py,send_email.pyrun_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)  # 參數(shù)化一下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("郵件發(fā)送完成暂刘!")
    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("================================== 測試結(jié)束 ==================================")

框架整理

所有文件都在項(xiàng)目根目錄

當(dāng)前所有文件(配置文件,公共方法捂刺,測試用例鸳惯,數(shù)據(jù),報告叠萍,log)都在項(xiàng)目根目錄下,隨著用例的增加和功能的補(bǔ)充绪商,文件會越來越多苛谷,不便于維護(hù)和管理,因此我們要建立不同的文件夾格郁,對文件進(jìn)行分類組織

  1. 在項(xiàng)目中新建以下文件夾:
  • config: 存放項(xiàng)目配置文件
  • data: 存放用例數(shù)據(jù)文件
  • lib: 公共方法庫
  • log: 存放日志文件
  • report: 存放報告文件
  • test: 存放測試用例
    • user: 存放user模塊用例 (模塊下要有__init__.py腹殿,這樣里面的用例才能讀取到)
  1. 將配置文件config.py移動到config目錄下,將數(shù)據(jù)文件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在項(xiàng)目根目錄下决采,如圖:
    整理后的項(xiàng)目結(jié)構(gòu)
  2. 修改配置文件
    config/config.py部分
import logging
import os

# 項(xiàng)目路徑
prj_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))  # 當(dāng)前文件的上一級的上一級目錄(增加一級)

data_path = os.path.join(prj_path, 'data')  # 數(shù)據(jù)目錄
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目錄下
  1. 修改對配置文件及公共方法的引用

為避免相對路徑導(dǎo)包出錯的問題自沧,我們統(tǒng)一把導(dǎo)包搜索路徑(sys.path)提升到項(xiàng)目根目錄下,如lib/db.py

lib/db.py 部分

import pymysql
import sys
sys.path.append('..')  # 提升一級到項(xiàng)目更目錄下
from config.config import *  # 從項(xiàng)目根目錄下導(dǎo)入

測試用例test_user_login.py部分

import unittest
import requests
import json
import os  # 增加了一個os树瞭,需要用來組裝路徑
import sys
sys.path.append("../..")  # 提升2級到項(xiàng)目根目錄下
from config.config import *  # 從項(xiàng)目路徑下導(dǎo)入
from lib.read_excel import *  # 從項(xiàng)目路徑下導(dǎo)入
from lib.case_log import log_case_info  # 從項(xiàng)目路徑下導(dǎo)入

class TestUserLogin(unittest.TestCase):
    @classmethod
    def setUpClass(cls):   # 整個測試類只執(zhí)行一次
        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  # 修改導(dǎo)入路徑
from config.config import *  # 修改導(dǎo)入路徑
from lib.send_email import send_email  # 修改導(dǎo)入路徑

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("================================== 測試結(jié)束 ==================================")

  1. 如果同一文件夾下的方法相互引用(如lib/read_excel.py假如需要引用lib/db.py)拇厢,也需要采用這種從項(xiàng)目路徑下導(dǎo)入的方式
  2. run_all.py直接在項(xiàng)目路徑下,不需要提升sys.path晒喷,無需相對導(dǎo)入我們自己的包時孝偎,如read_excel.py,不需要提升
  1. 運(yùn)行run_all.py凉敲,根據(jù)log和報告調(diào)試代碼衣盾,直至所有用例全部通過

源碼下載鏈接:https://pan.baidu.com/s/1RzwAlUMHwG4FQmeS-yB9rw 密碼:rvq1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寺旺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子势决,更是在濱河造成了極大的恐慌阻塑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徽龟,死亡現(xiàn)場離奇詭異叮姑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)据悔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門传透,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人极颓,你說我怎么就攤上這事朱盐。” “怎么了菠隆?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵兵琳,是天一觀的道長。 經(jīng)常有香客問我骇径,道長躯肌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任破衔,我火速辦了婚禮清女,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晰筛。我一直安慰自己嫡丙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布读第。 她就那樣靜靜地躺著曙博,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怜瞒。 梳的紋絲不亂的頭發(fā)上父泳,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音盼砍,去河邊找鬼尘吗。 笑死,一個胖子當(dāng)著我的面吹牛浇坐,可吹牛的內(nèi)容都是我干的睬捶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼近刘,長吁一口氣:“原來是場噩夢啊……” “哼擒贸!你這毒婦竟也來了臀晃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤介劫,失蹤者是張志新(化名)和其女友劉穎徽惋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體座韵,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡险绘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了誉碴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宦棺。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖黔帕,靈堂內(nèi)的尸體忽然破棺而出代咸,到底是詐尸還是另有隱情,我是刑警寧澤成黄,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布呐芥,位于F島的核電站,受9級特大地震影響奋岁,放射性物質(zhì)發(fā)生泄漏思瘟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一闻伶、第九天 我趴在偏房一處隱蔽的房頂上張望潮太。 院中可真熱鬧,春花似錦虾攻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至澡为,卻和暖如春漂坏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媒至。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工顶别, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拒啰。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓驯绎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谋旦。 傳聞我的和親對象是個殘疾皇子剩失,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容