Last time I used pytest to unit test flask. However, it became very difficult to see because I created and tested Flask's client in one test function. This time, we will use various pytest functions to make the test a little easier to see.
I used to combine the flask client and test source into one function, but it becomes difficult to see as the number of test functions increases. Also, strictly speaking, client creation is not a test, so it is not good to depend on the performance test or the result of the function. Therefore, client creation / deletion is separated from testing.
The source to be tested uses the source of the previous flask.
flask_mod.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def root():
return "root"
@app.route('/sample/<message>')
def sample(message):
return 'sample_' + message
Create pre- and post-processing functions and register them with `@ pytest.fixture`
decoration. This is an image in which the test function is embedded in the yield of this function. Pre-processing is described before yield, and post-processing is described after yield.
In the example, a test client is generated and given to yield. After that, delete deletes the client.
pytest_flask.py
@pytest.fixture
def client():
app.config['TESTING'] = True
test_client = app.test_client()
yield test_client
test_client.delete()
Create a test function and specify the argument that receives the value given to yield in the source of pre-processing and post-processing. This argument must have the same name as the pre-processing and post-processing functions. After that, the test source is listed normally.
In the example, `test_flask_simple ()`
We prepare an argument to receive the client generated by fixture in the argument of the function and issue get to test.
pytest_flask.py
import pytest
from flask_mod import app
@pytest.fixture
def client():
app.config['TESTING'] = True
test_client = app.test_client()
yield test_client
test_client.delete()
def test_flask_simple(client):
result = client.get('/')
assert b'root' == result.data
Now that you have the source for the test target and test method, run it.
PS C:\Users\xxxx\program\python> pytest .\pytest_flask.py
======= test session starts ========
platform win32 -- Python 3.6.5, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: C:\Users\xxxx\program\python\flask
collected 1 item
pytest_flask.py . [100%]
======== 1 passed in 0.21s =========
Looking at the result, the pytest_flask created earlier is 100% and it ends normally. The test was OK and I was able to successfully create and destroy the client using fixture.
I think you often want to create a test source and test it with different arguments. In that case, register with the `@ pytest.fixture ()`
decoration `param`
argument.
`@ pytest.fixture ()`
Decorator params of pre-processing and post-processing functions describes test parameters in tuple list format. Prepare an argument in the function to receive those parameters and give param in yield.
In the example, the source being tested is `sample (message)`
, so the first tuple defines the parameters to give to flask and the second contains the answer.
pytest_flask.py
@pytest.fixture(params=[('message', b'sample_message'),('sample', b'sample_sample')])
def client(request):
app.config['TESTING'] = True
test_client = app.test_client()
yield test_client, request.param
test_client.delete()
Since the value given by `yield`
in the pre-processing and post-processing functions is included in the argument of the test function as a tuple, the necessary elements are extracted and used.
In the example, the `client`
argument contains the first client and the second one of the tuples given in params, so I extracted it and used it for URL and result confirmation.
pytest_flask.py
import pytest
from flask_mod import app
@pytest.fixture(params=[('message', b'sample_message'),('sample', b'sample_sample')])
def client(request):
app.config['TESTING'] = True
test_client = app.test_client()
yield test_client, request.param
test_client.delete()
def test_flask_simple(client):
test_client = client[0]
test_param = client[1]
result = test_client.get('/sample/' + test_param[0])
assert test_param[1] == result.data
Now that you have the source for the test target and test method, run it.
PS Users\xxxx\program\python> pytest -v .\pytest_flask.py
======= test session starts ========
platform win32 -- Python 3.6.5, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- c:\users\xxxx\appdata\local\programs\python\python36-32\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\xxxx\program\python\flask
collected 2 items
pytest_flask.py::test_flask_simple[client0] PASSED [ 50%]
pytest_flask.py::test_flask_simple[client1] PASSED [100%]
======== 2 passed in 0.20s =========
Looking at the result, the test_flask_simple function created earlier is PASSED twice. This means that I gave two tuples in the fixture, so I tested it twice and both were OK.
To make sure that the values are passed properly, give an incorrect value to only one of them.
PS Users\xxxx\program\python> pytest -v .\pytest_flask.py
======= test session starts ========
platform win32 -- Python 3.6.5, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- c:\users\xxxx\appdata\local\programs\python\python36-32\python.exe
cachedir: .pytest_cache
rootdir: C:\Users\xxxx\program\python\flask
collected 2 items
pytest_flask.py::test_flask_simple[client0] FAILED [ 50%]
pytest_flask.py::test_flask_simple[client1] PASSED [100%]
============= FAILURES =============
_______ test_flask_simple[client0] _______
client = (<FlaskClient <Flask 'flask_mod'>>, ('message', b'sample_detail'))
def test_flask_simple(client):
test_client = client[0]
test_param = client[1]
result = test_client.get('/sample/' + test_param[0])
> assert test_param[1] == result.data
E AssertionError: assert b'sample_detail' == b'sample_message'
E At index 7 diff: b'd' != b'm'
E Full diff:
E - b'sample_detail'
E + b'sample_message'
pytest_flask.py:17: AssertionError
============= 1 failed, 1 passed in 0.27s =============
When I gave a value that failed only one, one of the functions became FAILED and failed.
The pytest summarized here is just a small part of the functionality. In addition to this, there are more convenient functions such as a function to automatically create a combination of parameters and a function to save data. However, as is the case this time, there are some quirks in using it, and you may find it difficult to use at first glance. However, it has a lot of convenient and easy-to-use functions, so the more you get used to it, the sooner you can create a variety of tests. How to check the coverage that comes with unit tests is summarized in Check python coverage with pytest-cov.
Recommended Posts