איך לבצע בדיקות יחידה ב Flask

הקדמה

בדיקה היא חלק בלתי נפרד מתהליך פיתוח תוכנה, והיא מבטיחה שהקוד יתנהג כמצופה וחסר כשלים. בפייתון, pytest היא מערכת בדיקה פופולרית המציעה יתרונות מספרים על מודול unit test המובנה בתוך הספרייה הסטנדרטית של פייתון. pytest כוללת סינטקסיזם פשוט יותר, פלט טוב יותר, תפיסות עוצמתיות ואקוסיסטים מורכבים של תוספים. המדריך הזה יוביל אתכם דרך הקמת אפליקציית Flask, שילוב תפיסות pytest וכתיבת בדיקות ליחידות בשימוש ב- pytest.

הדרישות הקדםיות

לפני שתתחילו, תצטרכו להיות בעלים של:

  • שרת שמריץ את אובונטו ומשתמש לא ראשי עם הרשאות sudo ושורה הילה פעילה. להנחות על הקמת זה, בחרו בהפצה שלכם מתוך רשימה זו והתחילו לעקוב אחרי המדריך הראשוני של השרת. ודאו שאתם עובדים עם גירסה נתמכת של אובונטו.

  • מודעות לשולחן הפקדים של Linux. אתם יכולים לבקר במדריך זה על מקצב השולחן הפקדים של Linux.

  • הבנה בסיסית של התכנות ב Python ועל הפרימרים ב Python pytest. אתם יכולים להגיע למדריך שלנו על מערכת בדיקות Python Pytest כדי ללמוד עוד על pytest.

  • Python 3.7 או גרסה גבוהה יותר מותקנת במערכת Ubuntu שלך. כדי ללמוד כיצד להריץ סקריפט Python על Ubuntu, ניתן לעיין במדריך שלנו על איך להריץ סקריפט Python על Ubuntu.

למה pytest היא חלופה טובה יותר ל-unittest

pytest מציעה כמה יתרונות על פני מסגרת ה-unittest המובנית:

  • Pytest מאפשרת לך לכתוב בדיקות עם פחות קוד בסיסי, תוך שימוש בהצהרות assert פשוטות במקום השיטות המפורטות יותר הנדרשות על ידי unittest.

  • היא מספקת פלט מפורט וקריא יותר, מה שמקל על זיהוי היכן ומדוע בדיקה נכשלה.

  • ה-fixtures של Pytest מאפשרים הקמת בדיקות גמישה יותר וניתנת לשימוש חוזר מאשר השיטות setUp ו-tearDown של unittest.

  • Pytest מקלה על הרצת אותה פונקציית בדיקה עם מספר קבוצות של קלט, מה שלא פשוט לעשות ב-unittest.

  • ל-Pytest יש אוסף עשיר של תוספים שמרחיבים את הפונקציונליות שלה, החל מכלי כיסוי קוד ועד הרצת בדיקות במקביל.

  • Pytest מזהה אוטומטית קבצי בדיקה ופונקציות שתואמים את כללי השמות שלה, ובכך חוסכת זמן ומאמץ בניהול סוויטות בדיקות.

עם התועלות האלה, pytest הוא לרוב הבחירה המועדפת על ידי בדיקות בפיתונים מודרניים בפיתון היום. בואו ניצור אפליקציית Flask וניתן בדיקות יחידות בעזרת pytest.

שלב 1 – הגדרת הסב境

Ubuntu 24.04 משלוח פיתון 3 באופן בר-מצב. פתח את התמונל ובוא נרצה אחרי הפקודה הבאה כדי לבדוק את התקנת פיתון 3:

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

אם פיתון 3 כבר מותקן על המכונה שלך, הפקודה העליונה תחזיר את הגיון הנוכחי של התקנת פיתון 3. במקרה שהוא לא מותקן, ניתן לבדוק את התקנת הפיתון 3 על ידי ביצוע הפקודה הבאה:

root@ubuntu:~# sudo apt install python3

בהמשך, עליך להתקין את השימוש ב pip במערכת שלך:

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

ברגע ש pip תיתקן, בואו נתקע את Flask.

שלב 2 – יצירת אפליקציית Flask

בואו נתחיל ביצירת אפליקציית Flask פשוטה. יצירו מחשבה חדשה לפרוייקט שלך וניווט אליה:

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

עכשיו, בואו ניצור ונעציר סביבה מדומה כדי לנהל תלויות:

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

נ Instal Flask בעזרת pip:

root@ubuntu:~# pip install Flask

עכשיו, בואו ניצור יישומון פלאסק פשוט. יצירה קופסה קטנה בשם app.py והוסף אחר קוד בדיוק:

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)

היישומון הזה יש שלושה נתיבים:

  • /: מחזירה מסר פשוט “הלו, Flask!”.
  • /about: מחזירה מסר פשוט “זוהי דף האודות”.
  • /multiply/<int:x>/<int:y>: מרבה שתי מספרים עצמוניים ומחזירה את התוצאה.

כדי להריץ את היישומון, בואו נבצע את הפקודה הבאה:

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)

מה שאתה יכול לומר מעל הפיצוח הזה הוא שהשרת פועל על http://127.0.0.1 ומקשיב בפורט 5000. פתח עוד קונסולט Ubuntu ובואו נבצע את הפקודות הבאות של curl אחת אחת:

  • 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

בואו נבין מה הבקשות GET האלה עושות:

  1. curl http://127.0.0.1:5000/:
    זו שולחת בקשה GET אל המסלול הישן (‘/’) של היישומנו Flask. השרת מגיב עם מבנה JSON שמכיל את המסר “היי, Flask!”, מדגים את הפונקציה הבסיסית של מסלול הבית שלנו.

  2. curl http://127.0.0.1:5000/about:
    זו שולחת בקשה GET אל המסלול /about. השרת מגיב עם מבנה JSON שמכיל את המסר “זוהי דף האודות.” זה מראה שמסלול שלנו פועל נכון.

  3. curl http://127.0.0.1:5000/multiply/10/20:
    זו שולחת בקשה GET אל המסלול /multiply עם שני פרמטרים: 10 ו-20. השרת מתחלב את המספרים האלה ומגיב עם מבנה JSON שמכיל את התוצאה (200). זה מדגים שמסלול ההרבהה שלנו יכול בצורה נכונה לעבד פרמטרים URL ולבצע חישובים.

הבקשות GET אלה מאפשרות לנו לבוא במגע עם API הקצת של היישומנו Flask, לאסוף מידע או להפעיל פעולות על השרת בלי לשנות מידע. הם שימושיים להוצאת מידע, בדיקת פונקציות של נתיבים, ולבדיקה שהנתיבים מגיבים כפי שצפוי.

בואו נראה את כל אחד מהבקשות הGET האלה בפעולה:

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}

שלב 3 – התקנת pytest וכתיבת הבדיקה הראשונה שלך

עכשיו שיש לך יישום Flask בסיסי, בואו נתקע pytest ונכתוב מספר בדיקות בדינמיות.

התקנו pytest בעזרת pip:

root@ubuntu:~# pip install pytest

יצירו תיקיית בדיקות כדי לאחסן את הקבצים שלך עם הבדיקות:

root@ubuntu:~# mkdir tests

עכשיו, בואו ניצור קובץ חדש בשם test_app.py ונוסף את הקוד הבא:

test_app.py
# ייבא את המודל sys עבור שינוי הסביבה הרצונית של פיתוח הPython
import sys
# ייבא את המודל os עבור אינטראקציות עם המערכת היישומית
import os

# הוסף את התוכנית הורית ל sys.path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# ייבא את המיקום של היישום Flask מקובץ היישום הראשי
from app import app 
# ייבא את pytest עבור כתיבת והוצאת בדיקות
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

בואו נפרד את הפעולות בקובץ הבדיקה הזה:

  1. @pytest.fixture def client():
    זהו מודל בדיקה לפיצוח שיוצר לוויזור בדיקה עבור היישום הFlask שלנו. הוא משתמש בשיטת app.test_client() כדי ליצור לוויזור שיוכל לשלוח בקשות ליישום שלנו בלי להריץ את השרת הממשי. המשפט yield מאפשר לבדיקות להשתמש בלוויזור הזה ואז לסגור אותו בהדרגה אחרי כל בדיקה.
  2. def test_home(client):
    הפונקציה הזו בודקת את הנתיב הראשי (/) של האפליקציה שלנו. היא שולחת בקשת GET לנתיב באמצעות הלקוח הבדיקה, ואז מוודאת שקוד הסטטוס של התגובה הוא 200 (OK) ושתגובה בפורמט JSON תואמת להודעה הצפויה.

  3. def test_about(client):
    בדומה ל-test_home, הפונקציה הזו בודקת את הנתיב 'אודות' (/about). היא מוודאת קוד סטטוס 200 ובודקת את תוכן התגובה בפורמט JSON.

  4. def test_multiply(client):
    הפונקציה הזו בודקת את הנתיב הכפלה עם קלט חוקי (/multiply/3/4). היא מוודאת שקוד הסטטוס הוא 200 ושתגובה בפורמט JSON מכילה את התוצאה הנכונה של ההכפלה.

  5. def test_multiply_invalid_input(client):
    הפונקציה הזו בודקת את הנתיב הכפלה עם קלט לא חוקי (multiply/three/four). היא מוודאת שקוד הסטטוס הוא 404 (לא נמצא), שזהו ההתנהגות הצפויה כאשר הנתיב אינו מצליח להתאים את הקלטים הטקסטואליים לפרמטרים המספריים הנדרשים.

  6. def test_non_existent_route(client):
    הפונקציה הזו בודקת את התנהגות האפליקציה כאשר ניגשים לנתיב שאינו קיים. היא שולחת בקשת GET ל-/non-existent, שאינו מוגדר באפליקציית Flask שלנו. הבדיקה מאשרת שקוד התגובה הוא 404 (לא נמצא), ומוודאת שהאפליקציה שלנו מטפלת נכון בבקשות לנתיבים לא מוגדרים.

בדיקות אלה מכסות את הפונקציונליות הבסיסית של אפליקציית Flask שלנו, ומוודאות שכל נתיב מגיב נכון לקלטים חוקיים ושנתיב ההכפלה מטפל בקלטים לא חוקיים כראוי. באמצעות pytest, ניתן להריץ את הבדיקות הללו בקלות כדי לוודא שהאפליקציה שלנו עובדת כפי שמצופה.

שלב 4 – בהרגלת הבדיקות

כדי לבצע את הבדיקות, ביצוע הפעם הפקודה הבאה:

root@ubuntu:~# pytest

בהקדם מובהק, תהליך גילוי הpytest יסרוק באופן רצף את התיקייה הנוכחית ואת תת תיקיותיה בחיפוש עבור קבצים שמתחילים בשמות של "test_" או מסתיימים בשמות של "_test". הבדיקות הממוקמות בקבצים האלה יובצעות אחר כך. תצלמו שיווק דומה ל:

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

זה מרמז שכל הבדיקות עברו בהצלחה.

שלב 5: שימוש באטימים בpytest

אטימים הם פונקציות שמשתמשות לספק מידע או משאבים לבדיקות. הם יכולים להשתמש להגדר ולהפריד סביבות בדיקה, לטעון מידע או לבצע משימות אחרות של ההגדרה. בpytest, אטימים מוגדרים בעזרת דיוקנט ה@pytest.fixture.

כאן איך לשיפור את האטים הקיים. עדכון את האטים של הלקוח בשימוש בהגדרה ובהפרידה לוגית:

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  # זה המקום בו הבדיקות מתבצעות
    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

ההגדרה הזו מוסיפה את התבטאות הדפוסים כדי להדגיש את השלבים של ההגדלה והריסה בהוצאה לתצוגה בבחינות. הם יכולים להיחלף עם קוד למנהג משאבים אם זקוקים.

בואו ננסה לבצע את הבחינות שוב:

root@ubuntu:~# pytest -vs

הדגל -v מגביר את הפרופורציות, והדגל -s מאפשר לתבטאות להוצג בתוצר הקונסול המקורי.

צריך לראות את התוצאה הבאה:

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

שלב 6: הוספת מקרה בדיקה כשלון

בואו נוסף מקרה בדיקה כשלון לקובץ הבדיקות הקיים. שינוי את הקובץ test_app.py והוסף את הפונקצייה הבאה לקצה האחרון עבור מקרה בדיקה כשלון עבור תוצאה שגויה:

test_app.py
def test_multiply_edge_cases(client):
    """Test the multiply route with edge cases to demonstrate failing tests."""
    # בדיקה עם אפס
    response = client.get('/multiply/0/5')
    assert response.status_code == 200
    assert response.json == {"result": 0}

    # בדיקה עם מספרים גדולים (זה עשוי להיכשל אם לא מטופל בצורה נכונה)
    response = client.get('/multiply/1000000/1000000')
    assert response.status_code == 200
    assert response.json == {"result": 1000000000000}

    # בדיקה מכוונת להיכשל: תוצאה שגויה
    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"

בואו נפרד את פונקציית test_multiply_edge_cases ונסביר מה כל חלק עושה:

  1. בדיקה עם אפס:
    הבדיקה הזו בדוגמה אם הפונקציית הכפלה מטפלת בצורה נכונה בהכפלה על אפס. אנו מצפים שהתוצאה תהיה 0 כשמכפלים מספר בעזרת אפס. זוהי מקרה קצות חשוב לבחינה כיוון שחלק מהמימושים יכולים להיות ב
  2. בדיקה עם מספרים גדולים:
    בדיקה זו מאמתת אם פונקציית ההכפלה יכולה לטפל במספרים גדולים ללא בעיות של גלישה או דיוק. אנו מכפילים שני ערכים של מיליון, מצפים לתוצאה של טריליון אחד. בדיקה זו חיונית מכיוון שהיא בודקת את הגבולות העליונים של יכולת הפונקציה. יש לציין כי ייתכן שתבדיקה זו תיכשל אם יישום השרת אינו מטפל נכון במספרים גדולים, מה שעשוי להצביע על הצורך בספריות מספרים גדולים או בסוג נתונים אחר.
  3. בדיקה מכוונת לכישלון:
    בדיקה זו מוגדרת בכוונה להיכשל. היא בודקת אם 2 * 3 שווה ל-7, שזה כמובן לא נכון. מטרת הבדיקה היא להראות כיצד נראית בדיקה כושלת בפלט הבדיקות. זה מסייע בהבנת האופן שבו ניתן לזהות ולתקן בדיקות כושלות, מיומנות חיונית בתהליכי פיתוח מונחה בדיקות ופתרון בעיות.

על ידי כך שאתם כוללים את המקרים הקצובים האלה ובדיקה מכוונת להיות מוכשלת, אתם בדוגמה, בדוגמה לבדיקה לא רק את הפונקציה הבסיסית של הנתיב המרבה אלא גם את התנהגותו במצבים קיצוניים ויכולתו לדווח על השגיאות. הגישה הזו לבדיקה עוזרת לוודא את העמידות והאמינות של היישום שלנו.

בואו ננסה להריץ את הבדיקות שוב:

root@ubuntu:~# pytest -vs

צריך לראות את התוצאות הבאה:

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.""" # בדיקה עם אפס response = client.get('/multiply/0/5') assert response.status_code == 200 assert response.json == {"result": 0} # בדיקה עם מספרים גדולים (זה עלול להיכשל אם זה לא מנוהל נכון) response = client.get('/multiply/1000000/1000000') assert response.status_code == 200 assert response.json == {"result": 1000000000000} # בדיקה מכוונת להיות מוכשלת: תוצאה לא נכונה 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 ========================================================

המסר הביך הזה מראה שבדיקה test_multiply_edge_cases בקובץ tests/test_app.py היא מוכשלת. בדיוק, האשמה האחרונה בפונקציית הבדיקה הזאת גרמה לכשלון.

בדיקה מכוונת להיות מוכשלת זו מועילה להד

במצב של מערכת אמיתית, היית צריך ל修正 את הקוד כדי לגרום לבחינה להתחבר, או להגדיר את הבחינה בצורה שונה אם התוצאה הצפויה היתה שגויה. אך במקרה זה, הכשלון מכוון למטרות חינוכיות.

סיכוי

בהדרכה זו, עברנו על איך להגדיר בחינות יחידות עבור יישומו של Flask בעזרת pytest, אינטגרציות אפקטים בpytest, והדגמנו את הרעיון של כשלון בבחינה. על ידי עיצוב הצעדים האלה, ניתן לוודא שיישומי Flask שלך הם מאמינים וניתנים לשימור, בדרך זו מיינמצים בugs ומגבירים את איכות הקוד.

ניתן להגיע אל המסמך הרשמי של Flask וPytest כדי ללמוד עוד.

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