[PYTHON] Give pytest clean parameters for flask unit tests

Introduction

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.

environment

Separation of test and pre-processing / post-processing

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.

Source to be tested

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

Source of pre-processing and post-processing

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

Test function

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

Execution result

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.

Reuse the test source with multiple parameters.

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.

Definition of test parameters

`@ 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()

Test function

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

Execution result

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.

Try to fail one

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.

in conclusion

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

Give pytest clean parameters for flask unit tests
I wrote unit tests for various languages
Give pytest clean parameters for flask unit tests
Python unit tests
How to make unit tests Part.2 Class design for tests
Unit test flask with pytest
Get query parameters for Flask GET
I wrote unit tests for various languages
Python unit tests
How to make unit tests Part.2 Class design for tests