暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

「Pytest」工具美眉之初学hooks (二)

将咖啡转化为程序的工具人 2021-09-08
2563

在接下来的几篇hooks的学习中,我们将通过一起实现一些实用的功能,来学习pytest的一些自带的hooks方法。


今天我们先来说一说怎么处理那些失败的case。比方说,如何把我们的失败的测试用例写到一个failures.txt文件,并在terminal_summary中打印出来。


首先,可以准备一个test_sample.py,存放我们的测试用例:

    def add(x):
    return x + 1


    def test_answer():
    assert add(3) == 4


    def test_answer1():
    assert add(4) == 3


    def test_answer2():
    assert add(5) == 3


    def test_answer3():
    assert add(6) == 5

    准备一个conftest.py存放我们的hooks方法:

      import pytest
      from pathlib import Path
      from _pytest.config import Config
      from _pytest.main import Session
      from _pytest.terminal import TerminalReporter


      FAILURES_FILE = Path() "failures.txt"


      @pytest.hookimpl()
      def pytest_sessionstart(session: Session):
      if FAILURES_FILE.exists():
      FAILURES_FILE.unlink()
      FAILURES_FILE.touch()


      @pytest.hookimpl(hookwrapper=True)
      def pytest_runtest_makereport(item, call):
      """  每个测试用例执行后,制作测试报告
        :param
      item: 测试用例对象
        :param
      call: 测试用例的测试步骤
      先执行when =’setup’ 返回setup的执行结果
      然后执行when =’call’ 返回call的执行结果
      最后执行when =’teardown’返回teardown的执行结果
        :return:
          """
      outcome = yield
      # 获取钩子方法的调用结果,返回一个result对象
      result = outcome.get_result()
      # 获取调用结果的测试报告,返回一个report对象,reportd对象的属性包括:
      # when(steup, call, teardown三个值)
      # nodeid(测试用例的名字)
      # outcome(用例的执行结果,passed,failed)
      if result.when == "call" and result.failed:
      try:
      with open(str(FAILURES_FILE), "a") as f:
      f.write(result.nodeid + "\n")
      except Exception as e:
      print("ERROR", e)
      pass


      @pytest.hookimpl(hookwrapper=True)
      def pytest_terminal_summary(
      terminalreporter: TerminalReporter, exitstatus: int, config: Config
      ):
      yield
      print(f"Failures outputted to: {FAILURES_FILE}")
      print(f"to see run\ncat {FAILURES_FILE}")

      一起来看下执行效果:


      我们通过三个hooks方法来实现了这一功能,分别是:

      pytest_sessionstart

      pytest_runtest_makereport

      pytest_terminal_summary

      在每个hooks方法上我们都加上了一个@pytest.hookimpl,该注解作用于pytest的钩子函数上,可以获取到测试用例不同执行阶段的结果(setup,call,teardown),也可以获取钩子方法的调用结果(返回一个result对象)和调用结果的测试报告。下面我们具体说明一下:

      1)pytest_sessionstart这个hook方法是整个测试session开始时会运行一次,在这个方法里,我们会尝试删除已经存在的failure.txt文件,如果没有则重新创建一个。这个方法可以拿到pytest的session对象,我们debug了一下,通过session的对象,我们可以拿到许多信息例如:config,可以动态地做一些pytest ini配置,拿到option等等。

      2)pytest_runtest_makereport加上了一个@pytest.hookimpl,该注解作用于pytest的钩子函数上,可以获取到测试用例不同执行阶段的结果(setup,call,teardown),也就是说,每一条测试用例的setup 阶段,call阶段和tear down 阶段都会运行至pytest_runtest_makereport的方法,我们一起debug看一下test_answer这个测试用例分别会进入此hook方法三次。

      when = 'setup'

      when = 'call'

      when = 'teardown'

      我们可以通过result对象的outcome属性拿到这条case的执行结果是passed还是failed,通过nodeid属性拿到这条case的名称,通过when属性拿到当前case的执行阶段。于是,我们可以在pytest_runtest_makereport中,把failed的case的名字追加写入到failures.txt文件中。

      3)pytest_terminal_summary这个方法会在所有的测试用例都执行完成后调用。我们在这个方法中打印了最终的failures.txt的路径。通过这个方法,我们可以拿到三个参数

      terminalreporter: TerminalReporter

      exitstatus: int

      config: Config

      我们一起来debug一下,看看具体这些对象有哪些属性

      可以看到exitstatus = ExitCode.TESTS_FAILED,因为我们有一部分测试用例失败了,通过stats 的passed dict 和failed dict我们可以拿到所有成功和失败的用例名等等。


      我们通常对pytest失败的case会做两个处理,一个是上面例子里提到的收集用于report展示,另一个就是失败重跑。工具美眉在之前的文章中介绍过,pytest提供了很强大的插件rerunfailures,可以用来对失败的case进行重跑。


      我们来看看这个插件的源码,学习一下,它是怎么实现失败重跑的功能的。


      可以看到这个插件通过pytest_addoption方法提供了一系列命令行参数,例如

        pytest testcase.py --reruns 5 #对失败的case进行重跑,最多5次

        通过pytest_configure这个pytest自带的hook方法,完成了初始化,往pytest.ini中添加了一个marker标签-flaky,flaky有两个参数,一个是rerun次数,另一个是rerun之间的delay时间,可以通过flaky这个mark给某个testcase标注成需要失败重跑。

          @pytest.mark.flaky(reruns=3, reruns_delay=5)
          def test_demo_02():
          b = 1 + 2
          assert 2 == b


          通过实现了pytest另一个hook方法pytest_runtest_protocol,这个hook方法在每个测试用例执行之前调用一次,它接收两个参数:

          item :要执行的测试用例对象;

          nextitem :预计的下一个测试用例对象;

          这个插件的核心代码在于以下这段:

          这个插件的运行流程图大概长这个样子:


          总结一下:

          今天主要学习了以下的hook:

          • pytest_sessionstart 

            整个测试session开始时会运行一次

          • pytest_runtest_makereport 

            每一条测试用例的setup 阶段,call阶段和tear down 阶段都会调用这个方法

          • pytest_terminal_summary

            所有的测试用例都执行完成后调用

          • pytest_runtest_protocol 

            在每个测试用例执行之前调用一次,会比pytest_runtest_makereport先执行


          今天的学习就到这里,如果这篇文章对你有帮助,请记得点赞和转发哦👍

          文章转载自将咖啡转化为程序的工具人,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论