# Pytest 101 Speaker: Zong-han, Xie https://hackmd.io/PdkOGp6NQDWJVLCxz06vWg --- # Disclaimer --- ~~Spaker only has written almost no test in his projects.~~ --- ## Installation of pytest ```bash conda install pytest ``` or ```bash pip install pytest ``` --- ## First test example --- 1. create a function with the test_ prefix ```python def inc(x): return x + 1 def test_answer(): assert inc(3) == 4 ``` 2. Run the test ```bash pytest or pytest -q test_simple.py ``` ![](https://i.imgur.com/AcXevYT.png) --- ## File names and function names * By default pytest only identifies the file names starting with **test_** or ending with **_test** as the test files. * Pytest requires the test method names to start with **"test"** . ![](https://i.imgur.com/7LbqrJN.png) --- ## Test Error Message ```python def inc(x): return x + 1 def test_answer(): assert inc(3) == 5 # Error ``` ![](https://i.imgur.com/zMYNKB7.png) --- ## Parameterized test ```python import pytest def add(x, y): return x + y @pytest.mark.parametrize("x, y, z", [ (1, 2, 3), (4, 5, 9), (3, 8, 12) # pop error ]) def test_answer(x, y, z): print(x, y, z) assert add(x, y) == z ``` --- ![](https://i.imgur.com/umOiJF5.png) --- ## Skip ```python import pytest def add(x, y): return x + y def test_func(): assert 4 == 5 @pytest.mark.skip @pytest.mark.parametrize("x, y, z", [ (3, 8, 12) # pop error ]) def test_add(x, y, z): assert add(x, y) == z ``` --- ![](https://i.imgur.com/I114jyi.png) --- ## Conditional skip ```python import pytest import sys def add(x, y): return x + y @pytest.mark.skipif(sys.platform != 'win32', reason='Only run on win32') def test_func(): assert 4 == 5 @pytest.mark.parametrize("x, y, z", [ (3, 8, 12) # pop error ]) def test_add(x, y, z): assert add(x, y) == z ``` --- ![](https://i.imgur.com/Lt7USJH.png) --- ## xfail (expected fail) ```python import pytest def add(x, y): return x + y @pytest.mark.xfail def test_func(): assert 4 == 5 ``` ![](https://i.imgur.com/vWhjOPI.png) --- ## Fixture * A software test fixture sets up the system for the testing process by providing it with all the necessary code to initialize it, thereby satisfying whatever preconditions there may be. (wikipedia) * The purpose of test fixtures is to provide a fixed baseline upon which tests can reliably and repeatedly execute. (pytest doc) * Dependency injection --- ## Simple fixture example ``` python import pytest class DBConnection(object): def query(self, stmt): return [('a', 'b', 1), ('d', 'ff', 9)] @pytest.fixture def db(): return DBConnection() def test_query(db): data = db.query('some stmt') # use here to check data property assert len(data[0]) == 4 ``` --- ![](https://i.imgur.com/HZ1WZyM.png) --- ## conftest.py ``` python # conftest.py import pytest class DBConnection(object): def query(self, stmt): return [('a', 'b', 1), ('d', 'ff', 9)] @pytest.fixture def db(): return DBConnection() ``` ``` python # test_answer.py def test_query(db): data = db.query('some stmt') # use here to check data property assert len(data[0]) == 4 ``` --- ![](https://i.imgur.com/k3HvYup.png) --- ## conftest.py: sharing fixture functions * Put fixtures into conftest.py, they will automatically included. * conftest.py can include some settings. * Every python package (a folder with __init__.py) can have their own conftest.py --- ## conftest.py in main/sub directory ```python # __init__.py ``` ``` python # conftest.py import pytest class DBConnection(object): def query(self, stmt): return [('a', 'b', 1), ('d', 'ff', 9)] @pytest.fixture(autouse=True) def db(): return DBConnection() ``` ``` python # test_answer.py def test_query(db): data = db.query('some stmt') assert len(data[0]) == 4 # Pop error ``` --- ## conftest.py in main/sub directory ```python # subdir/__init__.py ``` ``` python # subdir/conftest.py import pytest class DBConnection(object): def query(self, stmt): return [('a', 'b', 1, 9), ('d', 'ff', 9, 11)] @pytest.fixture(autouse=True) def db(): return DBConnection() ``` ``` python # subdir/test_answer.py def test_query(db): data = db.query('some stmt') assert len(data[0]) == 3 # Pop error ``` --- ## 2 errors!! ![](https://i.imgur.com/xqEX2y0.png) --- ## Scope of fixture | Scope | Desc | | -------- | -------- | | function | Run once per test | | class | Run once per class of tests | | module | Run once per module | | session | Run once per session | --- ## Scope of fixture ```python import pytest @pytest.fixture(scope="session") def resource_session(request): print('resource_session INIT') request.addfinalizer( lambda: print('\nIn resource_session\'s end')) @pytest.fixture(scope="module") def resource_module(request): print('resource_module INIT') request.addfinalizer( lambda: print('\nIn resource_module\'s end')) @pytest.fixture(scope="function") def resource_func(request): print('resource_func INIT') request.addfinalizer( lambda: print('\nIn resource_func\'s end')) ``` --- ## Scope of fixture ```python # test_1_2.py def test_one(resource_func): print('In test_one()') def test_two(resource_module): print('\nIn test_two()') ``` ```python # test_3_4.py def test_three( resource_func, resource_session, resource_module): print('\nIn test_three()') def test_four(resource_func, resource_module): print('\nIn test_four()') ``` --- ![](https://i.imgur.com/j5hcg8Y.png) --- ![](https://i.imgur.com/uHASi8I.png) --- * Fixture method is called when the resource is needed. * Resource given by the fixture is finalized according to fixture scope * http://pythontesting.net/framework/pytest/pytest-session-scoped-fixtures/ --- ## fixture X fixture ```python # conftest.py import pytest @pytest.fixture(scope='module', params=['a', 'b']) def resource_1(request): param = request.param return 'resource_1_' + param @pytest.fixture(scope='module', params=['a', 'b']) def resource_2(request): param = request.param return 'resource_2_' + param ``` --- ## fixture X fixture ```python # test_answer.py def test_1(resource_1): print('In test_1: ' + resource_1) def test_2(resource_2): print('In test_2: ' + resource_2) def test_3(resource_1, resource_2): print('In test_3: ' + resource_1 + ', ' + resource_2) ``` --- ## fixture X fixture ![](https://i.imgur.com/RUw9zEg.png) --- ## configuration with conftest.py ```python # conftest.py import pytest import sys def pytest_addoption(parser): parser.addoption( "--cmdopt", action="store", default="type1", help="my option: type1 or type2") def pytest_configure(config): sys._called_from_test = 'True' def pytest_unconfigure(config): del sys._called_from_test def pytest_report_header(config): return "This is header" @pytest.fixture def cmdopt(request): return request.config.getoption("--cmdopt") ``` --- ## configuration with conftest.py ```python # test_answer.py import argparse import sys def test_answer(cmdopt): print("sys._called_from_test: ", sys._called_from_test) if cmdopt == 'type1': print('first') elif cmdopt == 'type2': print('second') assert True ``` --- ![](https://i.imgur.com/7YA2Nam.png) --- More detail please go to * [pytest example](https://pytest.readthedocs.io/en/2.8.7/example/index.html) * [Pytest 還有他的快樂夥伴](https://www.slideshare.net/excusemejoe/pytest-and-friends) * [Introduction to py.test](https://slides.com/jeancruypenynck/introduction-to-pytest/embed#/) * [pytest ALL THE THINGS](https://www.slideshare.net/VincentBernat/pytest-all-the-things) --- # Thank you! # Q&A ---
{"metaMigratedAt":"2023-06-14T21:04:09.904Z","metaMigratedFrom":"YAML","title":"Pytest 101","breaks":true,"contributors":"[{\"id\":\"9c79a496-5a74-4ff1-852a-b129de67d2b0\",\"add\":12185,\"del\":4206}]"}
    901 views