owned this note
owned this note
Published
Linked with GitHub
---
title: Pytest 101
tags: python, pytest, test, unit test
description:
---
# 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
```

---
## 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"** .

---
## Test Error Message
```python
def inc(x):
return x + 1
def test_answer():
assert inc(3) == 5 # Error
```

---
## 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
```
---

---
## 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
```
---

---
## 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
```
---

---
## xfail (expected fail)
```python
import pytest
def add(x, y):
return x + y
@pytest.mark.xfail
def test_func():
assert 4 == 5
```

---
## 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
```
---

---
## 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
```
---

---
## 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!!

---
## 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()')
```
---

---

---
* 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

---
## 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
```
---

---
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
---