pytest中,fixture的目的是什么
为可靠的和可重复执行的测试提供固定的基线。(可以理解为测试的固定配置,使不同范围的测试都能够获得统一的配置。)
fixture提供了区别于传统单元测试(setup/teardown)风格的令人惊喜的功能,其实unittest和nose都支持fixture,但是pytest做得更炫:
- 有独立的命名,可以按照测试的用途来激活,比如用于functions/modules/classes甚至整个project。(我遇到的scope主要是session和function)。
- 按模块单元的方式实现,每个fixture name可以出发一个fixture function,每个fixture function本身也能调用其他的fixture function。(相互调用,不只是用于test_func())。
- fixture的范围覆盖简单的单元测试到复杂的功能测试,可用于参数传入或者class、module及test session范围内的复用。
- fixture有明确的名字,在其他函数,模块,类或整个工程调用它时会被激活。
- fixture还提供了参数化功能,根据配置和不同组件来选择不同的参数
- fixture主要的目的是为了提供一种可靠和可重复性的手段去运行那些最基本的测试内容。比如在测试网站的功能时,每个测试用例都要登录和退出,利用fixture就可以只做一次,否则每个测试用例都要做这两步也是冗余。
一、fixture可以作为一个函数的参数被使用
import pytest
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmai.com", 587, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0 #强制断言失败
这里的 test_ehlo函数,需要参数值smtp_connection,pytest就是找到并且调用这个用@pytest.fixture装饰的smtp_connection函数,
换句话讲,被装饰器装饰的函数或者方法,仍然可以被调用。步骤是这样:
- pytest 找到test_ 开头的函数,于是找到了test_ehlo
- test_ehlo这个测试函数,需要一个参数smtp_connection,于是函数smtp_connection被找到
- smtp_connection被调用来创建一个实例
二、fixture可以在一个类、或者一个模块、或者整个session中被共享,加上范围即可
加入scope=’module’的参数,可以让fixture function在每次模块测试的时候只请求一次。这样不同的test function在同一个test module中接收到的 smtpfixture参数都是一样的。
比如
import pytest
@pytest.fixture(scope="module")
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmai.com", 587,说timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0 #强制断言失败
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
assert 0
这里的smtp_connection,就可以在这个文件中,共享使用,同样的
如果想在一个类中使用,那么@pytest.fixture(scope="class")
如果想在全部会话中使用,那么@pytest.fixture(scope="session")
为了方便配置和访问,将这样的fixture放到conftest.py文件中单独存放。
# content of conftest.py
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp():
return smtplib.SMTP("smtp.gmail.com")
在conftest.py所处的目录下:
# content of test_module.py
def test_ehlo(smtp):
response, msg = smtp.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes
def test_noop(smtp):
response, msg = smtp.noop()
assert response == 250
assert 0 # for demo purposes
三、 当出现多个范围装饰的时候,优先实例化范围优先级高的,依次是,session-->module-->session
比如
@pytest.fixture(scope="session")
def s1():
pass
@pytest.fixture(scope="module")
def m1():
pass
@pytest.fixture
def f1(tmpdir):
pass
@pytest.fixture
def f2():
pass
def test_foo(f1, m1, f2, s1):
...
- s1 优先级是最高的,最先实例化它,
- m1 优先级次之
四、 fixture的如何实现teardown
在用unittest的时候,当测试完成后,teardown方法下的代码会执行。
在pytest中,当fixture超出scope指定的范围时,pytest支持执行指定的终结代码,
用yield代替return,所有yield语句之后的代码执行类似teardown的情况:
# content of conftest.py
import smtplib
import pytest
@pytest.fixture(scope="module")
def smtp():
smtp = smtplib.SMTP("smtp.gmail.com")
yield smtp # provide the fixture value
print("teardown smtp")
smtp.close()
上方代码中的 print("teardown smtp")和smtp.close(),会在module范围内的最后一个测试完成后执行,不管测试中有没有exception的状态,
如果我们在装饰器中指定scope="function",那么stmp将会在每次单个测试中建立和清除。
这里yield也可以配合with语句使用: