基於 Pytest+Requests+Allure 實現介面自動化測試

大话性能發表於2024-04-17

一、整體結構

  • 框架組成:pytest+requests+allure
  • 設計模式:
  • - 關鍵字驅動
  • 專案結構:
  • - 工具層:api_keyword/
    • 引數層:params/
    • 用例層:case/
    • 資料驅動:data_driver/
    • 資料層:data/
    • 邏輯層:logic/

二、具體步驟及程式碼

1、工具層

將 get、post 等常用行為進行二次封裝。

程式碼(api_key.py)如下:

import allure
import json
import jsonpath
import requests

# 定義一個關鍵字類
class ApiKey:
    # 將get請求行為進行封裝
    @allure.step("傳送get請求")
    def get(self, url, params=None, **kwargs):
        return requests.get(url=url, params=params, **kwargs)

    # 將post請求行為進行封裝
    @allure.step("傳送post請求")
    def post(self, url, data=None, **kwargs):
        return requests.post(url=url, data=data, **kwargs)

    # 由於介面之間可能相互關聯,因此下一個介面需要上一個介面的某個返回值,此處採用jsonpath對上一個介面返回的值進行定位並取值
    @allure.step("獲取返回結果字典值")
    def get_text(self, data, key):
        # json資料轉換為字典
        json_data = json.loads(data)
        # jsonpath取值
        value = jsonpath.jsonpath(json_data, '$..{0}'.format(key))
        return value[0]
  • 其中引用 allure.step() 裝飾器進行步驟詳細描述,使測試報告更加詳細。
  • 使用 jsonpath 對介面的返回值進行取值。

2、資料層

資料採用 yaml 檔案。

程式碼(user.yaml)如下:

-
  user:
    username: admin
    password: '123456'
  msg: success
  title: 輸入正確賬號密碼登入成功
-
  user:
    username: admin1
    password: '1234561'
  msg: 使用者名稱或密碼錯誤
  title: 輸入錯誤賬號1密碼1登入失敗
-
  user:
    username: admin2
    password: '1234562'
  msg: 使用者名稱或密碼錯誤
  title: 輸入錯誤賬號2密碼2登入失敗
  • 其中 title 是為了在用例進行時動態獲取引數生成標題。

3、資料驅動層

對資料進行讀寫。

程式碼(yaml.driver.py)如下:

import yaml


def load_yaml(path):
    file = open(path, 'r', encoding='utf-8')
    data = yaml.load(file, Loader=yaml.FullLoader)
    return data

4、引數層

引數層存放公共使用的引數,在使用時對其進行呼叫。

程式碼(allParams.py)如下:

'''python
    規則:
    全域性變數使用大寫字母表示
'''

# 地址
URL = 'http://39.98.138.157:'

# 埠
PORT = '5000'

5、邏輯層

用例一:進行登入的介面請求,此處登入請求在 yaml 檔案裡設定了三組不同的資料進行請求。

用例二:進行個人查詢的介面請求,此處需要用到登入介面返回的 token 值。

用例三、進行新增商品到購物車的介面請求,此處需要用到登入介面返回的 token 值以及個人查詢介面返回的 openid、userid 值

用例四、進行下單的介面請求,此處需要用到登入介面返回的 token 值以及個人查詢介面返回的 openid、userid、cartid 值

注意:由於多數介面需要用到登入介面返回的 token 值,因此封裝一個 conftest.py 定義專案級前置 fixture,在整個專案只執行一次,可以在各個用例中進行呼叫(其他共用引數也可以採取類似前置定義)。同時由於此處定義的專案級 fixture,因此可以將初始化工具類 ak = ApiKey() 也放入其中。

程式碼(conftest.py)如下:

from random import random

import allure
import pytest

from pytest_demo_2.api_keyword.api_key import ApiKey
from pytest_demo_2.params.allParams import *


def pytest_collection_modifyitems(items):
    """
    測試用例收集完成時,將收集到的item的name和nodeid的中文顯示在控制檯上
    """
    for item in items:
        item.name = item.name.encode("utf-8").decode("unicode_escape")
        item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")


# 專案級fix,整個專案只初始化一次
@pytest.fixture(scope='session')
def token_fix():
    # 初始化工具類
    ak = ApiKey()
    with allure.step("傳送登入介面請求,並獲取token,整個專案只生成一次"):
        # 請求介面
        # url = 'http://39.98.138.157:5000/api/login'
        url = URL + PORT + '/api/login'
        # 請求引數
        userInfo = {
            'username': 'admin',
            'password': '123456'
        }
        # post請求
        res = ak.post(url=url, json=userInfo)
        # 獲取token
        token = ak.get_text(res.text, 'token')
        # 驗證程式碼,驗證token只生成一次
        token_random = random()
        return ak, token, res, token_random
  • 其中也包含了防止中文亂碼,加入了 pytest_collection_modifyitems(函式)。

設定好 conftest 後,就可以應用在邏輯層裡面了。

程式碼(shopingApi.py)如下:

import pytest
import allure
from pytest_demo_2.api_keyword.api_key import ApiKey
from pytest_demo_2.params.allParams import *


class ApiCase():
    # 登入邏輯
    def params_login(self, userdata):
        # 動態獲取引數生成標題
        allure.dynamic.title(userdata['title'])
        # 初始化工具類
        ak = ApiKey()
        # 請求介面
        url = URL + PORT + '/api/login'
        # 請求引數
        userInfo = {
            'username': userdata['user']['username'],
            'password': userdata['user']['password']
        }
        res = ak.post(url=url, json=userInfo)
        with allure.step("介面返回資訊校驗及列印"):
            print("/api/login登入介面請求響應資訊")
            print(res.text)
            # 獲取響應結果
            msg = ak.get_text(res.text, 'msg')
            print(msg)
            # 斷言
            assert msg == userdata['msg']

    def params_getuserinfo(self, token_fix):
        # 從fix中獲取預置的工具類和token,所有返回值都需要接收
        ak, token, res, token_random01 = token_fix
        with allure.step("傳送個人查詢介面請求"):
            url = URL + PORT + '/api/getuserinfo'
            headers = {
                'token': token
            }
            res1 = ak.get(url=url, headers=headers)
        with allure.step("介面返回資訊校驗及列印"):
            print("/api/getuserinfo個人使用者查詢介面請求響應資訊")
            print(res1.text)
            # print("驗證的random值,測試用")
            # print(token_random01)
            name = ak.get_text(res1.text, 'nikename')
            # 斷言
            assert "風清揚" == name
        return res1

    def params_addcart(self, token_fix):
        # 從fix中獲取預置的工具類和token
        # 所有返回都要獲取,不然會報錯
        ak, token, res, token_random01 = token_fix
        with allure.step("呼叫getuserinfo介面獲取返回資訊"):
            res1 = self.params_getuserinfo(token_fix)
        with allure.step("傳送新增商品到購物車請求"):
            # 新增商品到購物車,基於token、userid、openid、productid
            url = URL + PORT + '/api/addcart'
            hd = {
                "token": token
            }
            data = {
                "userid": ak.get_text(res1.text, 'userid'),
                "openid": ak.get_text(res1.text, 'openid'),
                "productid": 8888
            }
            # 傳送請求
            res2 = ak.post(url=url, headers=hd, json=data)
        with allure.step("介面返回資訊校驗及列印"):
            print("/api/addcart新增商品到購物車請求響應資訊")
            print(res2.text)
            # print("驗證的random值,測試用")
            # print(token_random01)
            result = ak.get_text(res2.text, 'result')
            assert 'success' == result
        return res2

    def params_createorder(self, token_fix):
        ak, token, res, token_random01 = token_fix
        with allure.step("呼叫addcart介面獲取返回資訊"):
            res1 = self.params_addcart(token_fix)
        with allure.step("傳送下單請求"):
            url = URL + PORT + '/api/createorder'
            # 從專案級fix中獲取token
            hd = {
                "token": token
            }
            # 從新增商品到購物車介面中獲取userid,openid,cartid
            data = {
                "userid": ak.get_text(res1.text, 'userid'),
                "openid": ak.get_text(res1.text, 'openid'),
                "productid": 8888,
                "cartid": ak.get_text(res1.text, 'cartid')
            }
            res2 = ak.post(url=url, headers=hd, json=data)
        with allure.step("介面返回資訊校驗及列印"):
            print("/api/createorder下單請求響應資訊")
            print(res2.text)
            # print("驗證的random值,測試用")
            # print(token_random01)
            result = ak.get_text(res1.text, 'result')
            assert 'success' == result

6、用例層
呼叫邏輯層進行用例管理和資料傳輸。
程式碼(test_Tree.py)如下:

import allure
import pytest
from pytest_demo_2.data_driver import yaml_driver
from pytest_demo_2.logic.shopingApi import ApiCase


@allure.epic("shopXo電商平臺介面-介面測試")
class TestTree():
    # 初始化用例庫
    actions1 = ApiCase()

    @allure.feature("01.登陸")
    @allure.story("02.一般場景")
    @user8ize('userdata', yaml_driver.load_yaml('./data/user.yaml'))
    def test_case01(self, userdata):
        self.actions1.params_login(userdata)

    @allure.feature("02.個人查詢")
    @allure.story("01.典型場景")
    @allure.title("個人查詢")
    def test_case02(self, token_fix):
        self.actions1.params_getuserinfo(token_fix)

    @allure.feature("03.新增商品到購物車")
    @allure.story("01.典型場景")
    @allure.title("新增商品到購物車")
    def test_case03(self, token_fix):
        self.actions1.params_addcart(token_fix)

    @allure.feature("04.下單")
    @allure.story("01.典型場景")
    @allure.title("下單")
    def test_case04(self, token_fix):
        self.actions1.params_createorder(token_fix)

7、執行

程式碼(main_run.py)如下:

import os
import pytest


def run():
    pytest.main(['-v', './case/test_Tree.py',
                 '--alluredir', './result', '--clean-alluredir'])
    os.system('allure serve result')
    # os.system('allure generate ./result/ -o ./report_allure/ --clean')

if __name__ == '__main__':
    run()

8、結果

更多內容可以訪問個人主頁學習《測試工程師 Python 工具開發實戰》書籍《大話效能測試 JMeter 實戰》書籍

相關文章