Hoe uitvoeren u unit tests in Flask

Inleiding

Testen is essentieel aan het softwareontwikkelsproces, het zorgt ervoor dat de code zoals verwacht gaat werken en vrij is van fouten. In Python biedt pytest een populair testframebuffer die verschillende voordelen biedt ten opzichte van de standaard unit test module, die een ingebouwde Python-testframebuffer is en deel uitmaakt van de standaardbibliotheek. pytest beschikt over een eenvoudigere syntaxis, betere uitvoer, krachtige fixsen en een rijke pluginecosystem. Dit handleiding zal u doorstrijden hoe u een Flask-toepassing kunt instellen, pytest fixsen integreert en unit tests schrijft met pytest.

Vereisten

Voordat u begint, heeft u de volgende nodig:

  • Een server die draait op Ubuntu en een niet-rootgebruiker met sudo-privileges en een actieve brandmuur. Voor richtlijnen over hoe u dit kunt instellen, kies uw distributie van deze lijst en volg onze handleiding voor de initiale serverinstelling. Zorg ervoor dat u werkt met een ondersteunde versie van Ubuntu.

  • Bekendheid met de Linux commandoregel. U kunt deze handleiding over Linux commandoregel basis bezoeken.

  • Een basisbegrip van de Python-programmeertechniek en het pytest-testframebuffer in Python. U kunt uw toevoeging aan onze cursus over PyTest Python Testframebuffer om meer te leren over pytest.

  • Python 3.7 of hoger geïnstalleerd op je Ubuntu-systeem. Om te leren hoe je een Python-script op Ubuntu kunt uitvoeren, kun je onze tutorial raadplegen over Hoe een Python-script op Ubuntu uit te voeren.

Waarom pytest een betere keuze is dan unittest

pytest biedt verschillende voordelen ten opzichte van het ingebouwde unittest framework:

  • Pytest stelt je in staat tests te schrijven met minder boilerplate code, door eenvoudige assert-statements te gebruiken in plaats van de meer omslachtige methoden die vereist zijn door unittest.

  • Het biedt meer gedetailleerde en leesbare uitvoer, waardoor het gemakkelijker is te identificeren waar en waarom een test is mislukt.

  • Pytest fixtures bieden meer flexibiliteit en herhaalbare testsetup’s dan de setUp en tearDown methodes van unittest.

  • Het maakt het gemakkelijker om dezelfde testfunctie uit te voeren met meerdere sets van invoer, wat niet zo eenvoudig is in unittest.

  • Pytest heeft een rijke collectie van plugins die zijn functionaliteit vergroten, van code-coverage-tools tot parallelle testuitvoering.

  • Het identificeert automatisch testbestanden en testfuncties die overeenkomen met hun naamgevingsconventies, waardoor tijd en moeite wordt bespaard bij het beheren van testuitlegs.

Given deze voordelen is pytest vaak de voorkeursmethode voor moderne Python-testen. Laten we een Flask-toepassing opzetten en unit tests schrijven gebruik makend van pytest.

Stap 1 – Het milieu opzetten

Ubuntu 24.04 bevat Python 3 standaard. Open het terminal en voer het volgende commando uit om de installatie van Python 3 opnieuw te controleren:

root@ubuntu:~# python3 --version
Python 3.12.3

Als Python 3 reeds op uw machine is geïnstalleerd, zal het bovenstaande commando de huidige versie van de Python 3-installatie teruggeven. Indien het niet geïnstalleerd is, kunt u het volgende commando uitvoeren om Python 3 te installeren:

root@ubuntu:~# sudo apt install python3

Vervolgens moet u de pakketinstallerer pip op uw systeem installeren:

root@ubuntu:~# sudo apt install python3-pip

Zodra pip is geïnstalleerd, laten we Flask installeren.

Stap 2 – Een Flask-toepassing maken

Laten we beginnen met het maken van eenvoudige Flask-toepassing. Maak een nieuw directory voor uw project en ga naar dat directory:

root@ubuntu:~# mkdir flask_testing_app
root@ubuntu:~# cd flask_testing_app

Nu gaan we een virtuele omgeving aanmaken en activeren om afhankelijkheden te beheren:

root@ubuntu:~# python3 -m venv venv
root@ubuntu:~# source venv/bin/activate

Installeer Flask met behulp van `pip`:

root@ubuntu:~# pip install Flask

Nu gaan we een eenvoudige Flask-toepassing maken. Maak een nieuw bestand aan genaamd `app.py` en voeg het volgende codefragment toe:

app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def home():
    return jsonify(message="Hello, Flask!")

@app.route('/about')
def about():
    return jsonify(message="This is the About page")

@app.route('/multiply/<int:x>/<int:y>')
def multiply(x, y):
    result = x * y
    return jsonify(result=result)

if __name__ == '__main__':
    app.run(debug=True)

Deze toepassing heeft drie routes:

  • /: Geeft een eenvoudige “Hello, Flask!” boodschap terug.
  • /about: Geeft een eenvoudige “Dit is de Overige pagina” boodschap terug.
  • /multiply/<int:x>/<int:y>: Vermenigvuldigd twee getallen en geeft het resultaat terug.

Om de toepassing te draaien, voer de volgende opdracht uit:

root@ubuntu:~# flask run
output
* Serving Flask app "app" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

U kunt zien dat de server op http://127.0.0.1 draait en op poort 5000 luistert, uit de bovenstaande uitvoer. Open een andere Ubuntu Console en voer de volgende `curl`-opdrachten één voor één uit:

  • GET: curl http://127.0.0.1:5000/
  • GET: curl http://127.0.0.1:5000/about
  • GET: curl http://127.0.0.1:5000/multiply/10/20

Laten we kijken wat deze GET-verzoeken doen:

  1. curl http://127.0.0.1:5000/:
    Dit stuurt een GET-verzoek naar de root-route (‘/’) van onze Flask-toepassing. De server antwoordt met een JSON-object dat de boodschap “Hello, Flask!” bevat, wat de basisfunctie van onze home-route demonstreert.

  2. curl http://127.0.0.1:5000/about:
    Dit stuurt een GET-verzoek naar de /about-route. De server antwoordt met een JSON-object dat de boodschap “This is the About page” bevat. Dit laat zien dat onze route correct functioneert.

  3. curl http://127.0.0.1:5000/multiply/10/20:
    Dit stuurt een GET-verzoek naar de /multiply-route met twee parameters: 10 en 20. De server vermenigvuldigd deze getallen en antwoordt met een JSON-object dat het resultaat (200) bevat. Dit toont aan dat onze vermenigvuldigingsroute correct processen van URL-parameters en uitvoering van berekeningen kan.

Deze GET-verzoeken laten ons interactie voeren met de API-eindpunten van onze Flask-toepassing, informatie ophalen of acties op de server aansturen zonder gegevens te wijzigen. Ze zijn handig voor het ophalen van gegevens, het testen van eindpuntenfunctionaliteit en het verifiëren dat onze routes zo verwacht worden reageren.

Laat ons nu elk van deze GET-verzoeken in actie zien:

root@ubuntu:~# curl http://127.0.0.1:5000/
Output
{"message":"Hello, Flask!"}
root@ubuntu:~# curl http://127.0.0.1:5000/about
Output
{"message":"This is the About page"}
root@ubuntu:~# curl http://127.0.0.1:5000/multiply/10/20
Output
{"result":200}

Stap 3 – Installeren van pytest en Schrijven van Uw Eerste Test

Nu u een basis Flask-toepassing heeft, kunt u pytest installeren en een paar unit tests schrijven.

Installeer pytest met pip:

root@ubuntu:~# pip install pytest

Maak een tests directory aan om uw testbestanden op te slaan:

root@ubuntu:~# mkdir tests

Nu gaan we een nieuw bestand aanmaken met de naam test_app.py en voegen we het volgende code toe:

test_app.py
# Importeer het sys module voor het wijzigen van het runtime-omgeving van Python
import sys
# Importeer het os module voor het interacteren met het besturingssysteem
import os

# Voeg de bovenliggende map toe aan sys.path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Importeer de Flask app instantie uit het hoofdapp bestand
from app import app 
# Importeer pytest voor het schrijven en uitvoeren van testen
import pytest

@pytest.fixture
def client():
    """A test client for the app."""
    with app.test_client() as client:
        yield client

def test_home(client):
    """Test the home route."""
    response = client.get('/')
    assert response.status_code == 200
    assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
    """Test the about route."""
    response = client.get('/about')
    assert response.status_code == 200
    assert response.json == {"message": "This is the About page"}

def test_multiply(client):
    """Test the multiply route with valid input."""
    response = client.get('/multiply/3/4')
    assert response.status_code == 200
    assert response.json == {"result": 12}

def test_multiply_invalid_input(client):
    """Test the multiply route with invalid input."""
    response = client.get('/multiply/three/four')
    assert response.status_code == 404

def test_non_existent_route(client):
    """Test for a non-existent route."""
    response = client.get('/non-existent')
    assert response.status_code == 404

Laat ons de functies in dit testbestand doorbreken:

  1. @pytest.fixture def client():
    Dit is een pytest-fixture die een testclient voor onze Flask-app creëert. Het gebruikt de app.test_client() methode om een client te maken die verzoeken aan onze app kan sturen zonder het echte server te draaien. Het statement yield laat de client toe in tests te gebruiken en wordt na elke test correct gesloten.

  2. def test_home(client):

    Deze functie test de home route (/) van onze app. Hij stuurt een GET-verzoek naar de route aan met de testclient, en stel dan vast dat de response status code 200 (OK) is en dat de JSON-response overeenkomt met de verwachte boodschap.

  3. def test_about(client):

    Net zoals test_home, test deze functie de about route (/about). Hij controleert op een 200 status code en verifieert de inhoud van de JSON response.

  4. def test_multiply(client):

    Deze functie test de multiply route met geldige invoer (/multiply/3/4). Hij controleert of de status code 200 is en of de JSON response de juiste resultaten van de vermenigvuldiging bevat.

  5. def test_multiply_invalid_input(client):
    Deze functie test de multiply-route met een ongeldige invoer (multiply/three/four). Hij controleert of de statuscode 404 (Niet Gevonden) is, wat de verwachte gedrag is wanneer de route geen overeenkomst vindt tussen de tekstinvoer en de vereiste gehele getallenparameters.

  6. def test_non_existent_route(client):
    Deze functie test het gedrag van de app wanneer een bestaande route wordt aangeroepen. Hij stuurt een GET-verzoek aan /non-existent, die niet is gedefinieerd in onze Flask-app. De test stelt vast dat de reactie statuscode 404 (Niet Gevonden) is, waardoor we erop kunnen controleren dat onze app juist handelingen uitvoert bij verzoeken naar niet-bestaande routes.

Deze tests Coveren de basisfunctionaliteit van onze Flask-app, ervan uitgaande dat elke route correct reageert op geldige invoer en dat de multiply-route ongeldige invoer behandelt op de juiste wijze. Door pytest te gebruiken, kunnen we deze tests gemakkelijk uitvoeren om er voor te zorgen dat onze app zoals verwacht werkt.

Stap 4 – Testen uitvoeren

Om de testen uit te voeren, voer de volgende opdracht uit:

root@ubuntu:~# pytest

Standaard zal de pytest ontdekkingproces recursief door de huidige map en haar submappen zoeken naar bestanden die beginnen met “test_” of eindigen met “_test”. De testen die in deze bestanden zijn gelegen, worden vervolgens uitgevoerd. U zou output zien die er ongeveer uitziet:

Output
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 rootdir: /home/user/flask_testing_app collected 5 items tests/test_app.py .... [100%] ======================================================= 5 passed in 0.19s ========================================================

Dit geeft aan dat alle tests succesvol zijn afgelopen.

Stap 5: Fixtures gebruiken in pytest

Fixtures zijn functies die worden gebruikt om gegevens of resources aan tests te verschaffen. Ze kunnen worden gebruikt om testomgevingen in te stellen en af te breken, gegevens te laden of andere opsettaakken uit te voeren. In pytest worden voorzieningen gedefinieerd met behulp van de decorator @pytest.fixture.

Hier is hoe u de bestaande voorziening kunt verbeteren. Bijwerken van de client voorziening om setup- en teardown-logica te gebruiken:

test_app.py
@pytest.fixture
def client():
    """Set up a test client for the app with setup and teardown logic."""
    print("\nSetting up the test client")
    with app.test_client() as client:
        yield client  # Dit is waar de testen gebeuren
    print("Tearing down the test client")

def test_home(client):
    """Test the home route."""
    response = client.get('/')
    assert response.status_code == 200
    assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
    """Test the about route."""
    response = client.get('/about')
    assert response.status_code == 200
    assert response.json == {"message": "This is the About page"}

def test_multiply(client):
    """Test the multiply route with valid input."""
    response = client.get('/multiply/3/4')
    assert response.status_code == 200
    assert response.json == {"result": 12}

def test_multiply_invalid_input(client):
    """Test the multiply route with invalid input."""
    response = client.get('/multiply/three/four')
    assert response.status_code == 404

def test_non_existent_route(client):
    """Test for a non-existent route."""
    response = client.get('/non-existent')
    assert response.status_code == 404

Deze instelling voegt afdrukstatements toe om de setup- en teardown-fases te demonstreren in de testuitvoer. Deze kunnen vervangen worden door actueel resourcebeheercode indien nodig.

Probeer nu de tests opnieuw uit te voeren:

root@ubuntu:~# pytest -vs

De -v vlag verhoogt de uitgebreide uitvoer en de -s vlag laat afdrukstatements zien in de console-uitvoer.

U zou de volgende uitvoer moeten zien:

Output
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 rootdir: /home/user/flask_testing_app cachedir: .pytest_cache collected 5 items tests/test_app.py::test_home Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_about Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply_invalid_input Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_non_existent_route Setting up the test client PASSED Tearing down the test client ============================================ 5 passed in 0.35s =============================================

Step 6: Adding a Failure Test Case

Voeg nu een falende testcase toe aan het bestaande testbestand. Wijzig het bestand test_app.py en voeg de onderstaande functie aan het eind toe voor een falende testcase voor een onjuist resultaat:

test_app.py
def test_multiply_edge_cases(client):
    """Test the multiply route with edge cases to demonstrate failing tests."""
    # Test with zero
    response = client.get('/multiply/0/5')
    assert response.status_code == 200
    assert response.json == {"result": 0}

    # Test with large numbers (this might fail if not handled properly)
    response = client.get('/multiply/1000000/1000000')
    assert response.status_code == 200
    assert response.json == {"result": 1000000000000}

    # Intentional failing test: incorrect result
    response = client.get('/multiply/2/3')
    assert response.status_code == 200
    assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case"

Bekijk nu de test_multiply_edge_cases functie op de volgende wijze en verklaar waarover elk deel beschikt:

  1. Test with zero:
    Deze test controleert of de multiply functie correct deelt door nul. We verwachten dat het resultaat 0 is als je elke getal door nul deelt. Dit is een belangrijke randgevaltest want sommige implementaties kunnen problemen hebben met deelen door nul.

  2. Test met grote getallen:
    Deze test controleert of de functie ‘vermenigvuldig’ grote getallen kunt behandelen zonder overloop of nauwkeurigheidproblemen. We vermenigvuldigen twee miljoen waarden en verwachten een resultaat van een biljoen. Deze test is crucial omdat hij de bovengrenzen van de functie’s capaciteit controleert. Merk op dat dit mogelijk mis zal lopen als de serverimplementatie niet goed grote getallen behandelt, wat kan wijzen op een behoefte aan bibliotheken voor grote getallen of een ander datatype.

  3. Ongeveer te falen test:
    Deze test is opzettelijk ingesteld om te falen. Hij controleert of 2 * 3 gelijk is aan 7, wat onjuist is. Deze test heeft als doel te demonstreren hoe een falende test eruitziet in de testuitvoer. Dit helpt bij het begrijpen hoe falende tests te identificeren en debuggen, wat een essentieel vaardigheid is in testgerichte ontwikkeling en debugproces.

Door deze randgevallen inclusief een opzettelijke mislukking toe te voegen, test je niet alleen de basisfunctionaliteit van uw vermenigvuldigingsroute, maar ook zijn gedrag onder extreme omstandigheden en zijn foutmeldingscapaciteiten. Dit testen helpt ervoor zorgen dat onze toepassing robuust en betrouwbaar is.

Probeer nu de tests opnieuw uit te voeren:

root@ubuntu:~# pytest -vs

U zou de volgende uitvoer moeten zien:

Output
platform linux -- Python 3.12.3, pytest-8.3.2, pluggy-1.5.0 rootdir: /home/user/flask_testing_app cachedir: .pytest_cache collected 6 items tests/test_app.py::test_home Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_about Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply_invalid_input Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_non_existent_route Setting up the test client PASSED Tearing down the test client tests/test_app.py::test_multiply_edge_cases Setting up the test client FAILED Tearing down the test client ================================================================= FAILURES ================================================================== _________________________________________________________ test_multiply_edge_cases __________________________________________________________ client = <FlaskClient <Flask 'app'>> def test_multiply_edge_cases(client): """Test the multiply route with edge cases to demonstrate failing tests.""" # Test met nul response = client.get('/multiply/0/5') assert response.status_code == 200 assert response.json == {"result": 0} # Test met grote getallen (dit kan mislukken als het niet goed wordt afgehandeld) response = client.get('/multiply/1000000/1000000') assert response.status_code == 200 assert response.json == {"result": 1000000000000} # Onbedoeld falende test: onjuist resultaat response = client.get('/multiply/2/3') assert response.status_code == 200 > assert response.json == {"result": 7}, "This test should fail to demonstrate a failing case" E AssertionError: This test should fail to demonstrate a failing case E assert {'result': 6} == {'result': 7} E E Differing items: E {'result': 6} != {'result': 7} E E Full diff: E { E - 'result': 7,... E E ...Full output truncated (4 lines hidden), use '-vv' to show tests/test_app.py:61: AssertionError ========================================================== short test summary info ========================================================== FAILED tests/test_app.py::test_multiply_edge_cases - AssertionError: This test should fail to demonstrate a failing case ======================================================== 1 failed, 5 passed in 0.32s ========================================================

Het bovenstaande foutmelding duidt op het mislukken van de test test_multiply_edge_cases in het bestand tests/test_app.py. Specifiek werd de laatste assert in deze testfunctie de oorzaak van het mislukken.

Deze opzettelijke mislukking is handig voor het demonstreren hoe fouten in tests worden gemeld en welke informatie er in het foutmeldingsbericht staat. Het toont de exacte regel waar het mislukken gebeurde, de verwachte en de actuele waarden, en de verschillen tussen beide.

In een echte-wereld situatie zou u de code repareren om de test te laten slagen of de test aanpassen als het verwachte resultaat onjuist was. Echter, in dit geval is het falen opzettelijk voor educatieve doeleinden.

Conclusie

In deze handleiding hebben we gezien hoe u unit tests kunt instellen voor een Flask-toepassing met behulp van pytest, geïntegreerde pytest-fixtures en we hebben getoond wat een testfout eruitziet. door deze stappen te volgen, kunt u ervoor zorgen dat uw Flask-toepassingen betrouwbaar en onderhoudsbaar zijn, bugs minimaliseren en de kwaliteit van de code verhogen.

U kunt zich verdiepen in de Flask– en de Pytest-officiële documentatie voor meer informatie.

Source:
https://www.digitalocean.com/community/tutorials/unit-test-in-flask