fixture是pytest最核心的技术。
首先放一句“狠话”。
如果你不会fixture,那么你最好别说自己会pytest。
(只是为了烘托主题哈,手上的砖头可以放下了,手动滑稽)
fixture是什么
看看源码
def?fixture( ????callable_or_scope=None, ????*args, ????scope="function", ????params=None, ????autouse=False, ????ids=None, ????name=None ): ????"""Decorator?to?mark?a?fixture?factory?function. ????This?decorator?can?be?used,?with?or?without?parameters,?to?define?a ????fixture?function. ????The?name?of?the?fixture?function?can?later?be?referenced?to?cause?its ????invocation?ahead?of?running?tests:?test ????modules?or?classes?can?use?the?``pytest.mark.usefixtures(fixturename)`` ????marker. ????Test?functions?can?directly?use?fixture?names?as?input ????arguments?in?which?case?the?fixture?instance?returned?from?the?fixture ????function?will?be?injected. ????Fixtures?can?provide?their?values?to?test?functions?using?``return``?or?``yield`` ????statements.?When?using?``yield``?the?code?block?after?the?``yield``?statement?is?executed ????as?teardown?code?regardless?of?the?test?outcome,?and?must?yield?exactly?once. ????:arg?scope:?the?scope?for?which?this?fixture?is?shared,?one?of ????????????????``"function"``?(default),?``"class"``,?``"module"``, ????????????????``"package"``?or?``"session"``?(``"package"``?is?considered?**experimental** ????????????????at?this?time). ????????????????This?parameter?may?also?be?a?callable?which?receives?``(fixture_name,?config)`` ????????????????as?parameters,?and?must?return?a?``str``?with?one?of?the?values?mentioned?above. ????????????????See?:ref:`dynamic?scope`?in?the?docs?for?more?information. ????:arg?params:?an?optional?list?of?parameters?which?will?cause?multiple ????????????????invocations?of?the?fixture?function?and?all?of?the?tests ????????????????using?it. ????????????????The?current?parameter?is?available?in?``request.param``. ????:arg?autouse:?if?True,?the?fixture?func?is?activated?for?all?tests?that ????????????????can?see?it.??If?False?(the?default)?then?an?explicit ????????????????reference?is?needed?to?activate?the?fixture. ????:arg?ids:?list?of?string?ids?each?corresponding?to?the?params ????????????????so?that?they?are?part?of?the?test?id.?If?no?ids?are?provided ????????????????they?will?be?generated?automatically?from?the?params. ????:arg?name:?the?name?of?the?fixture.?This?defaults?to?the?name?of?the ????????????????decorated?function.?If?a?fixture?is?used?in?the?same?module?in ????????????????which?it?is?defined,?the?function?name?of?the?fixture?will?be ????????????????shadowed?by?the?function?arg?that?requests?the?fixture;?one?way ????????????????to?resolve?this?is?to?name?the?decorated?function ????????????????``fixture_``?and?then?use ????????????????``@pytest.fixture(name='')``. ????""" ????if?params?is?not?None: ????????params?=?list(params) ????fixture_function,?arguments?=?_parse_fixture_args( ????????callable_or_scope, ????????*args, ????????scope=scope, ????????params=params, ????????autouse=autouse, ????????ids=ids, ????????name=name, ????) ????scope?=?arguments.get("scope") ????params?=?arguments.get("params") ????autouse?=?arguments.get("autouse") ????ids?=?arguments.get("ids") ????name?=?arguments.get("name") ????if?fixture_function?and?params?is?None?and?autouse?is?False: ????????#?direct?decoration ????????return?FixtureFunctionMarker(scope,?params,?autouse,?name=name)( ????????????fixture_function ????????) ????return?FixtureFunctionMarker(scope,?params,?autouse,?ids=ids,?name=name)
总结一下
【定义】
fixture是一个函数,在函数上添加注解@pytest.fixture来定义 定义在conftest.py中,无需import就可以调用 定义在其他文件中,import后也可以调用 定义在相同文件中,直接调用【使用】
第一种使用方式是@pytest.mark.usefixtures(fixturename)(如果修饰TestClass能对类中所有方法生效) 第二种使用方式是作为函数参数 第三种使用方式是autouse(不需要显示调用,自动运行)conftest.py
我们常常会把fixture定义到conftest.py文件中。
这是pytest固定的文件名,不能自定义。
必须放在package下,也就是目录中有__init__.py。
conftest.py中的fixture可以用在当前目录及其子目录,不需要import,pytest会自动找。
可以创建多个conftest.py文件,同名fixture查找时会优先用最近的。
依赖注入
fixture实现了依赖注入。依赖注入是控制反转(IoC, Inversion of Control)的一种技术形式。
简单理解一下什么是依赖注入和控制反转
实在是妙啊!我们可以在不修改当前函数代码逻辑的情况下,通过fixture来额外添加一些处理。
入门示例
#?content?of?./test_smtpsimple.py import?smtplib import?pytest @pytest.fixture def?smtp_connection(): ????return?smtplib.SMTP("smtp.gmail测试数据",?587,?timeout=5) def?test_ehlo(smtp_connection): ????response,?msg?=?smtp_connection.ehlo() ????assert?response?==?250 ????assert?0??#?for?demo?purposes
执行后程序处理逻辑
pytest找到test_开头的函数,发现需要名字为smtp_connection的fixture,就去找 找到之后,调用smtp_connection(),return了SMTP的实例 调用test_ehlo(如果想看文件定义了哪些fixture,可以使用命令,_前缀的需要跟上-v
pytest?--fixtures?test_simplefactory.py
fixture scope & order
既然到处都可以定义fixture,那多了岂不就乱了?
pytest规定了fxture的运行范围和运行顺序。
fixture的范围通过参数scope来指定
@pytest.fixture(scope="module")
默认是function,可以选择function, class, module, package 或 session。
fixture都是在test第一次调用时创建,根据scope的不同有不同的运行和销毁方式
function ?每个函数运行一次,函数结束时销毁 class ?每个类运行一次,类结束时销毁 module ?每个模块运行一次,模块结束时销毁 package ?每个包运行一次,包结束时销毁 session ?每个会话运行一次,会话结束时销毁fixture的顺序优先按scope从大到小,session > package > module > class > function。
如果scope相同,就按test调用先后顺序,以及fixture之间的依赖关系。
autouse的fixture会优先于相同scope的其他fixture。
示例
import?pytest #?fixtures?documentation?order?example order?=?[] @pytest.fixture(scope="session") def?s1(): ????order.append("s1") @pytest.fixture(scope="module") def?m1(): ????order.append("m1") @pytest.fixture def?f1(f3): ????order.append("f1") @pytest.fixture def?f3(): ????order.append("f3") @pytest.fixture(autouse=True) def?a1(): ????order.append("a1") @pytest.fixture def?f2(): ????order.append("f2") def?test_order(f1,?m1,?f2,?s1): ????assert?order?==?["s1",?"m1",?"a1",?"f3",?"f1",?"f2"]
虽然test_order()是按f1, m1, f2, s1调用的,但是结果却不是按这个顺序
s1 ?scope为session m1 ?scope为module a1 ?autouse,默认function,后于session、module,先于function其他fixture f3 ?被f1依赖 f1 test_order()参数列表第1个 f2 test_order()参数列表第3个fixture嵌套
fixture装饰的是函数,那函数也有入参咯。
fixture装饰的函数入参,只能是其他fixture。
示例,f1依赖f3,如果不定义f3的话,执行会报错fixture 'f3' not found
@pytest.fixture def?f1(f3): ????order.append("f1") @pytest.fixture def?f3(): ????order.append("f3") def?test_order(f1): ????pass
从test传值给fixture
借助request,可以把test中的值传递给fixture。
示例1,smtp_connection可以使用module中的smtpserver
#?content?of?conftest.py import?smtplib import?pytest @pytest.fixture(scope="module") def?smtp_connection(request): ????server?=?getattr(request.module,?"smtpserver",?"smtp.gmail测试数据") ????smtp_connection?=?smtplib.SMTP(server,?587,?timeout=5) ????yield?smtp_connection ????print("finalizing?{}?({})".format(smtp_connection,?server)) ????smtp_connection.close()
#?content?of?test_anothersmtp.py smtpserver?=?"mail.python.org"??#?will?be?read?by?smtp?fixture def?test_showhelo(smtp_connection): ????assert?0,?smtp_connection.helo()
示例2,结合request+mark,把fixt_data从test_fixt传值给了fixt
import?pytest @pytest.fixture def?fixt(request): ????marker?=?request.node.get_closest_marker("fixt_data") ????if?marker?is?None: ????????#?Handle?missing?marker?in?some?way... ????????data?=?None ????else: ????????data?=?marker.args[0] ????#?Do?something?with?the?data ????return?data @pytest.mark.fixt_data(42) def?test_fixt(fixt): ????assert?fixt?==?42
fixture setup / teardown
其他测试框架unittest/testng,都定义了setup和teardown函数/方法,用来测试前初始化和测试后清理。
pytest也有,不过是兼容unittest等弄的,不推荐!
from?loguru?import?logger def?setup(): ????logger.info("setup") def?teardown(): ????logger.info("teardown") def?test(): ????pass
建议使用fixture。
setup,fixture可以定义autouse来实现初始化。
@pytest.fixture(autouse=True)
autouse的fixture不需要调用,会自己运行,和test放到相同scope,就能实现setup的效果。
autouse使用说明
autouse遵循scope的规则,scope="session"整个会话只会运行1次,其他同理 autouse定义在module中,module中的所有function都会用它(如果scope="module",只运行1次,如果scope="function",会运行多次) autouse定义在conftest.py,conftest覆盖的test都会用它 autouse定义在plugin中,安装plugin的test都会用它 在使用autouse时需要同时注意scope和定义位置示例,transact默认scope是function,会在每个test函数执行前自动运行
#?content?of?test_db_transact.py import?pytest class?DB: ????def?__init__(self): ????????self.intransaction?=?[] ????def?begin(self,?name): ????????self.intransaction.append(name) ????def?rollback(self): ????????self.intransaction.pop() @pytest.fixture(scope="module") def?db(): ????return?DB() class?TestClass: ????@pytest.fixture(autouse=True) ????def?transact(self,?request,?db): ????????db.begin(request.function.__name__) ????????yield ????????db.rollback() ????def?test_method1(self,?db): ????????assert?db.intransaction?==?["test_method1"] ????def?test_method2(self,?db): ????????assert?db.intransaction?==?["test_method2"]
这个例子不用autouse,用conftest.py也能实现
#?content?of?conftest.py @pytest.fixture def?transact(request,?db): ????db.begin() ????yield ????db.rollback()
@pytest.mark.usefixtures("transact") class?TestClass: ????def?test_method1(self): ????????...
teardown,可以在fixture中使用yield关键字来实现清理。
示例,scope为module,在module结束时,会执行yield后面的print()和smtp_connection.close()
#?content?of?conftest.py import?smtplib import?pytest @pytest.fixture(scope="module") def?smtp_connection(): ????smtp_connection?=?smtplib.SMTP("smtp.gmail测试数据",?587,?timeout=5) ????yield?smtp_connection??#?provide?the?fixture?value ????print("teardown?smtp") ????smtp_connection.close()
可以使用with关键字进一步简化,with会自动清理上下文,执行smtp_connection.close()
#?content?of?test_yield2.py import?smtplib import?pytest @pytest.fixture(scope="module") def?smtp_connection(): ????with?smtplib.SMTP("smtp.gmail测试数据",?587,?timeout=5)?as?smtp_connection: ????????yield?smtp_connection??#?provide?the?fixture?value
fixture参数化
后续会专门讲“pytest参数化”,这里就先跳过,请各位见谅啦。
因为我觉得想用pytest做参数化,一定是先到参数化的文章里面找,而不是到fixture。
把这部分放到参数化,更便于以后检索。
查看更多关于pytest封神之路第三步 精通fixture的详细内容...