測試是為了更高效的完成功能實作。
pytest 是基于 unittest 實作的第三方測試架構,比 unittest 更加的簡潔、高效,并且可以完美相容 unittest 的測試代碼,無需對其做任何的修改。
pytest 的使用
使用
pip install pytest
可以直接安裝 pytest 測試架構。
pytest 通過裝飾器「@pytest.fixture」将函數設定為固件,以便于在測試開始前和測試開始後執行相應的操作。在函數中通過 yield 将同一個函數分為兩部分,分别在測試前和測試後執行,避免遺漏資源的釋放。
pytest 通過 conftest.py 檔案進行資料共享,在其它檔案中無需導入即可使用。并且 pytest 會自動識别 conftest.py 檔案,無需顯示指定。可以為子檔案夾單獨設定 conftest.py 檔案。
在 Python 程式設計必不可少的測試架構「unittest 篇」 中講述了 unittest 測試架構的使用,在這裡我們将上一篇中的測試使用 pytest 重新實作,來觀察 unittest 和 pytest 的差別。
我們将所有的公共函數「固件」放入 conftest.py 檔案中,檔案内容大緻如下:
DEFAULT_USERNAME = 'test'
DEFAULT_PASSWORD = 'test'
@pytest.fixture
def app():
db_fd, db_file = tempfile.mkstemp()
app = create_app()
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+ db_file
print('pytest start')
with app.app_context():
db.drop_all()
db.create_all()
user = User.create(
name=DEFAULT_USERNAME,
password=DEFAULT_PASSWORD,
permission=Permission.ADMINISTRATOR,
active=True)
yield app
print('pytest stop')
with app.app_context():
db.session.remove()
db.drop_all()
os.close(db_fd)
os.unlink(db_file)
@pytest.fixture
def clinet(app):
return app.test_client()
@pytest.fixture
def headers(app, clinet):
rv = clinet.post('/api/v01/user/login',
data=json.dumps(dict(user_name='test', password='test')),
content_type='application/json')
data = json.loads(rv.data)
token = data['token']
headers = {"Authorization":"Bearer "+token, 'Content-Type': 'application/json'}
yield headers
pass
複制
conftest.py 檔案實作的内容實際上就是 unittest 中和
setUp
函數的内容。整體實作上更加的簡單明了。
setDown
在測試檔案中可以直接将使用裝飾器
@pytest.fixture
标記的函數以同名參數的方法傳入測試函數中,即可在測試函數中使用相應的功能。同樣以 login 和 add_user 兩個功能的測試為例,實作在 pytest 架構的測試實作:
def test_login(clinet):
rv = clinet.post('/api/v01/user/login',
data=json.dumps(dict(user_name='test', password='test')),
content_type='application/json')
data = json.loads(rv.data)
assert rv.status_code == 200
assert data['status'] == 1
assert data['name'] == 'test'
assert data['token'] isnotNone
assert data['admin'] isnotNone
assert data['expire'] isnotNone
def test_add_user(clinet, headers):
rv = clinet.post('/api/v01/user',
data=json.dumps(dict(user_name='123', password='123', admin=False)),
headers=headers)
data = json.loads(rv.data)
assert rv.status_code == 200
assert data['status'] == 1
複制
在 pytest 中使用 assert 加表達式的方法來對結果進行驗證,而在 unittest 中要通過 assertEqual、assertIn、assertTrue、assertFalse 等等來完成,要記憶的更多實作也更複雜。
使用
pytest
來運作測試執行個體,可以看到如下結果
================================================================================ test session starts ================================================================================
platform darwin -- Python3.7.5, pytest-5.3.3, py-1.8.1, pluggy-0.13.1
rootdir: ***************
collected 4 items
tests/test_user.py .. [ 50%]
tests/unittest/test_user_unittest.py .. [100%]
複制
可以看到測試結果标記了測試進度,并且同步測試了 unittest 的測試用例。你可以通過
-s
參數來顯示測試函數中的 print 輸出内容。
如果你使用
-s
參數來 print 函數的輸出的話,就會看到目前所有的固件「Fixture」在每個測試函數開始和完成時都會執行一次,這不是很浪費資源嗎,是否可以每次測試運作隻執行一次固件呢,答案是可以的,這就要用到固件的作用域了,通過裝飾器
@pytest.fixture(scope='session')
來設定該固件的作用域是整個測試過程。更多内容請看文末的思維導圖。
unittest 和 pytest 的比較
- 固件「Fixture」 在 unittest 中通過固定的函數 setUp 和 tearDown 來實作測試用例的前置和後置函數,并且是針對所有測試用例的。而在 pytest 中通過裝飾器來設定固件的函數命名方式更加的靈活,并且可以将固件設定為函數級、類級、子產品級、以及全局級。pytest 以 conftest.py 作為預設配置實作全局資料共享。
- 斷言實作方式 在 unittest 中将每種判斷方式單獨實作了一個斷言函數,比如 assertEqual、assertIn、assertTrue、assertFalse 等等,使用起來過于麻煩。在 pytest 中直接使用 assert + 表達式的方法來實作,更加清晰明了。
- 參數化 unittest 本身沒有實作參數化的功能,pytest 可以通過裝飾器
快速實作參數化。@pytest.mark.parametrize
pytest 知識點的思維導圖:
公衆号回複 Flask 擷取相關源碼!