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.reibang.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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市婉弹,隨后出現(xiàn)的幾起案子睬魂,更是在濱河造成了極大的恐慌,老刑警劉巖镀赌,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氯哮,死亡現(xiàn)場離奇詭異,居然都是意外死亡商佛,警方通過查閱死者的電腦和手機喉钢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來良姆,“玉大人肠虽,你說我怎么就攤上這事棒假∩装” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵端壳,是天一觀的道長。 經(jīng)常有香客問我韩玩,道長垒玲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任找颓,我火速辦了婚禮合愈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘击狮。我一直安慰自己想暗,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布帘不。 她就那樣靜靜地躺著说莫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寞焙。 梳的紋絲不亂的頭發(fā)上储狭,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天,我揣著相機與錄音捣郊,去河邊找鬼辽狈。 笑死,一個胖子當(dāng)著我的面吹牛呛牲,可吹牛的內(nèi)容都是我干的刮萌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼娘扩,長吁一口氣:“原來是場噩夢啊……” “哼着茸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琐旁,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤涮阔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后灰殴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敬特,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年牺陶,在試婚紗的時候發(fā)現(xiàn)自己被綠了伟阔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡掰伸,死狀恐怖皱炉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碱工,我是刑警寧澤娃承,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布奏夫,位于F島的核電站,受9級特大地震影響历筝,放射性物質(zhì)發(fā)生泄漏酗昼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一梳猪、第九天 我趴在偏房一處隱蔽的房頂上張望麻削。 院中可真熱鬧,春花似錦春弥、人聲如沸呛哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扫责。三九已至,卻和暖如春逃呼,著一層夾襖步出監(jiān)牢的瞬間鳖孤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工抡笼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留苏揣,地道東北人。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓推姻,卻偏偏與公主長得像平匈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子藏古,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354