Pytest(3)fixture的使用

fixture

测试fixture的目的是提供一个测试的基线,在此基线基础上,可以更可靠的进行重复测试。Pytest的fixture相对于传统的xUnit的setup/teardown函数做了显著的改进:

  • 测试fixture有明确的名称,通过在函数/模块/类或者整个项目中激活来使用

  • 测试fixture是模块化的实现,使用fixture名即可触发特定的fixture,fixture可以在其他fixture中进行使用

  • 测试fixture不仅可以进行简单的单元测试,也可以进行复杂的功能测试。可以根据配置和组件的选项进行参数化定制测试,或者跨函数/类/模块或者整个测试过程进行测试。

此外,pytest依然支持经典的xUnit的样式,你可以根据自己的喜好混合两种样式,甚至可以基于现

有的unittest.TestCase或者nose的样式来开发
 

作为函数入参的fixture

测试函数可以通过接受一个已经命名的fixture对象来使用他们。对于每个参数名,如果fixture已经声明定义,会自动创建一个实例并传入该测试函数。fixture函数通过装饰器标志@pytest.fixture来注册。

# test_1.py
import pytest 

def testDelDynamic(self, add_dynamic):
  data = {
    "dynamicId": dynamicId
  }
  response = DataRequests().publicRequsts(url=self.url, data=data)
  assert response["statusCode"] == 200
  
# conftest.py
@pytest.fixture(scope="function")
def add_dynamic():  # 增加动态
    addUserDynamic.addUserDynamic()

代码讲解:testDynamic函数想要实现删除动态的接口,但是删除动态,首先要新增动态,所以传入了add_dynamic参数,此时就会去找到conftest下的add_dynamic函数
 

usefixtures

# conftest.py 
import pytest 
import tempfile 
import os 

@pytest.fixture() 
def cleandir(): 
  newpath = tempfile.mkdtemp() 
  os.chdir(newpath) 
  
通过usefixtures标记来使用它: 

# test_setenv.py 
import os 
import pytest 
@pytest.mark.usefixtures("cleandir") 
class TestDirectoryInit(object): 
  def test_cwd_starts_empty(self): 
    assert os.listdir(os.getcwd()) == [] 
    with open("myfile", "w") as f: 
      f.write("hello") 
  def test_cwd_again_starts_empty(self): 
    assert os.listdir(os.getcwd()) == []

因为使用了usefixtures,所以cleandir会在每个测试用例之前被调用,就好像为这些测试指定

了"cleandir"入参一样。 如下是运行测试的结果:

$ pytest ‐q 
.. 																												[100%] 
2 passed in 0.12 seconds

可以同时指定多个fixtures:

@pytest.mark.usefixtures("cleandir", "anotherfixture") 
	def test(): ...

 

usefixtures与传fixture区别

如果fixture有返回值,那么usefixture就无法获取到返回值,这个是装饰器fixture与用例直接传fixture参数的区别
 

conftest.py: 共享fixture函数

定义

实现测试用例的过程中,当你发现需要使用来自多个文件的fixture函数的时候,可以将这些fixture函数放到conftest.py中。

你不需要导入这些fixture函数,它会由pytest自动检索。

fixture函数的检索顺序是从测试类开始,然后测试的模块,然后就是conftest.py文件,最后是内置的插件和第三方插件。

共享测试数

如果要使用数据文件中的测试数据,最好的方法是将这些数据加载到fixture函数中以供测试方法注入使用。这利用到了pytest的自动缓存机制。

另一个好方法是在tests文件夹中添加数据文件。 还有社区插件可用于帮助处理这方面的测试,例如:pytest-datadirpytest-datafiles
 

作用域

  • 一个工程下可以建多个conftest.py的文件,一般在工程根目录下的conftest.py文件起到全局作用,在不同子目录下也可以放conftest.py文件,作用只能在该层目录及以下目录实现
  • conftest在不同的层级间的作用域不一样
  • conftest是不能跨模块调用的

一般情况下,只会在项目根目录下,建立一个conftest.py,提供全局作用域
 

fixture scope的范围参数

之前使用@pytest.fixture(scope='module')来定义框架,scope的参数有以下几种

  • funciton 每一个函数或方法都会调用
  • class 每一个类调用一次,一个类可以有多个方法
  • module 每一个.py文件调用一次,该文件内又有多个function和class
  • session 每个session只运行一次,在自动化测试时,登录步骤可以使用该session

范围:session > module > class > function
 

fixture自动使用autouse=True

当用例很多的时候,每次都传这个参数,会很麻烦。fixture里面有个参数autouse,默认是False没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了

autouse设置为True,自动调用fixture功能

autouse的fixture遵循以下规则:

  • autouse fixture遵守scope的定义,如果autouse fixture的scope为"session",那么这个fixture无论定义在哪儿都只会运行一次,定义为"class"则表示在每个class中只会运行一次。

  • 如果在module中定义了autouse,那么该module中的所有测试用例都会自动使用该fixture

  • 如果在conftest.py中定义了autouse,那么该目录下的所有测试用例都会自动使用该fixture

  • 最后,请谨慎使用该功能,如果你在插件中定义了一个autouse的fixture,那么所有使用了该插件的测试用例都会自动调用该fixture。这种方式在某些情况下是有用的,比如用ini文件配置fixture,这种全局的fixture应该快速有效的确定它应该完成哪些工作,避免代价高昂的导入和计算操作。
     

fixture调用结束/执行清理代码

pytest支持在fixture退出作用域的时候执行相关的清理/结束代码。使用yield而不是return关键字的时候,yield后面的语句将会在fixture退出作用域的时候被调用来清理测试用例
 

yield

@pytest.fixture(scope="function") 
def smtp_connection(): 
    smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) 
    yield smtp_connection 
    print("teardown smtp") 
    smtp_connection.close()
  
# 无论测试是否发生了异常,print及smtp.close()语句将在function测试函数完成之后被执行

除了yield可以实现teardown,在request-context对象中注册addfinalizer方法也可以实现终结函数。
 

addfinalizer

@pytest.fixture(scope="module") 
def smtp_connection(request): 
    smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) 
    def fin(): 
        print("teardown smtp_connection") 
        smtp_connection.close() 
    request.addfinalizer(fin) 
    return smtp_connection

yield和addfinalizer在测试结束之后的调用是基本类似的,addfinalizer主要有两点不同于yield:

  • 可以注册多个完成函数

  • 无论fixture的代码是否存在异常,addfinalizer注册的函数都会被调用,这样即使出现了异常,也可以正确的关闭那些在fixture中创建的资源

所以推荐大家都是用addfinalizer这种方式
 

工厂化的fixtures

工厂化的fixture的模式对于一个fixture在单一的测试中需要被多次调用非常有用。fixture用一个生成数据的函数取代了原有的直接返回数据。该函数可以在测试中被多次调用。

如果需要,工厂也可以携带参数:

@pytest.fixture
def make_customer_record():
    def _make_customer_record(name):
        return { "name": name, "orders": [] }
    return _make_customer_record


def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")
    print(customer_1)
    print(customer_2)
    print(customer_3)
    
    
test_1.py::test_customer_records PASSED                                  [100%]
{'name': 'Lisa', 'orders': []}
{'name': 'Mike', 'orders': []}
{'name': 'Meredith', 'orders': []}

 

fixtures参数化

@pytest.fixture(scope="function", params=["test1", "test2", pytest.param(2, marks=pytest.mark.skip)])
def updateName(request):
    print("teardown执行")
    print("更改后的名字是:", request.param)

    def clean_data():
        print("清理数据")
    request.addfinalizer(clean_data)


def test_data(updateName):
    pass
test_1.py::test_data[test1] 
test_1.py::test_data[test2] 
test_1.py::test_data[2] 

========================= 2 passed, 1 skipped in 0.02s =========================

Process finished with exit code 0
teardown执行
更改后的名字是: test1
PASSED                                       [ 33%]
清理数据
teardown执行
更改后的名字是: test2
PASSED                                       [ 66%]
清理数据
SKIPPED (unconditional skip)                     [100%]
Skipped: unconditional skip

说明:我们执行test_data,实际执行了3条用例,因为fixture里面用了参数化,其中最后一条用例通过标记跳过了,所以最后只执行了2条用例

上一篇:pytest之fixture的详细使用


下一篇:pytest 自动化测试框架(二)