在测试过程当中,fixture均可以派上用场。fixture是在测试函数运行先后,则pytest执行的外壳函数。fixture中的代码能够定制,知足多变的测试需求,包含定义传入测试中的数据集、配置测试前系统的初始状态、为批量测试提供数据源等等。来看如下简单示例,返回一个简单的fixturehtml
import pytest @pytest.fixture() def getData(): return 28 def test_getFixture(getData): assert getData==28
@pytest.fixture()装饰器用于声明函数是一个fixture。若是测试函数的参数列表中包含fixture名称,则pytest会检测到,并在测试函数运行以前执行该fixture。fixture能够完成任务,也能够返回数据给测试函数。python
test_getFixture()的参数列表中包含一个fixture,名为getData,pytest会以该名称搜索fixture。pytest会优先搜索该测试所在模块,而后搜索conftest.pysql
后面所提到的fixture均是由@pytest.fixture()装饰器定义的函数,fixture是pytest用于将测试先后进行预备、清理工做的代码分离出核心逻辑的一种机制。数据库
fixture的特色以下所示:微信
在测试前准备和测试结束后清理环境,在数据库中链接使用比较多。测试前须要链接数据库,测试完成后,须要关闭数据库等,这时就可使用fixture进行配置和清理环境了,以下所示:session
1.DBOperate.pyapp
import sqlite3 import os class DBOperate: def __init__(self,dbPath=os.path.join(os.getcwd(),"db")): self.dbPath=dbPath self.connect=sqlite3.connect(self.dbPath) def Query(self,sql:str)->list: """传统查询语句""" queryResult = self.connect.cursor().execute(sql).fetchall() return queryResult if queryResult else [] def QueryAsDict(self,sql:str)->dict: """调用该函数返回结果为字典形式""" self.connect.row_factory=self.dictFactory cur=self.connect.cursor() queryResult=cur.execute(sql).fetchall() return queryResult if queryResult else {} def Insert(self,sql:str)->bool: insertRows=self.connect.cursor().execute(sql) self.connect.commit() return True if insertRows.rowcount else False def Update(self,sql:str)->bool: updateRows=self.connect.cursor().execute(sql) self.connect.commit() return True if updateRows.rowcount else False def Delete(self,sql:str)->bool: delRows=self.connect.cursor().execute(sql) self.connect.commit() return True if delRows.rowcount else False def CloseDB(self): self.connect.cursor().close() self.connect.close() def dictFactory(self,cursor,row): """将sql查询结果整理成字典形式""" d={} for index,col in enumerate(cursor.description): d[col[0]]=row[index] return d
2.conftest.py函数
import pytest from DBOperate import DBOperate @pytest.fixture() def dbOperate(): # setup:connect db db=DBOperate() # 数据库操做 sql="""SELECT * FROM user_info""" res=db.QueryAsDict(sql) # tearDown:close db db.CloseDB() return res
3.test_02.py测试
import pytest from DBOperate import DBOperate def test_dbOperate(dbOperate): db=DBOperate() sql = """SELECT * FROM user_info""" expect=db.QueryAsDict(sql) res=dbOperate assert expect==res
在fixture中,在执行查询语句前,db=DBOperate()至关于创建数据库链接,可视为配置过程(setup),而db.CloseDB()则至关于清理过程(teardown)过程,不管测试过程发生了什么,清理过程均会被执行。fetch
若是直接运行前面的测试,则看不到其fixture的执行过程,以下所示:
>>> pytest -v .\test_02.py =================== test session starts ================================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 1 item test_02.py::test_dbOperate PASSED [100%] ===================== 1 passed in 0.07s ==================================
若是但愿看到其详细的执行过程及执行的前后顺序,可使用参数--setup-show,以下所示:
>>> pytest --setup-show -v .\test_02.py ====================== test session starts ================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 1 item test_02.py::test_dbOperate SETUP F dbOperate test_02.py::test_dbOperate (fixtures used: dbOperate)PASSED TEARDOWN F dbOperate ============================ 1 passed in 0.03s =================================
从上面的运行的输出结果中能够看到,真正的测试函数被夹在中间,pytest会将每个fixture的执行分红setup和teardown两部分。
fixture名称前面F表明其做用范围,F:表示函数级别,S:表示会话级别
fixture很是适合存放测试数据,且能够返回任何数据,示例以下所示:
import pytest @pytest.fixture() def sampleList(): return [1,23,"a",{"a":1}] def test_sampleList(sampleList): assert sampleList[1]==32
运行结果以下所示:
>>> pytest -v .\test_fixture.py =========================== test session starts ============================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 1 item test_fixture.py::test_sampleList FAILED [100%] ================================= FAILURES =========================================== _________________________test_sampleList ____________________________________________ sampleList = [1, 23, 'a', {'a': 1}] def test_sampleList(sampleList): > assert sampleList[1]==32 E assert 23 == 32 E +23 E -32 test_fixture.py:8: AssertionError ===========================short test summary info ================================= FAILED test_fixture.py::test_sampleList - assert 23 == 32 =========================== 1 failed in 0.20s ======================================
除了指明详细的错误信息以外,pytest还给出了引发assert异常的函数参数值。fixture做为测试函数的参数,也会被归入测试报告中。
上面的示例演示的是异常发生在测试函数中,那若是异常发生在fixture中,会怎么样?
import pytest @pytest.fixture() def sampleList(): x=23 assert x==32 return x def test_sampleList(sampleList): assert sampleList==32
运行结果以下所示:
>>> pytest -v .\test_fixture.py ======================= test session starts ============================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 1 item test_fixture.py::test_sampleList ERROR [100%] ========================== ERRORS ========================================== _________________ ERROR at setup of test_sampleList ________________________ @pytest.fixture() def sampleList(): x=23 > assert x==32 E assert 23 == 32 E +23 E -32 test_fixture.py:6: AssertionError ==================== short test summary info ================================ ERROR test_fixture.py::test_sampleList - assert 23 == 32 =========================== 1 error in 0.27s =================================
在运行的输出结果中,正肯定位到了fixture函数中发生assert异常的位置,其次test_sampleList并无被标记为FAIL,而是被标记为ERROR,这个区分很清楚,若是被标记为FAIL,用户就知道失败发生在核心函数中,而不是发生在测试依赖的fixture中。
示例代码以下所示:
1.DBOperate
import sqlite3 import os class DBOperate: def __init__(self,dbPath=os.path.join(os.getcwd(),"db")): self.dbPath=dbPath self.connect=sqlite3.connect(self.dbPath) def Query(self,sql:str)->list: """传统查询语句""" queryResult = self.connect.cursor().execute(sql).fetchall() return queryResult if queryResult else [] def QueryAsDict(self,sql:str)->dict: """调用该函数返回结果为字典形式""" self.connect.row_factory=self.dictFactory cur=self.connect.cursor() queryResult=cur.execute(sql).fetchall() return queryResult if queryResult else {} def Insert(self,sql:str)->bool: insertRows=self.connect.cursor().execute(sql) self.connect.commit() return True if insertRows.rowcount else False def Update(self,sql:str)->bool: updateRows=self.connect.cursor().execute(sql) self.connect.commit() return True if updateRows.rowcount else False def Delete(self,sql:str)->bool: delRows=self.connect.cursor().execute(sql) self.connect.commit() return True if delRows.rowcount else False def CloseDB(self): self.connect.cursor().close() self.connect.close() def dictFactory(self,cursor,row): """将sql查询结果整理成字典形式""" d={} for index,col in enumerate(cursor.description): d[col[0]]=row[index] return d
2.conftest.py
import pytest from DBOperate import DBOperate @pytest.fixture() def dbOperate(): # setup:connect db db=DBOperate() yield # tearDown:close db db.CloseDB() @pytest.fixture() def mulQuerySqlA(): return ( "SELECT * FROM user_info", "SELECT * FROM case_info", "SELECT * FROM config_paras" ) @pytest.fixture() def mulQuerySqlB(): return ( "SELECT * FROM user_info WHERE account in('admin')", "SELECT * FROM case_info WHERE ID in('TEST-1')", "SELECT * FROM config_paras WHERE accountMinChar==2", "SELECT * FROM report_info WHERE ID in('TEST-1')" ) @pytest.fixture() def mulFixtureA(dbOperate,mulQuerySqlA): db = DBOperate() tmpList=[] for item in mulQuerySqlA: tmpList.append(db.QueryAsDict(item)) return tmpList @pytest.fixture() def mulFixtureB(dbOperate,mulQuerySqlB): db = DBOperate() tmpList = [] for item in mulQuerySqlB: tmpList.append(db.QueryAsDict(item)) return tmpList
3.test_03.py
import pytest def test_count(mulQuerySqlA): assert len(mulQuerySqlA)==3
运行结果以下所示:
>>> pytest -v --setup-show .\test_03.py ========================= test session starts ================================ platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 1 item test_03.py::test_count SETUP F mulQuerySqlA test_03.py::test_count (fixtures used: mulQuerySqlA)PASSED TEARDOWN F mulQuerySqlA ========================== 1 passed in 0.05s ==================================
使用fixture的优点在于:用户编写的测试函数能够只考虑核心的测试逻辑,而不须要考虑测试前的准备工做。
fixture有一个叫scope的可选参数,称为做用范围,经常使用于控制fixture什么时候执行配置和销毁逻辑。@pytest.fixture()一般有4个可选值,分别为function、class、module和session,默认为function。各个scope的描述信息以下所示:
函数级别的fixture每一个测试函数仅运行一次,配置代码在测试函数运行以前运行,清理代码则在测试函数运行以后运行。
类级别的fixture每一个测试类仅运行一次,不管类中有多少个测试方法,均可以共享这个fixture.
模块级别的fixture每一个模块仅运行一次,不管模块中有多少个测试函数、测试方法或其余fixture均可以共享这个fixture
会话级别的fixture每一个会话仅运行一次,一次会话中,全部测试方法和测试函数均可以共享这个fixture。
各个做用范围的scope示例以下所示:
import pytest @pytest.fixture(scope="function") def funcScope(): pass @pytest.fixture(scope="module") def moduleScope(): pass @pytest.fixture(scope="session") def sessionScope(): pass @pytest.fixture(scope="class") def classScope(): pass def test_A(sessionScope,moduleScope,funcScope): pass @pytest.mark.usefixtures("classScope") class TestSomething: def test_B(self): pass def test_C(self): pass
运行结果以下所示:
>>> pytest --setup-show -v .\test_scope.py =================== test session starts =================================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 3 items test_scope.py::test_A SETUP S sessionScope SETUP M moduleScope SETUP F funcScope test_scope.py::test_A (fixtures used: funcScope, moduleScope, sessionScope)PASSED TEARDOWN F funcScope test_scope.py::TestSomething::test_B SETUP C classScope test_scope.py::TestSomething::test_B (fixtures used: classScope)PASSED test_scope.py::TestSomething::test_C test_scope.py::TestSomething::test_C (fixtures used: classScope)PASSED TEARDOWN C classScope TEARDOWN M moduleScope TEARDOWN S sessionScope =========================3 passed in 0.04s ===============================
以上各字母表明了不一样的scope级别,C(class)、M(module)、F(function)、S(Session)。
fixture只能使用同级别或比本身更高级别的fixture。例如函数级别的fixture可使用同级别的fixture,也可使用类级别、模块级别、会话级别的fixture,反之则不行。
除在测试函数列表中指定fixture以外,也能够用@pytest.mark.usefixtures("fixture1","fixture2")标记测试函数或类。这种标记方法对测试类很是适用。以下所示:
@pytest.fixture(scope="class") def classScope(): pass @pytest.mark.usefixtures("classScope") class TestSomething: def test_B(self): pass def test_C(self): pass
使用usefixtures和在测试方法中添加fixture参数,二者并没有太大差异,惟一区别在于后者可使用fixture的返回值。
以前所用到的fixture都是根据测试自己来命名或针对示例中的测试类使用usefixtures,也能够经过指定autouse=True选项,使做用范围内的测试函数都运行该fixture,这种方式很是适合须要屡次运行,但不依赖任何系统状态或外部数据的代码。示例代码以下所示:
import pytest import time @pytest.fixture(autouse=True,scope="session") def endSessionTimeScope(): yield print(f"\nfinished {time.strftime('%Y-%m-%d %H:%M:%S')}") @pytest.fixture(autouse=True) def timeDeltaScope(): startTime=time.time() yield endTime=time.time() print(f"\ntest duration:{round(endTime-startTime,3)}") def test_A(): time.sleep(2) def test_B(): time.sleep(5)
运行结果以下所示:
>>> pytest -v -s .\test_autouse.py ==================== test session starts ======================================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 2 items test_autouse.py::test_A PASSED test duration:2.002 test_autouse.py::test_B PASSED test duration:5.006 finished 2020-05-26 12:35:57 =============================2 passed in 7.13s ====================================
fixture的名字一般显示在使用它的测试或其余fixture函数的参数列表上,通常会和fixture函数名保持一致。pytest也容许使用@pytest.fixture(name="fixtureName")对fixture重命名。示例以下所示:
import pytest @pytest.fixture(name="Surpass") def getData(): return [1,2,3] def test_getData(Surpass): assert Surpass==[1,2,3]
在前面的示例中,使用fixture名字时,是用的函数名,而使用@pytest.fixture(name="Surpass")后,就至关于给fixture取了一别名。在调用fixture时,则可使用别名了。运行结果以下所示:
>>> pytest --setup-show .\test_renamefixture.py =======================test session starts =============================== platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 1 item test_renamefixture.py SETUP F Surpass test_renamefixture.py::test_getData (fixtures used: Surpass). TEARDOWN F Surpass =========================== 1 passed in 0.05s ===============================
若是想找出重命名后的fixture定义,可使用pytest的选项--fixtures,并提供所在测试文件名。pytest可提供全部测试使用的fixture,包含重命名的,以下所示:
>>> pytest --fixtures .\test_renamefixture.py ========================test session starts ================================================= platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 1 item --------------------------- fixtures defined from conftest ---------------------------------- mulQuerySqlA conftest.py:14: no docstring available mulQuerySqlB conftest.py:22: no docstring available mulFixtureA conftest.py:32: no docstring available mulFixtureB conftest.py:40: no docstring available dbOperate conftest.py:5: no docstring available ------------------------- fixtures defined from test_renamefixture ------------------------- Surpass test_renamefixture.py:4: no docstring available
在4.7中已经介绍过测试的参数化,也能够对fixture作参数化处理。下面来演示fixture参数化另外一个功能,以下所示:
import pytest paras=((1,2),(3,5),(7,8),(10,-98)) parasIds=[f"{x},{y}" for x,y in paras] def add(x:int,y:int)->int: return x+y @pytest.fixture(params=paras,ids=parasIds) def getParas(request): return request.param def test_add(getParas): res=add(getParas[0],getParas[1]) expect=getParas[0]+getParas[1] assert res==expect
fixture参数列表中的request也是pytest内建的fixture之一,表明fixture的调用状态。getParas逻辑很是简单,仅以request.param作为返回值供测试用,paras里面有4个元素,所以须要被调用4次,运行结果以下所示:
>>> pytest -v .\test_fixtrueparamize.py ============================ test session starts ================================ platform win32 -- Python 3.7.6, pytest-5.4.2, py-1.8.1, pluggy-0.13.1 -- d:\program files\python\python.exe cachedir: .pytest_cache rootdir: C:\Users\Surpass\Documents\PycharmProjects\PytestStudy\Lesson03 collected 4 items test_fixtrueparamize.py::test_add[1,2] PASSED [ 25%] test_fixtrueparamize.py::test_add[3,5] PASSED [ 50%] test_fixtrueparamize.py::test_add[7,8] PASSED [ 75%] test_fixtrueparamize.py::test_add[10,-98] PASSED [100%] ================================ 4 passed in 0.10s =====================================
原文地址:http://www.javashuo.com/article/p-yksgfrlk-nt.html
本文同步在微信订阅号上发布,如各位小伙伴们喜欢个人文章,也能够关注个人微信订阅号:woaitest,或扫描下面的二维码添加关注: