【pytest】4.fixture介绍和使用

一、fixture介绍

fixture是pytest的精髓所在,就像unittest中的setup和teardown一样,如果不学fixture那么使用pytest和使用unittest是没什么区别的(个人理解)。

1、fixture用途:

1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等;

2.测试用例的前置条件可以使用fixture实现;

3.支持经典的xunit fixture ,像unittest使用的setup和teardown;

4.fixture可以实现unittest不能实现的功能,如unittest中的测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题

2、fixture介绍:

初始化测试功能。提供一个固定的基线,在此基线基础上,可以更可靠的进行重复测试。基线可以理解为测试的固定配置,使不同范围的测试都能够获得统一的配置。

初始化可以设置服务、状态或其他操作环境。在fixture函数中,每个函数的参数通常在test之后被命名为fixture。

Pytest的fixture相对于传统的xUnit的setup/teardown函数做了显著的改进:

  • fixture有明确的名称,通过在函数、模块、类或整个项目中声明来激活
  • fixture以模块化的方式实现,因为每个fixture名称会触发fixture函数,该fixture函数可以使用其它的fixture函数。
  • fixture不仅可以进行简单的单元测试,也可以进行复杂的功能测试。可以根据配置和组件选项对【fixture和测试】进行参数化,或者跨函数、类、模块或整个测试过程中,重复使用该Fixture函数。
  • 无论使用多少fixture,拆卸逻辑都可以轻松、安全地进行管理,无需手动仔细处理错误,或管理添加清理步骤的顺序。

此外,pytest依然支持经典的xUnit的样式,你可以根据自己的喜好混合两种样式,逐步从经典样式移动到新样式,甚至可以基于现有的unittest.TestCase或者nose的样式来开发

二、fixture函数作为用例参数

1、fixture简单使用

fixture函数定义:通过@pytest.fixture()装饰器装饰一个函数,这个函数就是一个fixture。fixture函数内部可以实现一些初始化和清理操作。

pytest运行测试函数时,会查看该测试函数中的参数,然后搜索与这些参数具有相同名称的fixture。一旦pytest找到这些对象,它就会运行这些fixture。

import pytest
@pytest.fixture()  # fixture函数定义,不带参数时默认scope="function"
def login():
    print("输入账号,密码先登录")
def test_s1(login):
    print("用例1:登录之后其它动作111")
def test_s2(): 
    print("用例2:不需要登录,操作222")

if __name__ == "__main__":
    pytest.main(["-s", "test_fix.py"])

2、详细介绍

函数通过使用@pytest.fixture注册成为一个fixture函数,来为测试用例提供一个Fixture函数。对应的测试用例通过在其参数中使用Fixture函数名称来接收Fixture函数。如下:

import pytest
# 1、smtp_connection通过使用@pytest.fixture注册成为fixture函数,来为用例提供一个Fixture函数
@pytest.fixture()  
def smtp_connection():
	import smtplib
	return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
	
def test_ehlo(smtp_connection):  # 2、使用Fixture函数名称来接收Fixture函数
	response, msg = smtp_connection.ehlo()
	assert response == 250
	assert 0 # 用于调试

这里,test_ehlo需要smtp_connection这个fixture的返回。pytest会在@pytest.fixture的fixture中查找并调用名为smtp_connection的fixture函数。

@pytest.fixture没有参数,则默认scope="function",就是用例运行级别为:函数级。

运行的测试结果如下:

================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
    def test_ehlo(smtp_connection):
        response,msg = smtp_connection.ehlo()
        assert response == 250
>       assert 0 # for demo purposes
E       assert 0
test_smtpsimple.py:11: AssertionError
========================= 1 failed in 0.12 seconds =========================

可看出测试函数test_ehlo调用了smtp_connection,这是由fixture函数创建的smtplib.SMTP()的一个实例。该函数在故意添加的assert 0处失败。步骤如下:

  1. pytest找到以test_作为前缀的测试用例test_ehlo()
  2. test_ehlo()有个名为smtp_connection的入参。而fixture函数中存在一个名为smtp_connection的fixture。所以smtp_connection()被fixture函数调用,用来来创建一个实例。
  3. test_ehlo()被调用,并在最后一行因为断言失败。

pytest --fixtures test_simplefactory.py:查看可用的fixture(添加-v参数,查看以_开头的fixture)

3、fixture:通过依赖注入实现

fixture允许【测试用例函数】轻松的接收和处理【预先定义好的初始化函数】,而不必关心import、setup、cleanup这些细节。

fixture是依赖注入的的一个极佳的示范,fixture函数是注入器,而测试用例函数是fixture的使用者。

三、@pytest.mark.usefixtures('fixture')

1、函数或类前使用@pytest.mark.usefixtures('fixture')装饰器装饰,和使用fixture函数名作为用例参数效果似乎一样,test_fixture.py:

import pytest

@pytest.fixture()
def fixtureFunc():
    print('\n fixture->fixtureFunc')
    
@pytest.mark.usefixtures('fixtureFunc')
def test_fixture():
    print('in test_fixture')

@pytest.mark.usefixtures('fixtureFunc')
class TestFixture(object):
    def test_fixture_class(self):
        print('in class with text_fixture_class')

if __name__=='__main__':
    pytest.main(['-v', 'test_fixture.py'])

2、可以同时指定多个fixtures:

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

3、使用mark的通用特性来为测试module指定fixture(注意:这里的变量只能命名为pytestmark,如果命名为其他变量,如foomark不会工作)

pytestmark = pytest.mark.usefixtures("cleandir")

注意:该标记不会对fixture函数生效,比如下面的代码,不会按照所想的调用my_other_fixture

@pytest.mark.usefixtures("my_other_fixture")
@pytest.fixture
def my_fixture_that_sadly_wont_use_my_other_fixture():...

四、使用autouse参数

1、简单使用:指定fixture的参数autouse=True,这样范围内的每个测试用例会自动调用fixture(这里fixture的作用范围是函数级别的)

# test_fixture.py
import pytest
@pytest.fixture(autouse=True)
def fixtureFunc():
    print('\n fixture->fixtureFunc')

def test_fixture():
    print('in test_fixture')

class TestFixture(object):
    def test_fixture_class(self):
        print('in class with text_fixture_class')

if __name__=='__main__':
    pytest.main(['-v', 'test_fixture.py'])

结果如下,可以看到每个测试用例执行前都自动执行了fixture。

 fixture->fixtureFunc
.in test_fixture

 fixture->fixtureFunc
.in class with text_fixture_class

2、测试用例参数同时接受【普通fixture函数】和【有autouse参数的fixture函数】:

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]

测试函数test_string_only(order, first_entry)的执行情况:

  1. 虽然在测试函数里请求了2个fixture函数,但是order拿到的并不是[]first_entry拿到的也并不是"a"
  2. 因为存在一个autouse=True的fixture函数,所以append_first先会被调用执行。
  3. 在执行append_first过程中,又分别请求了order、 first_entry这2和fixture函数。
  4. 接着,append_first对分别拿到的[]"a"进行append处理,最终返回了["a"]
    所以,断言assert order == [first_entry]是成功的。

五、总结

@pytest.mark.usefixtures('fixture')使用autouse参数,这两种fixture的使用方式是没有直接使用fixture对象,所以无法使用fixture返回的参数的。因为fixture中返回的数据默认存在fixture名字里面存储,所以只能使用第一种方式才可以调用fixture中的返回值。

实际工作中尽量少用auto=True这个参数,可能会引发意想不到的结果! 最常用的还是通过传递参数最好!

原文地址:https://www.cnblogs.com/mind18/p/15621642.html