httprunnerV3源碼——hrun命令詳解

原文:httprunnerV3源碼——hrun命令詳解 - 簡書 (jianshu.com)

httprunner命令介紹

在安裝httprunner庫之后,就可以使用httprunner命令了。

  • httprunner命令:
    在命令行工具輸入httprunner -h,從輸出可以看到,httprunner提供run、startprojecthar2case、make四個子命令:
    run命令用于運行測試用例
    startproject命令用于創(chuàng)建httprunner腳手架項目
    har2case命令用于將har文件轉(zhuǎn)成httprunner使用的yml/json文件
    make命令用于將yml/json用于轉(zhuǎn)成pytest用例
> httprunner -h
usage: httprunner [-h] [-V] {run,startproject,har2case,make} ...

One-stop solution for HTTP(S) testing.

positional arguments:
  {run,startproject,har2case,make}
                        sub-command help
    run                 Make HttpRunner testcases and run with pytest.
    startproject        Create a new project with template structure.
    har2case            Convert HAR(HTTP Archive) to YAML/JSON testcases for
                        HttpRunner.
    make                Convert YAML/JSON testcases to pytest cases.

optional arguments:
  -h, --help            show this help message and exit
  -V, --version         show version

  • 其它命令
    httprunner還提供了三個子命令的縮寫,httprunner run可使用hrun命令代替,同樣的,hmakehar2case分別是httprunner makehttprunner har2case的縮寫。另外,還提供了locusts命令用于執(zhí)行壓測。

pyproject.toml文件中定義了httprunner的命令并指定了入口:

# pyproject.toml
[tool.poetry.scripts]  
httprunner = "httprunner.cli:main"  
hrun = "httprunner.cli:main_hrun_alias"  
hmake = "httprunner.cli:main_make_alias"  
har2case = "httprunner.cli:main_har2case_alias"  
locusts = "httprunner.ext.locust:main_locusts"

命令執(zhí)行過程

httprunner命令的入口在cli模塊的main()函數(shù),在main()函數(shù)中解析了run、startproject、har2case、make命令參數(shù),最終分發(fā)到具體的執(zhí)行函數(shù)處理。
如果用戶輸入不是這些命令也不是-V/--version-h/--help命令,則退出。

# cli.py
def main():
    ...
    if sys.argv[1] == "run":  
        # 執(zhí)行httprunner測試
        sys.exit(main_run(extra_args))  
    elif sys.argv[1] == "startproject":  
        # 創(chuàng)建httprunner腳手架項目
        main_scaffold(args)  
    elif sys.argv[1] == "har2case":  
        # 通過har生成httprunner測試用例
        main_har2case(args)  
    elif sys.argv[1] == "make":  
        # 通過httprunner測試用例生成pytest測試用例
        main_make(args.testcase_path)

run命令

run命令由cli.pymain_run函數(shù)處理,處理流程如下:

  1. 進一步處理用戶輸入,適配httprunnerV2.x參數(shù)
  2. 通過路徑參數(shù)獲取測試文件,轉(zhuǎn)成pytest用例
  3. 將生成的pytest用例文件路徑和處理過的用戶輸入?yún)?shù)傳入pytest執(zhí)行
# cli.py
def main_run(extra_args) -> enum.IntEnum:
    capture_message("start to run")
    # 適配V2.x命令參數(shù)
    extra_args = ensure_cli_args(extra_args)
    # 進一步處理參數(shù),區(qū)分文件路徑參數(shù)和非文件路徑參數(shù),不存在文件路徑參數(shù)則結(jié)束執(zhí)行
    ...

    # 生成pytest測試用例文件,生成的文件不存在則結(jié)束執(zhí)行
    testcase_path_list = main_make(tests_path_list)
    if not testcase_path_list:
        sys.exit(1)

    # 添加--tb=short參數(shù)
    ...
    # 執(zhí)行pytest測試
    extra_args_new.extend(testcase_path_list)  
    return pytest.main(extra_args_new)

生成pytest用例

main_run函數(shù)中,處理用戶入?yún)⒑?,調(diào)用make.pymain_make函數(shù)將hrun用例文件轉(zhuǎn)換為pytest用例文件。

# make.py
def main_make(tests_paths: List[Text]) -> List[Text]:
    # 參數(shù)為空則返回空數(shù)組
    ...
    for tests_path in tests_paths:
        # 確保與 Linux 和 Windows 的不同路徑分隔符兼容,相對路徑轉(zhuǎn)絕對路徑
        ...
        try:
            # 生成pytest用例文件
            __make(tests_path)
        except exceptions.MyBaseError as ex:
            logger.error(ex)
            sys.exit(1)

    # 格式化pytest用例文件
    pytest_files_format_list = pytest_files_made_cache_mapping.keys()
    format_pytest_with_black(*pytest_files_format_list)

    # 返回pytest用例文件路徑數(shù)組
    return list(pytest_files_run_set)

hrun用例轉(zhuǎn)pytest用例

獲取hrun用例文件路徑

tests_pathmain_make函數(shù)中已經(jīng)全部處理成了絕對路徑,但路徑可能是用例文件也可能是用例目錄,__make函數(shù)首先把傳入的路徑數(shù)組轉(zhuǎn)換成用例文件路徑數(shù)組。

# make.py
def __make(tests_path: Text) -> NoReturn:
    test_files = []  
    if os.path.isdir(tests_path): 
        # 目錄路徑,將目錄及子目錄下的所有用例文件取出
        files_list = load_folder_files(tests_path)  
        test_files.extend(files_list)  
    elif os.path.isfile(tests_path): 
        # 文件路徑,直接添加
        test_files.append(tests_path)  
    else:  
        raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}")
    ...

  • load_folder_files函數(shù)
    位于loader.py,該函數(shù)返回指定目錄及其子目錄下的所有以.yml、.yaml、.json、_test.py結(jié)尾的文件路徑

通過hrun用例生成pytest用例

經(jīng)過上一步操作,得到了僅包含測試用例文件路徑的數(shù)組test_files,遍歷數(shù)組,為每個hrun用例生成pytest用例:

# make.py
def __make(tests_path: Text) -> NoReturn:
    ...
    for test_file in test_files:
        # _test.py結(jié)尾已經(jīng)是pytest用例,無需處理,直接添加到待執(zhí)行集合
        ...
        # 加載測試用例內(nèi)容,如果內(nèi)容不是Dict類型,結(jié)束本次處理,不執(zhí)行該用例(此處省略異常捕獲語句)
        test_content = load_test_file(test_file)
        ...
        # V2.x中的api格式轉(zhuǎn)換為V3的testcase格式
        if "request" in test_content and "name" in test_content:
            test_content = ensure_testcase_v3_api(test_content)

        # 用例缺少配置(config屬性)或配置不是Dict類型,結(jié)束本次處理,不執(zhí)行該用例
        ...
        # 設(shè)置path為當(dāng)前文件絕對路徑配置
        test_content.setdefault("config", {})["path"] = test_file

        if "teststeps" in test_content:
            # 文件內(nèi)容是testcase,生成pytest用例文件,將pytest用例添加到待執(zhí)行集合(此處省略異常捕獲語句)
            testcase_pytest_path = make_testcase(test_content)
            pytest_files_run_set.add(testcase_pytest_path)
        elif "testcases" in test_content:
            # 文件內(nèi)容是testsuite,通過其中的testcase生成pytest用例文件,并添加到待執(zhí)行集合(此處省略異常捕獲語句)
            make_testsuite(test_content)
        ...

  • pytest_files_run_set集合
    make.py中定義的Set類型變量,用于保存生成的pytest文件以運行,引用的testcase除外
  • load_test_file函數(shù)
    位于loader.py,該函數(shù)返回指定文件的內(nèi)容。指定文件不存在或不以.json/.yml/.yaml結(jié)尾則拋出異常
  • ensure_testcase_v3_api函數(shù)
    位于compat.py,將V2.x中的api格式內(nèi)容轉(zhuǎn)換為V3統(tǒng)一的testcase格式,返回一個包含config和teststeps屬性的字典數(shù)據(jù)
  • make_testcase函數(shù)
    位于make.py,通過testcase對象生成pytest用例文件,返回文件路徑
  • make_testsuite函數(shù)
    位于make.py,遍歷testsuite中的testcases,通過testcase對象生成pytest用例文件,并將pytest文件路徑添加到pytest_files_run_set集合

從上述代碼段可知,hrun用例轉(zhuǎn)pytest用例的核心方法是make_testcasemake_testsuite

make_testcase

make_testcase函數(shù)中,首先校驗和格式化用例內(nèi)容,確保測試用例內(nèi)容是httprunnerV3的格式

# make.py
def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
    # V2.x用例格式轉(zhuǎn)V3格式
    testcase = ensure_testcase_v3(testcase) 
    # 校驗內(nèi)容格式,load_testcase接收Dict類型入?yún)?,返回一個TestCase對象
    load_testcase(testcase)
    # 獲取用例文件絕對路徑
    testcase_abs_path = __ensure_absolute(testcase["config"]["path"])
    ...

在得到確定的V3格式用例內(nèi)容后,開始轉(zhuǎn)換pytest格式用例。
首先需要確定生成的pytest用例文件路徑、文件名和類名:

def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
    ...
    # 獲取pytest文件路徑和類名
    testcase_python_abs_path, testcase_cls_name = convert_testcase_path(testcase_abs_path)  
    if dir_path:
        # 指定pytest文件目錄
        testcase_python_abs_path = os.path.join(dir_path, os.path.basename(testcase_python_abs_path))

convert_testcase_path函數(shù)根據(jù)原始的yaml/json文件路徑和文件名確定將要生成的pytest文件名和類名:如果原始文件名以數(shù)字開頭,就在文件名前加T;原始文件名中的.-、替換為_;文件名以_test.py結(jié)尾,最終生成一個蛇形命名的文件名;而類名則是將蛇形的文件名字符串(不包含_test.py)轉(zhuǎn)換為大駝峰格式字符串。
例如:

原始文件名 pytest文件名 類名
2021-user.login.yml T2021_user_login_test.py T2021UserLogin
request-with-variables.json request_with_variables_test.py RequestWithVariables

確定pytest文件路徑后,在全局的pytest文件緩存池pytest_files_made_cache_mapping查找文件是否已經(jīng)生成,已生成就直接返回文件路徑。在執(zhí)行多個用例時,用例之間可能存在引用關(guān)系,把已生成的pytest文件記錄到全局變量中可以防止重復(fù)生成文件。

如果pytest文件未生成,接下來就開始轉(zhuǎn)換用例內(nèi)容,將httprunner的用例格式轉(zhuǎn)換為pytest用例格式

config部分
def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
    ...
    config = testcase["config"]
    # pytest文件相對于測試項目根目錄的路徑  
    config["path"] = convert_relative_project_root_dir(testcase_python_abs_path)  
    # 校驗變量格式,并處理$變量引用 
    config["variables"] = convert_variables(config.get("variables", {}), testcase_abs_path)

convert_variables返回字典型變量集合,函數(shù)定義如下:

  • def convert_variables(raw_variables: Union[Dict, List, Text], test_path: Text):
    如果raw_variables是字典類型,如{"var1": 1},無需處理直接返回。
    如果raw_variables是字典數(shù)組類型,如[{"var1": 1}, {"var2": 2}],則將所有元素合并到同一個字典后返回。
    如果raw_variables是文本類型,且存在,則解析引用并返回解析后數(shù)據(jù),否則直接返回原始文本。
    raw_variables不是上述3種類型則拋出異常。
    所以,httprunner用例的變量可以有3種寫法,如果是寫文本,可以寫成"${sum_two(1, 2)}"、$foo1、HttpRunner/${get_httprunner_version()}、"value1=$aaa&value2=$bbb"等多種形式。
teststeps部分

用例配置解析完成后,開始封裝pytest用例數(shù)據(jù)。

  • 如果測試步驟引用了其它用例,先處理引用的用例文件:
    1、加載用例內(nèi)容,并校驗內(nèi)容格式,格式錯誤拋出異常
    2、V2.x適配,將V2.x的api格式轉(zhuǎn)換為V3的testcase格式
    3、在配置中增加path屬性,值為引用用例文件的絕對路徑
    4、處理完用例內(nèi)容后,遞歸調(diào)用make_testcase,生成引用用例的pytest文件
    5、將引用用例的export列表轉(zhuǎn)換成測試步驟的export列表
    6、通過文件路徑從全局的pytest文件池獲取引用用例pytest文件的類名,結(jié)果如:RequestWithFunctions
    7、通過類名和文件路徑生成對用例pytest文件的import語句,結(jié)果如:from . import TestCaseRequestWithFunctions as RequestWithFunctions

封裝pytest用例數(shù)據(jù),生成pytest用例文件:

# 獲取當(dāng)前原始用例(yml、josn文件)相對于測試根目錄(執(zhí)行測試命令的)的路徑
testcase_path = convert_relative_project_root_dir(testcase_abs_path)
# 計算當(dāng)前用例相對于執(zhí)行測試根目錄的深度
diff_levels = len(testcase_path.split(os.sep))

data = {
    # httprunner版本號
    "version": __version__,
    "testcase_path": testcase_path,
    "diff_levels": diff_levels,
    # 最終生成的類名加上TestCase前綴,如:TestCaseRequestWithFunctions
    "class_name": f"TestCase{testcase_cls_name}",
    # 對其它用例的依賴
    "imports_list": imports_list,
    # 用例配置代碼格式化,如:Config("xxx").variables(xx=xxx, ...).verify(...).export(...)...
    "config_chain_style": make_config_chain_style(config),
    # 參數(shù)化配置
    "parameters": config.get("parameters"),
    # 測試步驟代碼格式化,如:RunRequest("xxx").variables(xx=xxx, ...)...
    "teststeps_chain_style": [make_teststep_chain_style(step) for step in teststeps],
}
# 通過jinja2模板,生成pytest用例內(nèi)容
content = __TEMPLATE__.render(data)

# 生成python文件并寫入文件內(nèi)容
dir_path = os.path.dirname(testcase_python_abs_path)
if not os.path.exists(dir_path):
    os.makedirs(dir_path)
with open(testcase_python_abs_path, "w", encoding="utf-8") as f:
    f.write(content)
# 已生成文件添加到全局pytest文件緩存池,key=python文件路徑,value=用例類名
pytest_files_made_cache_mapping[testcase_python_abs_path] = testcase_cls_name
# 確保pytest文件目錄一定存在__init__.py文件,有這個文件,文件目錄才會被識別成一個python模塊
__ensure_testcase_module(testcase_python_abs_path)

# 生成pytest文件結(jié)束,返回文件絕對路徑
return testcase_python_abs_path

make_testsuite

作者:衛(wèi)青臣
鏈接:http://www.itdecent.cn/p/8eb29bd630e7
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容