基于Python+Requests+Pytest+YAML+Allure實(shí)現(xiàn)接口自動(dòng)化

本項(xiàng)目實(shí)現(xiàn)接口自動(dòng)化的技術(shù)選型:Python+Requests+Pytest+YAML+Allure 纳本,主要是針對(duì)之前開發(fā)的一個(gè)接口項(xiàng)目來進(jìn)行學(xué)習(xí),通過 Python+Requests 來發(fā)送和處理HTTP協(xié)議的請(qǐng)求接口摊聋,使用 Pytest 作為測(cè)試執(zhí)行器,使用 YAML 來管理測(cè)試數(shù)據(jù)蹂风,使用 Allure 來生成測(cè)試報(bào)告乾翔。

接口項(xiàng)目開發(fā)學(xué)習(xí):
使用Flask開發(fā)簡(jiǎn)單接口(1)--GET請(qǐng)求接口
使用Flask開發(fā)簡(jiǎn)單接口(2)--POST請(qǐng)求接口
使用Flask開發(fā)簡(jiǎn)單接口(3)--引入MySQL
使用Flask開發(fā)簡(jiǎn)單接口(4)--借助Redis實(shí)現(xiàn)token驗(yàn)證
使用Flask開發(fā)簡(jiǎn)單接口(5)--數(shù)據(jù)加密處理

項(xiàng)目說明

本項(xiàng)目在實(shí)現(xiàn)過程中,把整個(gè)項(xiàng)目拆分成請(qǐng)求方法封裝嗡呼、HTTP接口封裝纸俭、關(guān)鍵字封裝、測(cè)試用例等模塊南窗。

首先利用Python把HTTP接口封裝成Python接口揍很,接著把這些Python接口組裝成一個(gè)個(gè)的關(guān)鍵字,再把關(guān)鍵字組裝成測(cè)試用例矾瘾,而測(cè)試數(shù)據(jù)則通過YAML文件進(jìn)行統(tǒng)一管理女轿,然后再通過Pytest測(cè)試執(zhí)行器來運(yùn)行這些腳本,并結(jié)合Allure輸出測(cè)試報(bào)告壕翩。

當(dāng)然蛉迹,如果感興趣的話,還可以再對(duì)接口自動(dòng)化進(jìn)行Jenkins持續(xù)集成放妈。

GitHub項(xiàng)目源碼地址:https://github.com/wintests/pytestDemo

項(xiàng)目結(jié)構(gòu)

  • api ====>> 接口封裝層北救,如封裝HTTP接口為Python接口
  • common ====>> 各種工具類
  • core ====>> requests請(qǐng)求方法封裝、關(guān)鍵字返回結(jié)果類
  • config ====>> 配置文件
  • data ====>> 測(cè)試數(shù)據(jù)文件管理
  • operation ====>> 關(guān)鍵字封裝層芜抒,如把多個(gè)Python接口封裝為關(guān)鍵字
  • pytest.ini ====>> pytest配置文件
  • requirements.txt ====>> 相關(guān)依賴包文件
  • testcases ====>> 測(cè)試用例

請(qǐng)求方法封裝

core/rest_client.py 文件中珍策,對(duì) Requests 庫(kù)下一些常見的請(qǐng)求方法進(jìn)行了簡(jiǎn)單封裝,以便調(diào)用起來更加方便宅倒。

class RestClient():

    def __init__(self, api_root_url):
        self.api_root_url = api_root_url
        self.session = requests.session()

    def get(self, url, **kwargs):
        return self.request(url, "GET", **kwargs)

    def post(self, url, data=None, json=None, **kwargs):
        return self.request(url, "POST", data, json, **kwargs)

    def put(self, url, data=None, **kwargs):
        return self.request(url, "PUT", data, **kwargs)

    def delete(self, url, **kwargs):
        return self.request(url, "DELETE", **kwargs)

    def patch(self, url, data=None, **kwargs):
        return self.request(url, "PATCH", data, **kwargs)

    def request(self, url, method, data=None, json=None, **kwargs):
        url = self.api_root_url + url
        headers = dict(**kwargs).get("headers")
        params = dict(**kwargs).get("params")
        files = dict(**kwargs).get("params")
        cookies = dict(**kwargs).get("params")
        self.request_log(url, method, data, json, params, headers, files, cookies)
        if method == "GET":
            return self.session.get(url, **kwargs)
        if method == "POST":
            return requests.post(url, data, json, **kwargs)
        if method == "PUT":
            if json:
                # PUT 和 PATCH 中沒有提供直接使用json參數(shù)的方法攘宙,因此需要用data來傳入
                data = complexjson.dumps(json)
            return self.session.put(url, data, **kwargs)
        if method == "DELETE":
            return self.session.delete(url, **kwargs)
        if method == "PATCH":
            if json:
                data = complexjson.dumps(json)
            return self.session.patch(url, data, **kwargs)

HTTP接口 封裝為 Python接口

api/user.py 文件中,將上面封裝好的HTTP接口拐迁,再次封裝為不同的Python接口蹭劈。不同的Python接口,會(huì)處理不同URL下的請(qǐng)求线召。

class User(RestClient):

    def __init__(self, api_root_url, **kwargs):
        super(User, self).__init__(api_root_url, **kwargs)

    def list_all_users(self, **kwargs):
        return self.get("/users", **kwargs)

    def list_one_user(self, username, **kwargs):
        return self.get("/users/{}".format(username), **kwargs)

    def register(self, **kwargs):
        return self.post("/register", **kwargs)

    def login(self, **kwargs):
        return self.post("/login", **kwargs)

    def update(self, user_id, **kwargs):
        return self.put("/update/user/{}".format(user_id), **kwargs)

    def delete(self, name, **kwargs):
        return self.post("/delete/user/{}".format(name), **kwargs)

關(guān)鍵字返回結(jié)果類

core/result_base.py 下铺韧,定義了一個(gè)空類 ResultBase ,該類主要用于自定義關(guān)鍵字返回結(jié)果缓淹。

class ResultBase():
    pass

"""
自定義示例:
result = ResultBase()
result.success = False
result.msg = res.json()["msg"]
result.response = res
"""

在多流程的業(yè)務(wù)場(chǎng)景測(cè)試下哈打,通過自定義期望保存的返回?cái)?shù)據(jù)值塔逃,以便更好的進(jìn)行斷言。

關(guān)鍵字封裝

關(guān)鍵字應(yīng)該是具有一定業(yè)務(wù)意義的料仗,在封裝關(guān)鍵字的時(shí)候湾盗,可以通過調(diào)用多個(gè)Python接口來完成。在某些情況下立轧,比如測(cè)試一個(gè)充值接口的時(shí)候淹仑,在充值后可能需要調(diào)用查詢接口得到最新賬戶余額,來判斷查詢結(jié)果與預(yù)期結(jié)果是否一致肺孵,那么可以這樣來進(jìn)行測(cè)試:

  • 1, 首先匀借,可以把 充值-查詢 的操作封裝為一個(gè)關(guān)鍵字,在這個(gè)關(guān)鍵字中依次調(diào)用充值和查詢的接口平窘,并可以自定義關(guān)鍵字的返回結(jié)果吓肋。
  • 2, 接著,在編寫測(cè)試用例的時(shí)候瑰艘,直接調(diào)用關(guān)鍵字來進(jìn)行測(cè)試是鬼,這時(shí)就可以拿到關(guān)鍵字返回的結(jié)果,那么斷言的時(shí)候紫新,就可以直接對(duì)關(guān)鍵字返回結(jié)果進(jìn)行斷言均蜜。

測(cè)試用例層

根據(jù)用例名分配測(cè)試數(shù)據(jù)

測(cè)試數(shù)據(jù)位于 data 文件夾下,在這里使用 YAML 來管理測(cè)試數(shù)據(jù)芒率,同時(shí)要求測(cè)試數(shù)據(jù)中第一層的名稱囤耳,需要與測(cè)試用例的方法名保持一致,如 test_get_all_user_info 偶芍、test_delete_user充择。

test_get_all_user_info:
  # 期望結(jié)果,期望返回碼,期望返回信息
  # except_result, except_code, except_msg
  - [True, 0, "查詢成功"]
省略
test_delete_user:
  # 刪除的用戶名,期望結(jié)果,期望返回碼,期望返回信息
  # username, except_result, except_code, except_msg
  - ["測(cè)試test", True, 0, "刪除用戶信息成功"]
  - ["wintest3", False, 3006, "該用戶不允許刪除"]

這里借助 fixture 方法,我們就能夠通過 request.function.__name__ 自動(dòng)獲取到當(dāng)前執(zhí)行用例的函數(shù)名 testcase_name 匪蟀,當(dāng)我們傳入測(cè)試數(shù)據(jù) api_data 之后椎麦,接著便可以使用 api_data.get(testcase_name) 來獲取到對(duì)應(yīng)用例的測(cè)試數(shù)據(jù)。

import pytest
from testcases.conftest import api_data

@pytest.fixture(scope="function")
def testcase_data(request):
    testcase_name = request.function.__name__
    return api_data.get(testcase_name)
數(shù)據(jù)準(zhǔn)備和清理

在接口自動(dòng)化中材彪,為了保證用例可穩(wěn)定观挎、重復(fù)地執(zhí)行,我們還需要有測(cè)試前置操作和后置操作段化,即數(shù)據(jù)準(zhǔn)備和數(shù)據(jù)清理工作嘁捷。

@pytest.fixture(scope="function")
def delete_register_user():
    """注冊(cè)用戶前,先刪除數(shù)據(jù)穗泵,用例執(zhí)行之后普气,再次刪除以清理數(shù)據(jù)"""
    del_sql = base_data["init_sql"]["delete_register_user"]
    db.execute_db(del_sql)
    logger.info("注冊(cè)用戶操作:清理用戶--準(zhǔn)備注冊(cè)新用戶")
    logger.info("執(zhí)行前置SQL:{}".format(del_sql))
    yield # 用于喚醒 teardown 操作
    db.execute_db(del_sql)
    logger.info("注冊(cè)用戶操作:刪除注冊(cè)的用戶")
    logger.info("執(zhí)行后置SQL:{}".format(del_sql))

在這里谜疤,以用戶注冊(cè)用例為例佃延。對(duì)于前置操作现诀,我們應(yīng)該準(zhǔn)備一條刪除SQL,用于將數(shù)據(jù)庫(kù)中已存在的相同用戶刪除履肃,對(duì)于后置操作仔沿,我們應(yīng)該再執(zhí)行刪除SQL,確保該測(cè)試數(shù)據(jù)正常完成清理工作尺棋。

在測(cè)試用例中封锉,我們只需要在用例上傳入 fixture 的函數(shù)參數(shù)名 delete_register_user ,這樣就可以調(diào)用 fixture 實(shí)現(xiàn)測(cè)試前置及后置操作膘螟。當(dāng)然成福,也可以使用pytest裝飾器 @pytest.mark.usefixtures() 來完成,如:

@pytest.mark.usefixtures("delete_register_user")
Allure用例描述

在這里荆残,我們結(jié)合 Allure 來實(shí)現(xiàn)輸出測(cè)試報(bào)告奴艾,同時(shí)我們可以使用其裝飾器來添加一些用例描述并顯示到測(cè)試報(bào)告中,以便報(bào)告內(nèi)容更加清晰内斯、直觀蕴潦、可讀。如使用 @allure.title() 自定義報(bào)告中顯示的用例標(biāo)題俘闯,使用 @allure.description() 自定義用例的描述內(nèi)容潭苞,使用 @allure.step() 可在報(bào)告中顯示操作步驟,使用 @allure.issue() 可在報(bào)告中顯示缺陷及其鏈接等真朗。

@allure.step("步驟1 ==>> 注冊(cè)用戶")
def step_1(username, password, telephone, sex, address):
    logger.info("步驟1 ==>> 注冊(cè)用戶 ==>> {}, {}, {}, {}, {}".format(username, password, telephone, sex, address))

@allure.severity(allure.severity_level.NORMAL)
@allure.epic("針對(duì)單個(gè)接口的測(cè)試")
@allure.feature("用戶注冊(cè)模塊")
class TestUserRegister():
    """用戶注冊(cè)"""
    @allure.story("用例--注冊(cè)用戶信息")
    @allure.description("該用例是針對(duì)獲取用戶注冊(cè)接口的測(cè)試")
    @allure.issue("https://www.cnblogs.com/wintest", name="點(diǎn)擊此疹,跳轉(zhuǎn)到對(duì)應(yīng)BUG的鏈接地址")
    @allure.testcase("https://www.cnblogs.com/wintest", name="點(diǎn)擊,跳轉(zhuǎn)到對(duì)應(yīng)用例的鏈接地址")
    @allure.title(
        "測(cè)試數(shù)據(jù):【 {username}遮婶,{password}秀菱,{telephone},{sex}蹭睡,{address}衍菱,{except_result},{except_code}肩豁,{except_msg}】")
    @pytest.mark.single
    @pytest.mark.parametrize("username, password, telephone, sex, address, except_result, except_code, except_msg",
                             api_data["test_register_user"])
    @pytest.mark.usefixtures("delete_register_user")
    def test_delete_user(self, login_fixture, username, except_result, except_code, except_msg):
省略

項(xiàng)目部署

首先脊串,下載項(xiàng)目源碼后,在根目錄下找到 requirements.txt 文件清钥,然后通過 pip 工具安裝 requirements.txt 依賴琼锋,執(zhí)行命令:

pip3 install -r requirements.txt

接著,修改 config/setting.ini 配置文件祟昭,在Windows環(huán)境下缕坎,安裝相應(yīng)依賴之后,在命令行窗口執(zhí)行命令:

pytest

注意:因?yàn)槲疫@里是針對(duì)自己的接口項(xiàng)目進(jìn)行測(cè)試篡悟,如果想直接執(zhí)行我的測(cè)試用例來查看效果谜叹,需要提前部署上面提到的接口項(xiàng)目匾寝。

測(cè)試報(bào)告效果展示

在命令行執(zhí)行命令:pytest 運(yùn)行用例后,會(huì)得到一個(gè)測(cè)試報(bào)告的原始文件荷腊,但這個(gè)時(shí)候還不能打開成HTML的報(bào)告艳悔,還需要在項(xiàng)目根目錄下,執(zhí)行命令啟動(dòng) allure 服務(wù):

# 需要提前配置allure環(huán)境女仰,才可以直接使用命令行
allure serve ./report

最終猜年,可以看到測(cè)試報(bào)告的效果圖如下:

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市疾忍,隨后出現(xiàn)的幾起案子乔外,更是在濱河造成了極大的恐慌,老刑警劉巖一罩,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袁稽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡擒抛,警方通過查閱死者的電腦和手機(jī)推汽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歧沪,“玉大人歹撒,你說我怎么就攤上這事≌锇” “怎么了暖夭?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)撵孤。 經(jīng)常有香客問我迈着,道長(zhǎng),這世上最難降的妖魔是什么邪码? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任裕菠,我火速辦了婚禮,結(jié)果婚禮上闭专,老公的妹妹穿的比我還像新娘奴潘。我一直安慰自己,他們只是感情好影钉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布画髓。 她就那樣靜靜地躺著,像睡著了一般平委。 火紅的嫁衣襯著肌膚如雪奈虾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音肉微,去河邊找鬼匾鸥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛浪册,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岗照,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼村象,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了攒至?” 一聲冷哼從身側(cè)響起厚者,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎迫吐,沒想到半個(gè)月后库菲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡志膀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年熙宇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溉浙。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡烫止,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出戳稽,到底是詐尸還是另有隱情馆蠕,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布惊奇,位于F島的核電站互躬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏颂郎。R本人自食惡果不足惜吼渡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乓序。 院中可真熱鬧诞吱,春花似錦、人聲如沸竭缝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)抬纸。三九已至咙俩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阿趁。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工膜蛔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脖阵。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓皂股,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親命黔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子呜呐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345