pytest 邊學邊用(二)

高凡超發表於2025-01-06

準備工作完成後,就是用例的規劃了。我準備按照這個思路來:

  1. 先梳理出基礎的業務流程,找到相關介面,先寫這些介面的用例,寫完再根據業務流程做介面組合測試。 介面主要分為兩類:WEB 頁面呼叫後端的介面,提供給第三方的業務介面。 提供給第三方的業務介面,之前已經用 RF 寫過了,並且整合到了 jenkins。 這次主要就是處理 WEB 頁面呼叫後端的介面,從鑑權的角度,這類介面分成 2 小類,需要鑑權 or NOT。 因為只有我一個人,所以肯定是優先冒煙測試先完成。 整體的鑑權相關設計思路:
  2. 登入介面不要鑑權,該介面返回的 token 是除去它以外全部介面所必須的。所以使用者就弄兩個,一個用來測試登入,一個作為預置的測試賬戶,作為全域性 token 獲取使用者。
  3. 獲取 token 放在 tests 目錄下的 conftest.py 中,結果存到 redis 中失效時間用 token 的有效期。存之前先看能不能取出來,能取到就跳過登入獲取 token,直接返回取到的 token。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author  : 小橙子的爸比 (279453094@qq.com)
# @Version        : 1.0
# @Update Time    : 2025/1/4 下午7:44
# @File           : conftest.py
# @IDE            : PyCharm
# @Desc           : 檔案描述資訊
import json
import httpx
from jsonpath import jsonpath
from db.database_factory import DatabaseFactory
from utils.nb_logger import httpx_log
from utils.nb_logger import pytest_log
import pytest
from config.env_config import EnvInfo, ApiUri


def pytest_runtest_makereport(item, call):
    """鉤子函式收集失敗的斷言資訊存入日誌檔案"""
    if call.excinfo is not None:
        msg = {
            "module": item.location[0],
            "function": item.name,
            "line": item.location[1],
            "message": str(call.excinfo.value).replace("\n", ":")
        }
        pytest_log.error(json.dumps(msg, indent=4, ensure_ascii=False))


headers = EnvInfo().stitching_headers()


@pytest.fixture(scope="session", autouse=True)
def get_token(uri: str = ApiUri.LOGIN_URI, username: str = EnvInfo.USER_NAME,
              password: str = EnvInfo.PASS_WORD):
    """負責全域性token預置,存入redis,有效期一週"""
    data = {
        "username": username,
        "password": password,
        "captchaKey": "b8cb0ef9-57d0-44d9-af53-aad6d5d00183",
        "captcha": "1"
    }
    url = EnvInfo().stitching_url(uri)
    with httpx.Client() as client:
        response = client.post(url=url, headers=headers, json=data)
        httpx_log.info(f"session級fixture POST請求(預置全域性token):{url}")
        httpx_log.info(f"POST請求頭訊息:{headers}")
        httpx_log.info(f"POST請求引數:{data}")
        httpx_log.info(f"POST響應引數:{response.text}")
        if response.status_code != 200:
            raise httpx.HTTPError(f"狀態碼異常,預期值200,實際返回{response.status_code}")
        token = jsonpath(response.json(), '$.idToken')
        rds = DatabaseFactory().get_db_instance("redis").db_getter()
        redis_key = EnvInfo.USER_NAME + "-" + EnvInfo.TEST_ENV_TAG + "-" + EnvInfo.REDIS_STORE_TOKEN_KEY
        rds.set(redis_key, token[0], ex=86400)
    return response

考慮到後期的維護,儘量不用硬編碼,能存到配置檔案的全存進去。

這個寫完登入測試用例跟著改改就行了,需要說明的就是冒煙用例跟異常用例只是驅動資料不同,我決定使用 yam 檔案來儲存測試資料,一個檔案就能搞定。
非鑑權介面就這麼一個,就不去封裝了。
鑑權 token 在頭訊息裡,全域性的前置指令碼只是保障 redis 中的 token 存在
封裝的 httpx 請求,會去 redis 裡取 token,並塞進頭訊息裡,這個放到下一個需要鑑權的介面再看具體的例項。
先看看這個介面測試用例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author  : 小橙子的爸比 (279453094@qq.com)
# @Version        : 1.0
# @Update Time    : 2025/1/3 下午5:55
# @File           : test_login.py
# @IDE            : PyCharm
# @Desc           : 檔案描述資訊
import os
import pytest
from utils.nb_logger import pytest_log as log
import allure
from project_config import settings as config
from utils.tools import YamlTool
import httpx
from config.env_config import EnvInfo, ApiUri, TestDataYamlFileName
from jsonpath import jsonpath

headers = EnvInfo().stitching_headers()


def api_login(url: str, username: str, password: str):
    data = {
        "username": username,
        "password": password,
        "captchaKey": "b8cb0ef9-57d0-44d9-af53-aad6d5d00183",
        "captcha": "1"
    }
    with httpx.Client() as client:
        response = client.post(url=url, headers=headers, json=data)
        log.info(f"smarthub登入請求返回資料:{response.json()}")
    return response


yml = YamlTool(str(os.path.join(config.system.DATA_PATH, TestDataYamlFileName.LOGIN_YML)))
datas = yml.read_yaml()


@allure.feature("SMARTHUB介面測試")
@allure.story("登入介面測試")
@allure.testcase("正確的使用者名稱密碼可以獲取token")
@pytest.mark.smock
@user18ize("user_info", datas['user_info'])
def test_login(user_info):
    username = user_info['username']
    password = user_info['password']
    title = user_info['title']
    # 動態標題
    allure.dynamic.title(title)
    url = EnvInfo().stitching_url(ApiUri.LOGIN_URI)
    resp = api_login(url, username, password)
    assert resp.status_code == 200
    # 使用jsonpath獲取返回值中的指定內容
    token = jsonpath(resp.json(), '$.idToken')
    assert token is not None


@allure.feature("SMARTHUB介面測試")
@allure.story("登入介面測試")
@allure.testcase("錯誤的使用者名稱密碼無法獲取token")
@user22y
@user23ize("user_info_error", datas['user_info_error'])
def test_login_error(user_info_error):
    print(user_info_error)
    username = user_info_error['username']
    password = user_info_error['password']
    title = user_info_error['title']
    # 動態標題
    allure.dynamic.title(title)
    except_value = user_info_error['except_value']
    url = EnvInfo().stitching_url(ApiUri.LOGIN_URI)
    resp = api_login(url, username, password)
    # 使用jsonpath獲取返回值中的指定內容
    title = jsonpath(resp.json(), '$.title')
    assert resp.status_code == 400
    assert except_value == title[0]


if __name__ == '__main__':
    pytest.main()

測試報告展示一下:


附贈一個 allure 的標記說明圖

相關文章