httprunner原始碼學習(四)ensure_testcase_v3_api()、make_testcase()
阿新 • • 發佈:2022-01-18
ensure_testcase_v3_api()
__make()方法的第40行呼叫此方法;
上篇39行說了,如果傳入的 json/yaml 檔案,第一層(最外層的鍵)包含 "request" 和 "name",那麼就執行這個方法。
作者的意思很明顯,如果傳入的json/yaml 檔案 只包含一個介面,就執行這個方法。
那如果有多個介面呢,當然會有其他的處理邏輯,但是應該相差不大,理解了這個,其他的不難。
這裡我們要有一個概念,下面這個方法接收的引數,大概是這個樣子:
{ "name": "headers", "request": { "url": "/headers", "method": "GET" }, "validate": [ { "eq": ["status_code", 200] }, { "eq": ["body.headers.Host", "httpbin.org"] } ] }
再來看原始碼
# httprunner/compat.py :: ensure_testcase_v3_api def ensure_testcase_v3_api(api_content: Dict) -> Dict: logger.info("convert api in v2 to testcase format v3") teststep = { # 定義一個測試步驟,呼叫自定義方法,將傳入的介面資訊,根據他定義好的順序來排序 "request": _sort_request_by_custom_order(api_content["request"]), } teststep.update(_ensure_step_attachment(api_content)) # 呼叫自定義方法,作用是:將api_content中的鍵值對經過處理後新增到teststep中,這裡主要處理了 extract 和 validate 這兩個鍵值(處理比較複雜,後面再研究吧),其他的原封不動直接新增 teststep = _sort_step_by_custom_order(teststep) # 呼叫自定義方法,將 teststep 根據他定義好的順序來排序,實現邏輯跟上面 _sort_request_by_custom_order 這個方法類似 config = {"name": api_content["name"]} extract_variable_names: List = list(teststep.get("extract", {}).keys()) if extract_variable_names: config["export"] = extract_variable_names return { # 這裡返回一個字典, "config": config, # config的鍵有:name、export "teststeps": [teststep], # teststeps就是將傳入的api_content裡面的某些欄位處理了一下(排序、提取等),然後放一個列表裡面 }
make_testcase()
__make()方法的第61行呼叫了此方法。
這裡我們要知道呼叫make_testcase方法時傳遞的引數大概是個什麼樣子:
{
"teststeps": [{
"name": ""
"request": {}
"validate": []
}, ]
"config": {}
}
再來看原始碼
# httprunner/make.py :: make_testcase def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: """convert valid testcase dict to pytest file path""" # ensure compatibility with testcase format v2 testcase = ensure_testcase_v3(testcase) # 資料處理,目的是為了鄉下相容V2版本 # validate testcase format load_testcase(testcase) # 資料型別校驗 testcase_abs_path = __ensure_absolute(testcase["config"]["path"]) # 路徑處理 logger.info(f"start to make testcase: {testcase_abs_path}") testcase_python_abs_path, testcase_cls_name = convert_testcase_path( testcase_abs_path ) if dir_path: testcase_python_abs_path = os.path.join( dir_path, os.path.basename(testcase_python_abs_path) ) global pytest_files_made_cache_mapping if testcase_python_abs_path in pytest_files_made_cache_mapping: return testcase_python_abs_path config = testcase["config"] config["path"] = convert_relative_project_root_dir(testcase_python_abs_path) config["variables"] = convert_variables( config.get("variables", {}), testcase_abs_path ) # prepare reference testcase imports_list = [] teststeps = testcase["teststeps"] for teststep in teststeps: if not teststep.get("testcase"): continue # make ref testcase pytest file ref_testcase_path = __ensure_absolute(teststep["testcase"]) test_content = load_test_file(ref_testcase_path) if not isinstance(test_content, Dict): raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}") # api in v2 format, convert to v3 testcase if "request" in test_content and "name" in test_content: test_content = ensure_testcase_v3_api(test_content) test_content.setdefault("config", {})["path"] = ref_testcase_path ref_testcase_python_abs_path = make_testcase(test_content) # override testcase export ref_testcase_export: List = test_content["config"].get("export", []) if ref_testcase_export: step_export: List = teststep.setdefault("export", []) step_export.extend(ref_testcase_export) teststep["export"] = list(set(step_export)) # prepare ref testcase class name ref_testcase_cls_name = pytest_files_made_cache_mapping[ ref_testcase_python_abs_path ] teststep["testcase"] = ref_testcase_cls_name # prepare import ref testcase ref_testcase_python_relative_path = convert_relative_project_root_dir( ref_testcase_python_abs_path ) ref_module_name, _ = os.path.splitext(ref_testcase_python_relative_path) ref_module_name = ref_module_name.replace(os.sep, ".") import_expr = f"from {ref_module_name} import TestCase{ref_testcase_cls_name} as {ref_testcase_cls_name}" if import_expr not in imports_list: imports_list.append(import_expr) testcase_path = convert_relative_project_root_dir(testcase_abs_path) # current file compared to ProjectRootDir diff_levels = len(testcase_path.split(os.sep)) data = { "version": __version__, "testcase_path": testcase_path, "diff_levels": diff_levels, "class_name": f"TestCase{testcase_cls_name}", "imports_list": imports_list, "config_chain_style": make_config_chain_style(config), "parameters": config.get("parameters"), "teststeps_chain_style": [ make_teststep_chain_style(step) for step in teststeps ], } content = __TEMPLATE__.render(data) # ensure new file's directory exists 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_files_made_cache_mapping[testcase_python_abs_path] = testcase_cls_name __ensure_testcase_module(testcase_python_abs_path) logger.info(f"generated testcase: {testcase_python_abs_path}") return testcase_python_abs_path