【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处失败。步骤如下:
- pytest找到以
test_
作为前缀的测试用例test_ehlo()
。 test_ehlo()
有个名为smtp_connection的入参。而fixture函数中存在一个名为smtp_connection的fixture。所以smtp_connection()被fixture函数调用,用来来创建一个实例。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)
的执行情况:
- 虽然在测试函数里请求了2个fixture函数,但是
order
拿到的并不是[]
,first_entry
拿到的也并不是"a"
。 - 因为存在一个
autouse=True
的fixture函数,所以append_first
先会被调用执行。 - 在执行
append_first
过程中,又分别请求了order、 first_entry
这2和fixture函数。 - 接着,
append_first
对分别拿到的[]
和"a"
进行append处理,最终返回了["a"]
。
所以,断言assert order == [first_entry]
是成功的。
五、总结
@pytest.mark.usefixtures('fixture')
和使用autouse参数
,这两种fixture的使用方式是没有直接使用fixture对象,所以无法使用fixture返回的参数的。因为fixture中返回的数据默认存在fixture名字里面存储,所以只能使用第一种方式才可以调用fixture中的返回值。
实际工作中尽量少用auto=True这个参数,可能会引发意想不到的结果! 最常用的还是通过传递参数最好!