Pytest

Pytest-sovelluskehys mahdollistaa helpon tavan kirjoittaa testejä python-sovelluksille. Pytestin avulla voi testata myös Flask-sovelluksia. Tarkemmin aihetta opetetaan TEKS5466 Ohjelmistotestaus -kurssilla.

Flask-sovellusten testaaminen

Flask-sovellusten testaamisesta kerrotaan tarkemmin dokumentissa Testing Flask Applications.

Kirjoitetaan pieni esimerkkisovellus oma.py:

from flask import Flask, Response, request

import json
import random

app = Flask(__name__)

#muista mahdolliset ongelmat globaalien muuttujien kanssa
nimet = ["foo", "bar", "foobar"]

@app.route('/', methods=["GET"])
def listaa():
    #tässä haettaisiin nimet jostain ja lopulta palautettaisiin html-koodia, jossa nimet ovat
    return "foo, bar ja foobar"

@app.route('/lisaa', methods=["POST"])
def lisaa():
     #lisättäisiin uusi nimi esim. tietokantaan ja palautetaan tieto siitä onnistuiko lisäys vai ei
     #lisätään nyt vain globaaliin listaan
    global nimet
    nimi = request.values.get("nimi", "-")
    #palautetaan satunnainen tulos vaikka lisäys oikeasti aina onnistuisikin
    if random.choice([True, False]):
        nimet.append(nimi)
        return "Onnistui lisätä " + nimi
    else:
        return "Epäonnistui lisätä " + nimi

@app.route('/listaa_json', methods=["GET"])
def listaa_json():
    global nimet
    #tässä haettaisiin nimet jostain ja lopulta palautettaisiin nimet json-muodossa
    #tässä tulee mukana myös edellä lisätty, koska lista on globaalissa muuttujassa ja sama sovellus
    #elää kaikkien testien ajan. pytest ajaa testit tiedostossa olevassa järjestyksessä
    return Response(json.dumps(nimet), mimetype="application/json")

Edellistä sovellusta voitaisiin testata esimerkiksi seuraavilla tavoilla (test_oma.py):

from flask import Flask, Response

import pytest
import json

#ladataan oma sovelluskoodi
import oma

#alustetaan pytestiä varten tarvittavat funktiot
@pytest.fixture()
def app():
    #käytetään omassa sovelluksessa määriteltyä app-muuttujaa
    app = oma.app

    # other setup can go here
    # varo mahdollisia globaaleja muuttujia
    # ne voi tarvittaessa nollata täällä
    # oma.nimet = []
    yield app
    # clean up / reset resources here

#suoritetaan flaskin oma test-klientti
@pytest.fixture()
def client(app):
    return app.test_client()

#varsinaiset testit joille pytest tuo tässä tarvittavan klientin parametrina

def test_listaa1(client):
    response = client.get("/")
    #testataan http-koodi, 200 == ok
    assert 200 == response.status_code, 'Sovelluksen listaussivu ei toimi'
    #samaan tapaan voisi testata myös muita http-koodeja

def test_listaa2(client):
    url = "/"
    # jos haluttaisiin viedä query_stringin mukana parametreja, niin käytettäisiin samanlaista dictiä
    # kuin seuraavassa post-metodin yhteydessä. kutsu muuttuisi muotoon client.get(url, query_string=data)
    response = client.get(url)
    #response.data on se mitä flask-sovellus palauttaa. Data on oletuksena utf-8-koodattua binaaridataa eli aina pitää ensin ajaa decode
    data = response.data.decode("UTF-8")
    #data on nyt html-koodia ja yksinkertaisesti vain etsitään löytyykö kukin merkkijono sivulta
    assert all(nimi in data for nimi in ["foo", "bar", "foobar"]), 'Palautetut nimet ovat väärät' + response.data.decode("UTF-8")

def test_lisaa(client):
    url = "/lisaa"
    #alustetaan post-data eli vastaavat tiedot kuin tulisivat esim. lomakkeelta
    reqdata = {
        "nimi": "barfoo",
    }
    #huomaa, että käytetään nyt client.post eikä client.get
    response = client.post(url, data=reqdata)
    #Kokeillaan onko palautettu data haluttu
    assert "Onnistui" in response.data.decode("UTF-8"), "Lisääminen epäonnistui : " + response.data.decode("UTF-8")


def test_listaajson(client):
    url = "/listaa_json"
    response = client.get(url)
    data = json.loads(response.data)
    #data on nyt json-muodossa tullut pythonin lista. Sama testi kuin edellä toimii tässäkin, mutta tämä on toki tarkempi
    assert all(nimi in data for nimi in ["foo", "bar", "foobar", "barfoo"]), 'Palautetut nimet ovat väärät : ' + response.data.decode("UTF-8")

Suoritetaan testit. Ne onnistuvat satunnaisesti:

[venv] [tjlahton@jalava pytest]$ python -m pytest test_malli.py
================================================= test session starts ==================================================
platform linux -- Python 3.8.14, pytest-7.2.1, pluggy-1.0.0
rootdir: /autowww/itsovpa2/appro.mit.jyu.fi-https/ties4080/luennot/pytest
collected 4 items

test_malli.py ....                                                                                               [100%]

================================================== 4 passed in 0.14s ===================================================
[venv] [tjlahton@jalava pytest]$

Epäonnistunut tulos:

============================= test session starts ==============================
platform linux -- Python 3.8.14, pytest-7.2.1, pluggy-1.0.0
rootdir: /autowww/itsovpa2/appro.mit.jyu.fi-https/ties4080/luennot/pytest
collected 4 items

test_malli.py ..FF                                                       [100%]

=================================== FAILURES ===================================
__________________________________ test_lisaa __________________________________

client = <FlaskClient <Flask 'oma'>>

    def test_lisaa(client):
        url = "/lisaa"
        #alustetaan post-data eli vastaavat tiedot kuin tulisivat esim. lomakkeelta
        reqdata = {
            "nimi": "barfoo",
        }
        response = client.post(url, data=reqdata)
        #Kokeillaan onko palautettu data haluttu
>       assert "Onnistui" in response.data.decode("UTF-8"), "Lisääminen epäonnistui : " + response.data.decode("UTF-8")
E       AssertionError: Lisääminen epäonnistui : Epäonnistui lisätä barfoo
E       assert 'Onnistui' in 'Epäonnistui lisätä barfoo'
E        +  where 'Epäonnistui lisätä barfoo' = <built-in method decode of bytes object at 0x7fae6e603830>('UTF-8')
E        +    where <built-in method decode of bytes object at 0x7fae6e603830> = b'Ep\xc3\xa4onnistui lis\xc3\xa4t\xc3\xa4 barfoo'.decode
E        +      where b'Ep\xc3\xa4onnistui lis\xc3\xa4t\xc3\xa4 barfoo' = <WrapperTestResponse 28 bytes [200 OK]>.data

test_malli.py:46: AssertionError
_______________________________ test_listaajson ________________________________

client = <FlaskClient <Flask 'oma'>>

    def test_listaajson(client):
        url = "/listaa_json"
        response = client.get(url)
        data = json.loads(response.data)
        #data on nyt json-muodossa tullut pythonin lista. Sama testi kuin edellä toimii tässäkin
>       assert all(nimi in data for nimi in ["foo", "bar", "foobar", "barfoo"]), 'Palautetut nimet ovat väärät : ' + response.data.decode("UTF-8")
E       AssertionError: Palautetut nimet ovat väärät : ["foo", "bar", "foobar"]
E       assert False
E        +  where False = all(<generator object test_listaajson.<locals>.<genexpr> at 0x7fae6e61e7b0>)

test_malli.py:54: AssertionError
=========================== short test summary info ============================
FAILED test_malli.py::test_lisaa - AssertionError: Lisääminen epäonnistui : E...
FAILED test_malli.py::test_listaajson - AssertionError: Palautetut nimet ovat...
========================= 2 failed, 2 passed in 0.19s ==========================
                                                                                                   

mallitestit

Käyttäjien kommentit

Kommentoi Lisää kommentti