掌握 pytest fixture:优化你的测试代码(一)

277 阅读5分钟

前言

这篇文章开始,深入探究pytest核心功能fixture,感受pytest的强大。

fixture是啥

fixture是pytest特有的功能,它用@pytest.fixture标识,定义在函数前面。在编写测试函数的时候,可以将此函数的名称作为传入参数,pytest会以依赖注入方式将该函数的返回值作为测试函数的传入参数。

基本依赖注入

例如,进行接口测试时,大部分功能需要登录才能使用,我们看看使用fixture如何实现

import pytest
​
@pytest.fixture()
def login():
    print("登录成功")
    return 1def test_01(login):
    print("test_01执行")
    assert login == 1

执行用例test_01时,参数是login,就开始从本用例中查找是否有这个参数或函数。找到login()的函数后,执行它,返回值传入test_01,之后再执行test_01测试函数。

注意:@pytest.fixture()中不带参数时范围默认scope="function"

销毁重置测试数据怎么做呢?

解决办法一: 使用yield代替return,改过的代码如下

import pytest
​
@pytest.fixture()
def login():
    print("登录成功")
    yield 1
    print("后置步骤")
​
def test_01(login):
    print("test_01执行")
    assert login == 1

执行结果如下

登录成功
test_01执行
后置步骤

解决办法二: 使用addfinalizer方法

import pytest
​
@pytest.fixture()
def login(request):
    print("登录成功")
    def add_finalizer():
        print("后置步骤")
    request.addfinalizer(add_finalizer)
    return 1def test_01(login):
    print("test_01执行")
    assert login == 1

执行结果和上面是一样的。fixture函数能够接收一个request参数,表示测试请求的上下文。使用request.addfinalizer方法为fixture添加清理销毁函数。

选择yield还是addfinalizer?

  • addfinalizer可以注册多个终结销毁函数,而yield无法实现多个。
import pytest
​
@pytest.fixture()
def login(request):
    print("登录成功")
    def add_finalizer_01():
        print("后置步骤01")
    def add_finalizer_02():
        print("后置步骤02")
    request.addfinalizer(add_finalizer_01)
    request.addfinalizer(add_finalizer_02)
    return 1def test_01(login):
    print("test_01执行")
    assert login == 1

执行结果如下

登录成功
test_01执行
后置步骤02
后置步骤01

可以看到注册的两个函数都被执行了,但是要特别注意执行顺序,可以看到,执行顺序与注册的顺序相反

不同层级scope

scope有5个级别参数function(默认)、class、module、package和session。

  • function:每个函数或方法都会调用;
  • class:每个类调用一次,一个类可以有多种方法;
  • module:每个.py文件调用一次,该文件内又有多个function和class;·
  • session:多个文件调用一次,可以跨.py文件调用,每个.py文件就是module

执行下面代码,感受一下区别

import pytest
​
@pytest.fixture(scope="function")
def login():
    print("登录成功")
    return 1def test_01(login):
    print("test_01执行")
    assert login == 1def test_02(login):
    print("test_02执行")
    assert login == 1

scope="function"时执行结果如下

登录成功
PASSED                                             [ 50%]test_01执行
登录成功
PASSED                                             [100%]test_02执行

scope="module"时执行结果如下

登录成功
PASSED                                             [ 50%]test_01执行
PASSED                                             [100%]test_02执行

scope="class"时,看下面这个例子

import pytest
​
@pytest.fixture(scope="class")
def login():
    print("登录成功")
    return 1class TestClass:
​
    def test_01(self, login):
        print("test_01执行")
        assert login == 1
​
    def test_02(self, login):
        print("test_02执行")
        assert login == 1

输出结果为

登录成功
PASSED                                  [ 50%]test_01执行
PASSED                                  [100%]test_02执行

fixture为session级别是可以跨.py模块调用的,也就是当有多个.py文件用例的时候,如果多个用例只需调用一次fixture,那就可以设置为scope="session"。既然已经是跨模块,需要在.py模块之上。因此采用一个单独的文件conftest.py,文件名称是固定的,pytest会自动识别该文件。放到工程的根目录下就可以全局调用了,如果放到某个package包下,那就只在该package内有效。

conftest.py

import pytest
​
@pytest.fixture(scope="session")
def login():
    print("登录成功")
    return 1

test_demo01.py

class TestClass:
​
    def test_01(self, login):
        print("test_01执行")
        assert login == 1
​
    def test_02(self, login):
        print("test_02执行")
        assert login == 1

test_demo02.py

class TestClass02:
​
    def test_01_c(self, login):
        print("test_01_c执行")
        assert login == 1
​
    def test_02_c(self, login):
        print("test_02_c执行")
        assert login == 1

执行test_demo01.pytest_demo02.py会发现,登录成功只会打印一次。

自动调用fixture

上面我们使用fixture都是通过传参的方式,这会改变原来测试方法的结构。那如何不通过注入的方式来使用呢?有2种方式可选.

方法一: 在fixture的参数中将autouse参数设置为True,这样便会自动应用所作用的范围。

import pytest
​
@pytest.fixture(autouse=True)
def login():
    print("登录成功")
​
def test_01():
    print("test_01执行")
​
def test_02():
    print("test_02执行")

执行结果如下

登录成功
PASSED                                             [ 50%]test_01执行
登录成功
PASSED                                             [100%]test_02执行

可以看到,我们并没有将fixture通过参数传入测试方法,而是将autouse参数设置为True,这样就会自动调用。

方法二: 使用@pytest.mark.usefixtures,在需要的测试方法上添加。

如果我们只有部分测试方法需要调用,想要更灵活一些,我们就可以选择方法二

import pytest
​
@pytest.fixture()
def login():
    print("登录成功")
​
@pytest.mark.usefixtures("login")
def test_01():
    print("test_01执行")
​
def test_02():
    print("test_02执行")

我们只在test_01方法使用了@pytest.mark.usefixtures("login"),执行会发现只有test_01才会执行login方法中的内容。

最后

fixturepytest的核心,对应内容也比较多,这篇内容主要介绍了fixture基本使用、销毁数据实现、作用范围实例讲解、自动调用fixture,下一篇内容会写关于fixture并列嵌套使用以及重写fixture相关内容。