Python3+Pytest 接口自動化測試全方案設(shè)計與開發(fā)(部分筆記)

課程鏈接: https://www.boxuegu.com/promote/detail-1484.html

第一部分: 項目介紹及框架規(guī)劃

接口測試框架流程

image.png

接口測試代碼結(jié)構(gòu)

image.png

第二部分:接口自動化框架編寫

1. Requests 的封裝

import requests
from utils.LogUtil import my_log
#1、創(chuàng)建封裝get方法
def requests_get(url,headers):
#2、發(fā)送requests get請求
    r = requests.get(url,headers = headers)
#3、獲取結(jié)果相應(yīng)內(nèi)容
    code = r.status_code
    try:
        body = r.json()
    except Exception as e:
        body = r.text
#4、內(nèi)容存到字典
    res = dict()
    res["code"] = code
    res["body"] = body
#5、字典返回
    return res

#post方法封裝
#1、創(chuàng)建post方法
def requests_post(url,json=None,headers=None):
#2、發(fā)送post請求
    r= requests.post(url,json=json,headers=headers)
#3、獲取結(jié)果內(nèi)容
    code = r.status_code
    try:
        body = r.json()
    except Exception as e:
        body = r.text
#4、內(nèi)容存到字典
    res = dict()
    res["code"] = code
    res["body"] = body
#5、字典返回
    return res

#重構(gòu)
#1、創(chuàng)建類
class Request:
#2、定義公共方法
    def __init__(self):
        self.log = my_log("Requests")
    def requests_api(self,url,data = None,json=None,headers=None,cookies=None,method="get"):

        if method =="get":
            #get請求
            self.log.debug("發(fā)送get請求")
            r = requests.get(url, data = data, json=json, headers=headers,cookies=cookies)
        elif method == "post":
            #post請求
            self.log.debug("發(fā)送post請求")
            r = requests.post(url,data = data,  json=json, headers=headers,cookies=cookies)

        #2. 重復(fù)的內(nèi)容,復(fù)制進來
        #獲取結(jié)果內(nèi)容
        code = r.status_code
        try:
            body = r.json()
        except Exception as e:
            body = r.text
        #內(nèi)容存到字典
        res = dict()
        res["code"] = code
        res["body"] = body
        #字典返回
        return res

#3、重構(gòu)get/post方法
    #get
    #1、定義方法
    def get(self,url,**kwargs):
    #2、定義參數(shù)
        #url,json,headers,cookies,method
    #3、調(diào)用公共方法
        return self.requests_api(url,method="get",**kwargs)

    def post(self,url,**kwargs):
    #2、定義參數(shù)
        #url,json,headers,cookies,method
    #3、調(diào)用公共方法
        return self.requests_api(url,method="post",**kwargs)

2.Yaml 文件的封裝

import os
import yaml
#1、創(chuàng)建類
class YamlReader:
#2、初始化,文件是否存在
    def __init__(self,yamlf):
        if os.path.exists(yamlf):
            self.yamlf = yamlf
        else:
            raise FileNotFoundError("文件不存在")
        self._data = None
        self._data_all = None
#3、yaml讀取
    #單個文檔讀取
    def data(self):
        #第一次調(diào)用data,讀取yaml文檔,如果不是,直接返回之前保存的數(shù)據(jù)
        if not self._data:
            with open(self.yamlf,"rb") as f:
                self._data = yaml.safe_load(f)
        return self._data
    #多個文檔讀取
    def data_all(self):
        #第一次調(diào)用data,讀取yaml文檔,如果不是,直接返回之前保存的數(shù)據(jù)
        if not self._data_all:
            with open(self.yamlf,"rb") as f:
                self._data_all = list(yaml.safe_load_all(f))
        return self._data_all

3.日志文件的封裝

import logging
from config import Conf
import datetime,os
from config.Conf import ConfigYaml
#定義日志級別的映射
log_l = {
    "info": logging.INFO,
    "debug": logging.DEBUG,
    "warning": logging.WARNING,
    "error": logging.ERROR

}
#1、創(chuàng)建類
class Logger:
#2、定義參數(shù)
    #輸出文件名稱,Loggername,日志級別
    def __init__(self,log_file,log_name,log_level):
        self.log_file = log_file #擴展名 配置文件
        self.log_name = log_name #參數(shù)
        self.log_level = log_level # 配置文件
#3、編寫輸出控制臺或文件
        # 設(shè)置logger名稱
        self.logger = logging.getLogger(self.log_name)
        # 設(shè)置log級別
        self.logger.setLevel(log_l[self.log_level]) #logging.INFO
        #判斷handlers是否存在
        if not self.logger.handlers:
            # 輸出控制臺
            fh_stream = logging.StreamHandler()
            fh_stream.setLevel(log_l[self.log_level])
            formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s ')
            fh_stream.setFormatter(formatter)
            # 寫入文件
            fh_file = logging.FileHandler(self.log_file)
            fh_file.setLevel(log_l[self.log_level])
            fh_file.setFormatter(formatter)

            # 添加handler
            self.logger.addHandler(fh_stream)
            self.logger.addHandler(fh_file)

#1、初始化參數(shù)數(shù)據(jù)
#日志文件名稱,日志文件級別
#日志文件名稱 = logs目錄 + 當(dāng)前時間+擴展名
#log目錄
log_path = Conf.get_log_path()
#當(dāng)前時間
current_time = datetime.datetime.now().strftime("%Y-%m-%d")
#擴展名
log_extension = ConfigYaml().get_conf_log_extension()
logfile = os.path.join(log_path,current_time+log_extension)
#print(logfile)
#日志文件級別
loglevel = ConfigYaml().get_conf_log()
#print(loglevel)
#2、對外方法,初始log工具類,提供其它類使用
def my_log(log_name = __file__):
    return Logger(log_file=logfile,log_name=log_name,log_level=loglevel).logger

if __name__ == "__main__":
    my_log().debug("this is a debug")

4.pytest的應(yīng)用

之前總結(jié)的文檔: http://www.lxweimin.com/p/981c32b65de1

image.png
image.png
[pytest]
# 命令行參數(shù)----空格分隔,可添加多個命令行參數(shù) -所有參數(shù)均為插件包的參數(shù)
addopts = -s -reruns 1 --html=../report/report.html
# 測試路徑----當(dāng)前目錄下的scripts文件夾 -可自定義
testpaths = ../scripts
# 搜索文件名----當(dāng)前目錄下的scripts文件夾下,以test_開頭,以.py結(jié)尾的所有文件 -可自定義
python_files = test_*.py
# 搜索測試類名----當(dāng)前目錄下的scripts文件夾下,以Test_開頭的類 -可自定義
python_classes = Test_*
# 搜索測試方法名----當(dāng)前目錄下的scripts文件夾下,以Test_開頭的類內(nèi),以test_開頭的方法 -可自定義
python_functions = test_*

5.結(jié)果斷言

from utils.LogUtil import my_log
import json
#1、定義封裝類
class AssertUtil:
#2、初始化數(shù)據(jù),日志
    def __init__(self):
        self.log = my_log("AssertUtil")
#3、code相等
    def assert_code(self,code,expected_code):
        """
        驗證返回狀態(tài)碼
        :param code:
        :param expected_code:
        :return:
        """
        try:
            assert int(code) == int(expected_code)
            return True
        except:
            self.log.error("code error,code is %s,expected_code is %s"%(code,expected_code))

            raise
#4、body相等
    def assert_body(self,body,expected_body):
        """
        驗證返回結(jié)果內(nèi)容相等
        :param body:
        :param expected_body:
        :return:
        """
        try :
            assert body == expected_body
            return True
        except:
            self.log.error("body error,body is %s,expected_body is %s"%(body,expected_body))
            raise
#5、body包含
    def assert_in_body(self,body,expected_body):
        """
        驗證返回結(jié)果是否包含期望的結(jié)果
        :param body:
        :param expected_body:
        :return:
        """
        try:
            body = json.dumps(body)
            print(body)
            assert expected_body in body
            return True
        except:
            self.log.error("不包含或者body是錯誤,body is %s,expected_body is %s"%(body,expected_body))
            raise
from utils.LogUtil import my_log
import pymysql
#1、創(chuàng)建封裝類
class Mysql:
#2、初始化數(shù)據(jù),連接數(shù)據(jù)庫,光標(biāo)對象
    def __init__(self,host,user,password,database,charset="utf8",port=3306):
        self.log = my_log()
        self.conn = pymysql.connect(
            host=host,
            user=user,
            password=password,
            database=database,
            charset=charset,
            port=port
            )
        self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
#3、創(chuàng)建查詢、執(zhí)行方法
    def fetchone(self,sql):
        """
        單個查詢
        :param sql:
        :return:
        """
        self.cursor.execute(sql)
        return self.cursor.fetchone()

    def fetchall(self,sql):
        """
        多個查詢
        :param sql:
        :return:
        """
        self.cursor.execute(sql)
        return self.cursor.fetchall()

    def exec(self,sql):
        """
        執(zhí)行
        :return:
        """
        try:
            if self.conn and self.cursor:
                self.cursor.execute(sql)
                self.conn.commit()
        except Exception as ex:
            self.conn.rollback()
            self.log.error("Mysql 執(zhí)行失敗")
            self.log.error(ex)
            return False
        return True

#4、關(guān)閉對象
    def __del__(self):
        #關(guān)閉光標(biāo)對象
        if self.cursor is not None:
            self.cursor.close()
        #關(guān)閉連接對象
        if self.conn is not None:
            self.cursor.close()

if __name__ == "__main__":
    mysql = Mysql("211.103.136.242",
                  "test",
                  "test123456","meiduo",
                  charset="utf8",
                  port=7090)
    #res = mysql.fetchall("select username,password from tb_users")
    res = mysql.exec("update tb_users set first_name='python' where username = 'python'")
    print(res)
    #1、創(chuàng)建db_conf.yml, db1,db2
    #2、編寫數(shù)據(jù)庫基本信息
    #3、重構(gòu)Conf.py
    #4、執(zhí)行

"""
#1、導(dǎo)入pymysql包
import pymysql
#2、連接database
conn = pymysql.connect(
    host = "211.103.136.242",
    user = "test",
    password = "test123456",
    database = "meiduo",
    charset = "utf8",
    port =7090

)
#3、獲取執(zhí)行sql的光標(biāo)對象
cursor = conn.cursor()
#4、執(zhí)行sql
sql = "select username,password from tb_users"
cursor.execute(sql)
res = cursor.fetchone()
print(res)
#5、關(guān)閉對象
cursor.close()
conn.close()"""

6.數(shù)據(jù)驅(qū)動

Excel 讀取文件

import os
import xlrd

#目的:參數(shù)化,pytest list
#自定義異常
class SheetTypeError:
    pass
#1、驗證文件是否存在,存在讀取,不存在報錯
class ExcelReader:
    def __init__(self,excel_file,sheet_by):
        if os.path.exists(excel_file):
            self.excel_file = excel_file
            self.sheet_by = sheet_by
            self._data=list()
        else:
            raise  FileNotFoundError("文件不存在")
#2、讀取sheet方式,名稱,索引
    def data(self):
        #存在不讀取,不存在讀取
        if not self._data:
            workbook = xlrd.open_workbook(self.excel_file)
            if type(self.sheet_by) not in [str,int]:
                raise SheetTypeError("請輸入Int or Str")
            elif type(self.sheet_by) == int:
                sheet = workbook.sheet_by_index(self.sheet_by)
            elif type(self.sheet_by) == str:
                sheet = workbook.sheet_by_name(self.sheet_by)
    #3、讀取sheet內(nèi)容
            #返回list,元素:字典
            #格式[{"a":"a1","b":"b1"},{"a":"a2","b":"b2"}]
            #1.獲取首行的信息
            title = sheet.row_values(0)
            #2.遍歷測試行,與首行組成dict,放在list
                #1 循環(huán),過濾首行,從1開始
            for col in range(1,sheet.nrows):
                col_value = sheet.row_values(col)
                #2 與首組成字典,放list
                self._data.append(dict(zip(title, col_value)))

#4、結(jié)果返回
        return self._data

# head = ["a","b"]
# value1 = ["a1","b1"]
# value2 =  ["a2","b2"]
# data_list= list()
# #zip
# data_list.append(dict(zip(head,value1)))
# data_list.append(dict(zip(head,value2)))
# #print(dict(zip(head,value1)))
# #print(dict(zip(head,value2)))
# print(data_list)

if __name__ == "__main__":
    reader = ExcelReader("../data/testdata.xlsx","美多商城接口測試")
    print(reader.data())

發(fā)送郵件的封裝

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
#初始化
#smtp地址,用戶名,密碼,接收郵件者,郵件標(biāo)題,郵件內(nèi)容,郵件附件
class SendEmail:
    def __init__(self,smtp_addr,username,password,recv,
                 title,content=None,file=None):
        self.smtp_addr = smtp_addr
        self.username = username
        self.password = password
        self.recv = recv
        self.title = title
        self.content = content
        self.file = file
#發(fā)送郵件方法
    def send_mail(self):
        #MIME
        msg = MIMEMultipart()
        #初始化郵件信息
        msg.attach(MIMEText(self.content,_charset="utf-8"))
        msg["Subject"] = self.title
        msg["From"] = self.username
        msg["To"] = self.recv
        #郵件附件
        #判斷是否附件
        if self.file:
        #MIMEText讀取文件
            att = MIMEText(open(self.file).read())
        #設(shè)置內(nèi)容類型
            att["Content-Type"] = 'application/octet-stream'
        #設(shè)置附件頭
            att["Content-Disposition"] = 'attachment;filename="%s"'%self.file
        #將內(nèi)容附加到郵件主體中
            msg.attach(att)
        #登錄郵件服務(wù)器
        self.smtp = smtplib.SMTP(self.smtp_addr,port=25)
        self.smtp.login(self.username,self.password)
    #發(fā)送郵件
        self.smtp.sendmail(self.username,self.recv,msg.as_string())

if __name__ == "__main__":
    #初始化類(self,smtp_addr,username,password,recv,
            #     title,content=None,file=None):
    from config.Conf import ConfigYaml
    email_info = ConfigYaml().get_email_info()
    smtp_addr = email_info["smtpserver"]
    username = email_info["username"]
    password = email_info["password"]
    recv = email_info["receiver"]
    email = SendEmail(smtp_addr,username,password,recv,"測試")
    email.send_mail()
    #封裝公共方法
    #應(yīng)用測試發(fā)送

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,627評論 2 380