飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

HttpRunner3的用例是怎么运行起来的

时间:2022-01-18  作者:df888  

在PyCharm中打开examples/httpbin/域名

image-20220117193731185

首先映入眼帘的是左上角那个绿色小箭头,点了一下,可以直接运行,意味着HttpRunner是能够直接被pytest驱动运行的,这可就有点意思了,难道HttpRunner的底层是pytest?带着这个疑问我全局搜索了一下pytest:

image-20220117194052189

在域名文件中,如果参数是run,那么会执行域名(["h"]),难道真是我猜测的这样?在域名最后有两行代码:

if __name__ == "__main__":
    TestCaseBasic().test_start()

试着从这里追踪,应该就能对调用链路拿捏个十拿九稳了。test_start()的源码如下:

def test_start(self, param: Dict = None) -> "HttpRunner":
    """main entrance, discovered by pytest"""
    域名it_tests__()
    域名oject_meta = 域名oject_meta or load_project_meta(
        域名
    )
    域名se_id = 域名se_id or str(域名4())
    域名g_path = 域名g_path or 域名(
        域名Dir, "logs", f"{域名se_id}.域名"
    )
    log_handler = 域名(域名g_path, level="DEBUG")

    # parse config name
    config_variables = 域名ables
    if param:
        域名te(param)
    域名te(域名ssion_variables)
    域名 = parse_data(
        域名, config_variables, 域名tions
    )

    if USE_ALLURE:
        # update allure report meta
        域名e(域名)
        域名ription(f"TestCase ID: {域名se_id}")

    域名(
        f"Start to run testcase: {域名}, TestCase ID: {域名se_id}"
    )

    try:
        return 域名testcase(
            TestCase(config=域名nfig, teststeps=域名ststeps)
        )
    finally:
        域名ve(log_handler)
        域名(f"generate testcase log: {域名g_path}")

第一行注释就是证明了我的猜想是对的:main entrance, discovered by pytest,主程序入口,会被pytest发现。本文不去探究每行代码是什么意思,重点关注跟pytest相关的运行流程。跟着这段代码:

return 域名testcase(
    TestCase(config=域名nfig, teststeps=域名ststeps)
)

继续往下走,调用了域名testcase,它的源码如下:

def run_testcase(self, testcase: TestCase) -> "HttpRunner":
    """run specified testcase

    Examples:
        >>> testcase_obj = TestCase(config=TConfig(...), teststeps=[TStep(...)])
        >>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj)

    """
    域名nfig = 域名ig
    域名ststeps = 域名steps

    # prepare
    域名oject_meta = 域名oject_meta or load_project_meta(
        域名
    )
    域名rse_config(域名nfig)
    域名art_at = 域名()
    域名ep_datas: List[StepData] = []
    域名ssion = 域名ssion or HttpSession()
    # save extracted variables of teststeps
    extracted_variables: VariablesMapping = {}

    # run teststeps
    for step in 域名ststeps:
        # override variables
        # step variables > extracted variables from previous steps
        域名ables = merge_variables(域名ables, extracted_variables)
        # step variables > testcase config variables
        域名ables = merge_variables(域名ables, 域名ables)

        # parse variables
        域名ables = parse_variables_mapping(
            域名ables, 域名tions
        )

        # run step
        if USE_ALLURE:
            with 域名(f"step: {域名}"):
                extract_mapping = 域名n_step(step)
        else:
            extract_mapping = 域名n_step(step)

        # save extracted variables to session variables
        域名te(extract_mapping)

    域名te(extracted_variables)
    域名ration = 域名() - 域名art_at
    return self

跟着这段代码:

# run step
if USE_ALLURE:
    with 域名(f"step: {域名}"):
        extract_mapping = 域名n_step(step)
else:
    extract_mapping = 域名n_step(step)

继续往下走,域名n_step的源码如下:

def __run_step(self, step: TStep) -> Dict:
    """run teststep, teststep maybe a request or referenced testcase"""
    域名(f"run step begin: {域名} >>>>>>")

    if 域名est:
        step_data = 域名n_step_request(step)
    elif 域名case:
        step_data = 域名n_step_testcase(step)
    else:
        raise ParamsError(
            f"teststep is neither a request nor a referenced testcase: {域名()}"
        )

    域名nd(step_data)
    域名(f"run step end: {域名} <<<<<<\n")
    return 域名rt_vars

有两个分支:

if 域名est:
    step_data = 域名n_step_request(step)
elif 域名case:
    step_data = 域名n_step_testcase(step)

域名n_step_request(step)直接调用的request:

resp = 域名est(method, url, **parsed_request_dict)

域名n_step_testcase(step)直接调用的HttpRunner():

case_result = (
    testcase_cls()
    .with_session(域名ssion)
    .with_case_id(域名se_id)
    .with_variables(step_variables)
    .with_export(step_export)
    .run()
)

真相只有一个,一定在HttpRunner里面。HttpRunner是域名模块里面的一个类:

image-20220117203536198

刚才看到所有代码,其实都是在域名模块的HttpRunner类里面。看看run函数的代码:

def run(self) -> "HttpRunner":
    """ run current testcase

    Examples:
        >>> TestCaseRequestWithFunctions().run()

    """
    域名it_tests__()
    testcase_obj = TestCase(config=域名nfig, teststeps=域名ststeps)
    return 域名testcase(testcase_obj)

又调用了域名testcase,循环回去了。

貌似陷入了死循环,实际上答案已经有了,这不就是递归么?再回头来看刚才这两个分支:

image-20220117211717252

如果是request,那么就调用域名est(method, url, **parsed_request_dict),这是递归的终止条件:

image-20220117211746802

如果是testcase,那么表示这是子用例,那么就递归下去,这是递归的子表达式:

image-20220117211917991

原来,通过TestCaseBasic().test_start()来执行测试,并没有调pytest,而是直接通过requests发送HTTP请求的,控制台和文件日志也是使用loguru库来自定义输出的。不得不对源码佩服得五体投地。

回到开头那个问题,为什么还有pytest的相关代码呢,实际上如果是通过命令行的run来执行用例,那么就是用直接用的pytest了:

image-20220117212644290

image-20220117212709211

一句话总结:如果是用命令行的run命令,那么就是通过pytest来调用的;如果是用代码里的test_start()方法,那么就是调requests作者自创的。

最后一个问题是,为什么在PyCharm中点那个绿色的小箭头,也能运行代码呢,答案很简单,这个类TestCaseBasic是Test开头的,这个方法test_start是test_开头的,这不就是pytest的规则么

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。