【pytest】fixture参考

举报
子都爱学习 发表于 2024/05/16 20:33:49 2024/05/16
【摘要】 一、什么是固定装置夹具 为测试提供了定义的、可靠的和一致的上下文夹具定义构成测试的安排阶段的步骤和数据测试函数通过参数访问由固定装置设置的服务、状态或其他操作环境。import pytestclass Fruit: def __init__(self, name): self.name = name def __eq__(self, other): r...

一、什么是固定装置

夹具 为测试提供了定义的、可靠的和一致的上下文

夹具定义构成测试的安排阶段的步骤和数据

测试函数通过参数访问由固定装置设置的服务、状态或其他操作环境。

import pytest


class Fruit:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name


@pytest.fixture
def my_fruit():
    return Fruit("apple")


@pytest.fixture
def fruit_basket(my_fruit):
    return [Fruit("banana"), my_fruit]


def test_my_fruit_in_basket(my_fruit, fruit_basket):
    assert my_fruit in fruit_basket

二、“请求”赛程

在基本层面上,测试函数通过将它们声明为参数来请求它们所需的装置。

当 pytest 运行测试时,它会查看该测试函数签名中的参数,然后搜索与这些参数同名的装置。一旦 pytest 找到它们,它就会运行这些固定装置,捕获它们返回的内容(如果有的话),并将这些对象作为参数传递到测试函数中。

import pytest


class Fruit:
    def __init__(self, name):
        self.name = name
        self.cubed = False

    def cube(self):
        self.cubed = True


class FruitSalad:
    def __init__(self, *fruit_bowl):
        self.fruit = fruit_bowl
        self._cube_fruit()

    def _cube_fruit(self):
        for fruit in self.fruit:
            fruit.cube()


# Arrange
@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]


def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)

    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)

三、夹具使用

pytest 的最大优势之一是其极其灵活的夹具系统。它使我们能够将复杂的测试需求分解为更简单、更有组织的功能,我们只需要让每个功能描述它们所依赖的东西。

  •   灯具可以请求其他灯具
  • # contents of test_append.py
    import pytest
    
    
    # Arrange
    @pytest.fixture
    def first_entry():
        return "a"
    
    
    # Arrange
    @pytest.fixture
    def order(first_entry):
        return [first_entry]
    
    
    def test_string(order):
        # Act
        order.append("b")
    
        # Assert
        assert order == ["a", "b"]
    
    
    def test_int(order):
        # Act
        order.append(2)
    
        # Assert
        assert order == ["a", 2]
  • 夹具可重复使用
  • # contents of test_append.py
    import pytest
    
    
    # Arrange
    @pytest.fixture
    def first_entry():
        return "a"
    
    
    # Arrange
    @pytest.fixture
    def order(first_entry):
        return [first_entry]
    
    
    def test_string(order):
        # Act
        order.append("b")
    
        # Assert
        assert order == ["a", "b"]
    
    
    def test_int(order):
        # Act
        order.append(2)
    
        # Assert
        assert order == ["a", 2]
  • 一个测试/夹具一次可以请求多个夹具

  • # contents of test_append.py
    import pytest
    
    
    # Arrange
    @pytest.fixture
    def first_entry():
        return "a"
    
    
    # Arrange
    @pytest.fixture
    def second_entry():
        return 2
    
    
    # Arrange
    @pytest.fixture
    def order(first_entry, second_entry):
        return [first_entry, second_entry]
    
    
    # Arrange
    @pytest.fixture
    def expected_list():
        return ["a", 2, 3.0]
    
    
    def test_string(order, expected_list):
        # Act
        order.append(3.0)
    
        # Assert
        assert order == expected_list
  • 自动使用装置

  • # contents of test_append.py
    import pytest
    
    
    @pytest.fixture
    def first_entry():
        return "a"
    
    
    @pytest.fixture
    def order(first_entry):
        return []
    
    
    @pytest.fixture(autouse=True)
    def append_first(order, first_entry):
        return order.append(first_entry)
    
    
    def test_string_only(order, first_entry):
        assert order == [first_entry]
    
    
    def test_string_and_int(order, first_entry):
        order.append(2)
        assert order == [first_entry, 2]
  • 范围:跨类、模块、包或会话共享固定装置

              夹具在测试首次请求时创建,并根据其销毁scope

    • function:默认范围,测试结束时夹具被销毁。

    • class:在类中最后一个测试的拆卸过程中,夹具被破坏。

    • module:在模块中最后一次测试的拆卸过程中,夹具被破坏。

    • package:在定义夹具的包(包括其中的子包和子目录)中最后一个测试的拆卸过程中,夹具被破坏。

    • session:夹具在测试结束时被破坏。

  • 动态范围

  • def determine_scope(fixture_name, config):
        if config.getoption("--keep-containers", None):
            return "session"
        return "function"
    
    
    @pytest.fixture(scope=determine_scope)
    def docker_container():
        yield spawn_container()
  • 拆卸/清理(又名夹具完成)

    • 1.yield固定装置(推荐)

    • 2. 直接添加终结器(终结器按照先进后出的顺序执行

    • @pytest.fixture
      def email(sending_user, receiving_user, request):
          _email = Email(subject="Hey!", body="How's it going?")
          sending_user.send_email(_email, receiving_user)
      
          def empty_mailbox():
              receiving_user.clear_mailbox()
      
          request.addfinalizer(empty_mailbox)
          return _email
      
      
      def test_email_received(receiving_user, email):
          assert email in receiving_user.inbox
    • 3.安全拆解

    • from uuid import uuid4
      from urllib.parse import urljoin
      
      from selenium.webdriver import Chrome
      import pytest
      
      from src.utils.pages import LoginPage, LandingPage
      from src.utils import AdminApiClient
      from src.utils.data_types import User
      
      
      @pytest.fixture
      def admin_client(base_url, admin_credentials):
          return AdminApiClient(base_url, **admin_credentials)
      
      
      @pytest.fixture
      def user(admin_client):
          _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
          admin_client.create_user(_user)
          yield _user
          admin_client.delete_user(_user)
      
      
      @pytest.fixture
      def driver():
          _driver = Chrome()
          yield _driver
          _driver.quit()
      
      
      @pytest.fixture
      def login(driver, base_url, user):
          driver.get(urljoin(base_url, "/login"))
          page = LoginPage(driver)
          page.login(user)
      
      
      @pytest.fixture
      def landing_page(driver, login):
          return LandingPage(driver)
      
      
      def test_name_on_landing_page_after_login(landing_page, user):
          assert landing_page.header == f"Welcome, {user.name}!"
    • 安全地运行多个assert语句

    • # contents of tests/end_to_end/test_login.py
      from uuid import uuid4
      from urllib.parse import urljoin
      
      from selenium.webdriver import Chrome
      import pytest
      
      from src.utils.pages import LoginPage, LandingPage
      from src.utils import AdminApiClient
      from src.utils.data_types import User
      
      
      @pytest.fixture(scope="class")
      def admin_client(base_url, admin_credentials):
          return AdminApiClient(base_url, **admin_credentials)
      
      
      @pytest.fixture(scope="class")
      def user(admin_client):
          _user = User(name="Susan", username=f"testuser-{uuid4()}", password="P4$$word")
          admin_client.create_user(_user)
          yield _user
          admin_client.delete_user(_user)
      
      
      @pytest.fixture(scope="class")
      def driver():
          _driver = Chrome()
          yield _driver
          _driver.quit()
      
      
      @pytest.fixture(scope="class")
      def landing_page(driver, login):
          return LandingPage(driver)
      
      
      class TestLandingPageSuccess:
          @pytest.fixture(scope="class", autouse=True)
          def login(self, driver, base_url, user):
              driver.get(urljoin(base_url, "/login"))
              page = LoginPage(driver)
              page.login(user)
      
          def test_name_in_header(self, landing_page, user):
              assert landing_page.header == f"Welcome, {user.name}!"
      
          def test_sign_out_button(self, landing_page):
              assert landing_page.sign_out_button.is_displayed()
      
          def test_profile_link(self, landing_page, user):
              profile_href = urljoin(base_url, f"/profile?id={user.profile_id}")
              assert landing_page.profile_link.get_attribute("href") == profile_href
    • 夹具可以内省请求的测试上下文

    • 工厂作为固定装置

    • 参数化夹具

    • # content of conftest.py
      import smtplib
      
      import pytest
      
      
      @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
      def smtp_connection(request):
          smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
          yield smtp_connection
          print(f"finalizing {smtp_connection}")
          smtp_connection.close()
    • 模块化:使用固定功能中的固定装置

    • # content of test_appsetup.py
      
      import pytest
      
      
      class App:
          def __init__(self, smtp_connection):
              self.smtp_connection = smtp_connection
      
      
      @pytest.fixture(scope="module")
      def app(smtp_connection):
          return App(smtp_connection)
      
      
      def test_smtp_connection_exists(app):
          assert app.smtp_connection
    • 在类和模块中使用固定装置usefixtures

    • # content of conftest.py
      
      import os
      import tempfile
      
      import pytest
      
      
      @pytest.fixture
      def cleandir():
          with tempfile.TemporaryDirectory() as newpath:
              old_cwd = os.getcwd()
              os.chdir(newpath)
              yield
              os.chdir(old_cwd)
      
      # content of test_setenv.py
      import os
      
      import pytest
      
      
      @pytest.mark.usefixtures("cleandir")
      class TestDirectoryInit:
          def test_cwd_starts_empty(self):
              assert os.listdir(os.getcwd()) == []
              with open("myfile", "w", encoding="utf-8") as f:
                  f.write("hello")
      
          def test_cwd_again_starts_empty(self):
              assert os.listdir(os.getcwd()) == []
    • 使用直接测试参数化覆盖夹具

    • tests/
          conftest.py
              # content of tests/conftest.py
              import pytest
      
              @pytest.fixture
              def username():
                  return 'username'
      
              @pytest.fixture
              def other_username(username):
                  return 'other-' + username
      
          test_something.py
              # content of tests/test_something.py
              import pytest
      
              @pytest.mark.parametrize('username', ['directly-overridden-username'])
              def test_username(username):
                  assert username == 'directly-overridden-username'
      
              @pytest.mark.parametrize('username', ['directly-overridden-username-other'])
              def test_username_other(other_username):
                  assert other_username == 'other-directly-overridden-username-other'
    • 用非参数化夹具覆盖参数化夹具,反之亦然

    • tests/
          conftest.py
              # content of tests/conftest.py
              import pytest
      
              @pytest.fixture(params=['one', 'two', 'three'])
              def parametrized_username(request):
                  return request.param
      
              @pytest.fixture
              def non_parametrized_username(request):
                  return 'username'
      
          test_something.py
              # content of tests/test_something.py
              import pytest
      
              @pytest.fixture
              def parametrized_username():
                  return 'overridden-username'
      
              @pytest.fixture(params=['one', 'two', 'three'])
              def non_parametrized_username(request):
                  return request.param
      
              def test_username(parametrized_username):
                  assert parametrized_username == 'overridden-username'
      
              def test_parametrized_username(non_parametrized_username):
                  assert non_parametrized_username in ['one', 'two', 'three']
      
          test_something_else.py
              # content of tests/test_something_else.py
              def test_username(parametrized_username):
                  assert parametrized_username in ['one', 'two', 'three']
      
              def test_username(non_parametrized_username):
                  assert non_parametrized_username == 'username'



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。