תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

בדיקות יחידה unit test בפייתון באמצעות pytest

מחבר:
בתאריך:

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

python pytest unit test tutorial

Unit tests נכתבים לרוב כקבצים או מודולים נפרדים שמכילים את המקרים אותם רוצים לבחון test cases. כל מקרה בוחן test case כולל סדרה של תנאים או קלטים בהם משתמשים כדי לוודא את התנהגות ופלט היחידה unit אותה מעוניינים לבחון.

3 הספריות העיקריות של פייתון המשמשות ל-unit tests הם: unittest, nose ו-pytest. לכל אחת יתרונות וחסרונות בהתאם למקרי השימוש. במדריך זה נלמד על pytest הנחשבת לקלה יחסית ללמידה ועדיין חזקה וגמישה.

את pytest צריך להתקין. לדוגמה, בעזרת מתקין החבילות pip:

$ pip install pytest

נתחיל מקוד פשוט שבוחן פעולת חיבור וחיסור שאותו נכתוב לתוך קובץ:

calculator.py

def add(x, y=0):
    return x+y

def subtract(x, y=0):
    return x-y

את הבחינה unit test נעשה בקובץ נפרד ששמו כשם הקובץ המקורי עם קידומת test_. בדוגמה שלנו שם הקובץ המקורי calculator.py ועל כן קובץ הבחינה יקרא test_calculator.py.

את קובץ המבחן נקפיד לשים באותה התיקייה כמו הקובץ המקורי.

בראש הקובץ test_calculator.py נייבא את הספרייה pytest ובהמשך את הסקריפט הנבחן calculator.

בהמשך הקובץ נכתוב את הפונקציות הבוחנות. כל אחת מהם מתחילה בקידומת test_. בתוך הפונקציות נשתמש ב-assert עבור התרחישים השונים שאותם רוצים לבחון/לוודא.

לדוגמה, בחינה האם הפונקציה add() אשר מקבלת שני פרמטרים 2 ו-3 מחזירה 5:

assert add(2, 3) == 5

נוסיף לתוך קובץ הבדיקה test_calculator.py 2 פונקציות שתפקידם לבחון את הפונקציות add() ו-subtract(). לפונקציות המבחן נקרא על שם הפונקציות שאנחנו רוצים לבחון עם תוספת test_ בתחילת השם. במקרה שלנו שמות פונקציות המבחן יהיו test_add() ו-test_subtract():

test_calculator.py

import pytest
import calculator

def test_add():
   assert calculator.add(2, 3) == 5
   assert calculator.add(5, -2) == 3
   assert calculator.add(0, 0) == 0
   assert calculator.add(10) == 10  # Testing default argument

def test_subtract():
   assert calculator.subtract(5, 2) == 3
   assert calculator.subtract(10, 5) == 5
   assert calculator.subtract(0, 0) == 0
   assert calculator.subtract(10) == 10  # Testing default argument
  • בשביל לבחון מגוון של תרחישים, בפרט תרחישי קיצון נהוג להשתמש ב-unit test. בדוגמה שלנו אנחנו בוחנים את שתי הפונקציות add() באמצעות test_add() ו-subtract() באמצעות test_subtract(), והתרחישים הם מגוונים: מה קורה אם אחד המספרים שליליים, מה קורה אם שני המספרים הם אפס, מה קורה אם לא מעבירים פרמטר ברירת מחדל. מצד אחד אנחנו מזינים את הארגומנטים לפונקציה ומצד שני את התוצאה המצופה כאשר פקודת assert() מוודאת שאנחנו מקבלים את מה שאנחנו מצפים.

נריץ את ה-unit test באמצעות הרצת הפקודה הבאה בטרמינל:

$ pytest -v
  • הפקודה היא pytest כאשר האופציה v (קיצור של verbose) גורמת לתגובה מפורטת

התוצאה:

collected 2 items

test_calculator.py::test_add PASSED [ 50%]
test_calculator.py::test_subtract PASSED [100%]

== 2 passed in 0.01s ==
  • מעידה על כך ששני המבחנים עברו.

כל המבחנים שראינו בחנו שוויון אבל אנחנו יכולים להשתמש בכל השוואה אחרת. לדוגמה:

assert calculator.add(8) > 0
assert calculator.add(0) <= 0

וגם:

assert calculator.add(8) != 3

בחינה נוספת היא האם התוצאה מכילה (in) או שאינה מכילה (not in) את מה שאנחנו מעוניינים. לדוגמה, נבחן חיבור מחרוזות על ידי הפונקציה add של calculator. לצורך כך נוסיף את הפונקציה הבאה לקובץ הבדיקה test_calculator.py:

def test_add_strings():
    result = calculator.add("Hello", " unit test")
    assert result == "Hello unit test"
    assert type(result) is str
    assert "Hello" in result
    assert "world" not in result

נסביר:

  • נוודא שהתוצאה היא אכן מחרוזת באמצעות הפונקציה type:

    assert type(result) is str
  • באמצעות in נוודא שהתוצאה מכילה את המחרוזת "Hello":

    assert "Hello" in result
  • באמצעות not in נוודא ש-"world" לא קיים בתוצאה:

    assert "world" not in result

נריץ:

$ pytest -v

התוצאה:

collected 3 items

test_calculator.py::test_add PASSED [ 33%]
test_calculator.py::test_subtract PASSED [ 66%]
test_calculator.py::test_add_strings PASSED [100%]

==== 3 passed in 0.01s ====

 

כמה אופציות מועילות של pytest

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

אפשר להציג רק את המידע המסכם על ידי שימוש בדגל q (קיצור של quite):

$ pytest -v -q

התוצאה במקרה שלנו:

collected 3 items

test_calculator.py ... [100%]

===== 3 passed in 0.01s ====
  • כל נקודה מציינת מבחן שעבר. במקרה שלנו עברו שלושה מבחנים עברו ולכן אפשר לראות 3 נקודות.
  • במקרה של כשלון המבחן היה מופיע F במקום נקודה.

אפשר להריץ רק את אחד המבחנים. לדוגמה, נריץ רק את הפונקציה test_add():

$ pytest -v test_calculator.py::test_add
  • הפקודה היא pytest והאופציה -v בשביל תגובה מפורטת. שם הסקריפט לפני הנקודתיים, ושם הפונקציה אותה מעוניינים להריץ באופן ספציפי מופיעה אחרי הנקודתיים.

אפשר להריץ לפי דפוס לדוגמה את כל המבחנים ששמם מכיל "add":

$ pytest -v -k "add"

שימוש באופציה -k מאפשר להכניס אופרטורים לוגיים לדפוס המזהה את הפונקציות אותם רוצים להריץ. לדוגמה, להריץ רק פונקציות שיש בשמם "add" וגם "string" באמצעות האופרטור and:

$ pytest -v -k "add and string"

אפשר להשתמש באופרטור or כדי להריץ פונקציות ששמם מכיל את אחת המחרוזות. לדוגמה: "add" או "string":

$ pytest -v -k "add or string"

 

בחינה עם האופציה "-m"

אפשר להוסיף דקורטור pytest.mark מעל לפונקציות ולתת להן שמות כדי להריץ אחר כך רק את הפונקציות שיש להם את התכונה הרצויה. כך נראה דקורטור מסוג זה עם התכונה "int":

@pytest.mark.int
  • הבחירה ב-"int" היא שלי, אישית ושרירותית. אפשר לבחור בשמות אחרים לתכונות לפי מה שצריך.

נוסיף לכל אחת מהפונקציות דקורטור mark עם אחד משני שמות, "int" או "str":

import calculator
import pytest

@pytest.mark.int
def test_add():
    assert calculator.add(8,2) == 10
    assert calculator.add(8) == 8

@pytest.mark.int
def test_subtract():
    assert calculator.subtract(8,2) == 6
    assert calculator.subtract(8) == 8

@pytest.mark.str
def test_add_strings():
    result = calculator.add("Hello", " unit test")
    assert "Hello" in result

נריץ את המבחן רק על הפונקציות שקיבלו את הדקורטור mark בעלות תכונה "int" בלבד:

$ pytest -v -m "int"

מה שיגרום לשגיאה כי שכחנו להוסיף את הקובץ pytest.ini ובתוכו הגדרות הקונפיגורציה המגדירות את ה-markerים הצפויים אז נוסיף את הקובץ לתוך אותה התיקייה האחת והיחידה שאנחנו עובדים איתה לכל אורך המדריך:

pytest.ini

[pytest]
markers =
   str: mark a test as a string.
   int: mark a test for an int.

נריץ את המבחן שיריץ אך ורק את הפונקציות שיש להם את התכונה int של הדקורטור mark:

$ pytest -v -m "int"

התוצאה:

collected 3 items / 1 deselected / 2 selected

test_calculator.py::test_add PASSED      [ 50%]
test_calculator.py::test_subtract PASSED [100%]

===== 2 passed, 1 deselected in 0.01s ====

 

אפשר להשתמש בדקורטור mark כדי לדלג על הרצה של פונקציות על ידי זה שמוסיפים לדקורטור את האופציה skip עם ארגומנט reason. לדוגמה, נדלג על הרצת הפונקציה test_add() על ידי כך שנוסיף מעליה את הדקורטור עם הארגומנט
@pytest.mark.skip(reason="No need right now!"). :

@pytest.mark.skip(reason="No need right now!")
@pytest.mark.int
def test_add():
   assert calculator.add(8,2) == 10
   assert calculator.add(8) == 8

נריץ:

$ pytest -v

התוצאה:

test_calculator.py::test_add SKIPPED (No need right now!) [ 33%]
test_calculator.py::test_subtract PASSED                  [ 66%]
test_calculator.py::test_add_strings PASSED               [100%]

 

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

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

$ python --version

התוצאה אצלי על המערכת:

Python 3.10.7

נשתמש בדקורטור @pytest.mark.skipif כדי להגדיר תנאים בהם המבחן לא ירוץ.

נגדיר תנאי ולפיו נדלג על הרצת פונקציה אם גרסת הפייתון היא גבוהה מ-3.7:

import calculator
import pytest
import sys

@pytest.mark.skipif(sys.version_info > (3, 7), reason="Version is greater than 3.7")
def test_add():
   assert calculator.add(8,2) == 10
   assert calculator.add(8) == 8

נריץ:

$ pytest -v 

נקבל:

collected 3 items

test_calculator.py::test_add SKIPPED (Version is greater than 3.7) [ 33%]
test_calculator.py::test_subtract PASSED [ 66%]
test_calculator.py::test_add_strings PASSED [100%]

==== 2 passed, 1 skipped in 0.01s ====
  • דילוג על המבחן הראשון עם הסיבה שציינו וביצוע שני המבחנים האחרים.

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

@pytest.mark.skipif(sys.version_info <= (3, 7), reason="Version is less than or equal to 3.7")
def test_add():
   assert calculator.add(8,2) == 10
   assert calculator.add(8) == 8

 

מבחנים פרמטריים עם pytest parametrize

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

import calculator
import pytest

@pytest.mark.parametrize('num1, num2, res',
    [
    (8, 2, 10),
    ("Hello", " unit test", "Hello unit test"),
    (round(8.0, 1), round(4.7,1), round(12.7,1))
    ])
def test_add(num1, num2, res):
    assert calculator.add(num1, num2) == res
  • הדקורטור @pytest.mark.parametrize משמש להגדרת מבחנים מרובים על אותה הפונקציה. הוא מאפשר להריץ את אותה פונקציה נבחנת עבור סדרות של ארגומנטים ותוצאות צפויות. פה יישמנו אותו על הפונקציה test_add().

  • הארגומנטים שמקבל הדקורטור מגדירים את השמות ואת הערכים עבור הפרמטרים והתוצאות הצפויות. במקרה זה, שמות הפרמטרים הם: num1, num2 ו-res. המערך מגדיר את מקרי המבחן שבכל אחד מהם מוזנים לפי סדר הערכים עבור 3 הפרמטרים.

  • הפונקציה test_add() מכילה פונקציית assertion המוודאת האם התוצאה של calculator.add(num1, num2) היא שווה לתוצאה res. הוידוא נעשה על כל סט של קלטים ביחד עם התוצאות המוגדרות בדקורטור.

נריץ את ה-unittest:

$ pytest -v 

 

Pytest fixtures + setup/teardown methods

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

כדי להדגים נתחיל מהוספת קלאס ומסד נתונים. מסד נתונים שהוא לצורך ההסבר קובץ json:

db.json

{
"cars": [
{
"id": 1,
"name": "Honda Civic",
"price": 25000
},
{
"id": 2,
"name": "Toyota Camry",
"price": 30000
},
{
"id": 3,
"name": "Ford Mustang",
"price": 45000
}
]
}

קלאס CarsDB המכיל פונקציות להתקשרות עם מסד הנתונים ושליפת נתונים:

cars.py

import json

class CarsDB:
    def __init__(self, json_file):
        self.json_file = json_file
        self.data = None

    def connect(self):
        with open(self.json_file) as file:
            self.data = json.load(file)

    def get_data(self, collection_name):
        return self.data[collection_name]

    def get_by_name(self, collection_name, name):
        for item in self.data[collection_name]:
            if item['name'] == name:
                return item

נבחן את הקוד בצורה פרימיטיבית:

# Create an instance of CarsDB
db = CarsDB("./db.json")


# Connect to the database
db.connect()


# Retrieve the data
cars = db.get_data("cars")


# Get item by name
toyota = db.get_by_name("cars", "Toyota Camry")

נכתוב את המבחנים מסוג unittest:

test_cars.py

import json
import pytest

from cars import CarsDB

def test_get_data():
    db = CarsDB("./db.json")
    db.connect()
    cars = db.get_data("cars")

    assert len(cars) == 3

    # Verify the data of the first car
    assert cars[0]['id'] == 1
    assert cars[0]['name'] == "Honda Civic"
    assert cars[0]['price'] == 25000

    # Verify the data of the second car
    assert cars[1]['id'] == 2
    assert cars[1]['name'] == "Toyota Camry"
    assert cars[1]['price'] == 30000

    # Verify the data of the third car
    assert cars[2]['id'] == 3
    assert cars[2]['name'] == "Ford Mustang"
    assert cars[2]['price'] == 45000

def test_get_by_name():
    db = CarsDB("./db.json")
    db.connect()
    car = db.get_by_name("cars", "Toyota Camry")

    assert car['id'] == 2
    assert car['name'] == "Toyota Camry"
    assert car['price'] == 30000

    # Test for a non-existent car
    non_existent_car = db.get_by_name("cars", "Nonexistent Car")

    assert non_existent_car is None

נריץ את הבדיקה:

$ pytest -v

התוצאה:


test_cars.py::test_get_data PASSED    [ 50%]
test_cars.py::test_get_by_name PASSED [100%]

==== 2 passed in 0.01s =====

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

db = CarsDB("./db.json")
db.connect()

אז כדי לחסוך בשורות קוד ובמשאבי מערכת נעדיף לכתוב מתודה שרצה פעם אחת לפני הבדיקות ומכינה את המערכת לביצוע הבדיקות. במקרה שלנו, פותחת את מסד הנתונים. שם המתודה setup_module(). נוסיף אותה לקובץ הבדיקה:

def setup_module():
    print("----------setup----------")
    global db
    db = CarsDB("./db.json")
    db.connect()

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

def teardown_module():
    global db
    # Clean up resources after the entire module
    db = None
    print("----------teardown----------")

נסיר את הקוד המיותר מהמתודות עכשיו כש-setup_module() עושה את העבודה של ביסוס הקשר עם מסד הנתונים:

def test_get_data():
    cars = db.get_data("cars")

    assert len(cars) == 3

    # Verify the data of the first car
    assert cars[0]['id'] == 1
    assert cars[0]['name'] == "Honda Civic"
    assert cars[0]['price'] == 25000

    # Verify the data of the second car
    assert cars[1]['id'] == 2
    assert cars[1]['name'] == "Toyota Camry"
    assert cars[1]['price'] == 30000

    # Verify the data of the third car
    assert cars[2]['id'] == 3
    assert cars[2]['name'] == "Ford Mustang"
    assert cars[2]['price'] == 45000
def test_get_by_name():
    car = db.get_by_name("cars", "Toyota Camry")

    assert car['id'] == 2
    assert car['name'] == "Toyota Camry"
    assert car['price'] == 30000

    # Test for a non-existent car
    non_existent_car = db.get_by_name("cars", "Nonexistent Car")

    assert non_existent_car is None

ועכשיו נריץ את pytest עם האופציות -v וגם עם -s (כדי שנוכל לראות את מה שכותב print() לקונסולה):

$ pytest -v -s

התוצאה:

test_cars.py::test_get_data ----------setup----------
PASSED
test_cars.py::test_get_by_name PASSED----------teardown----------

נריץ ללא האופציה v כדי לוודא שאכן setup_module() רץ פעם בתחילת הרצת הבדיקה ו-teardown_module() רץ פעם אחת בסיום הריצה:

test_cars.py ----------setup----------
..----------teardown----------
  • שתי הנקודות אומרות ששני המבחנים עברו. הן נמצאות בין הריצה של setup ו-teardown.

במקום להשתמש ב-2 פונקציות setup שרץ לפני המבחנים ו-teardown שרץ אחרי אפשר להשתמש בפונקציה-fixture של pytest שממלאת את שני התפקידים ע"י פונקציה אחת.

כך נראית פונקציית ה-fixture:

@pytest.fixture(scope="module")
def db():
    db = CarsDB("./db.json")
    db.connect()
    yield db
    # Clean up resources after the entire module
    db = None
  • הדקורטור הפייתוני pytest.fixture משמש אותנו להגדרת פונקציית fixture ששמה db. הארגומנט scope="module" מציין שהפונקציה צריכה לרוץ פעם אחת בכל מודול.
  • הפונקציה יוצרת אובייקט CarsDb אותו היא מציבה למשתנה db. הפונקציה לא מחזירה את המשתנה db באמצעות return אלא עושה yield (זו תכונה של generator פייתוני שמאפשרת לפונקציה להמשיך להחזיר תוצאות). הקוד אחרי yield משמש לעשות teardown על ידי כך שהוא מבטיח את סגירת המשאבים וניקוי זיכרון אחרי שהמודול סיים לרוץ.

בהמשך הקוד, הפונקציות test_get_data() ו-test_get_by_name() יקבלו את db שמייצרת פונקציית ה-fixture כארגומנט מה שיאפשר להם לעבוד עם מסד הנתונים.

להלן הקוד המלא המשתמש בפונקציית fixture במקום במתודות setup ו-teardown:

import json
import pytest

from cars import CarsDB

@pytest.fixture(scope="module")
def db():
    db = CarsDB("./db.json")
    db.connect()
    yield db
    # Clean up resources after the entire module
    db = None

def test_get_data(db):
    cars = db.get_data("cars")

    assert len(cars) == 3

    # Verify the data of the first car
    assert cars[0]['id'] == 1
    assert cars[0]['name'] == "Honda Civic"
    assert cars[0]['price'] == 25000

    # Verify the data of the second car
    assert cars[1]['id'] == 2
    assert cars[1]['name'] == "Toyota Camry"
    assert cars[1]['price'] == 30000

    # Verify the data of the third car
    assert cars[2]['id'] == 3
    assert cars[2]['name'] == "Ford Mustang"
    assert cars[2]['price'] == 45000

def test_get_by_name(db):
    car = db.get_by_name("cars", "Toyota Camry")

    assert car['id'] == 2
    assert car['name'] == "Toyota Camry"
    assert car['price'] == 30000

    # Test for a non-existent car
    non_existent_car = db.get_by_name("cars", "Nonexistent Car")

    assert non_existent_car is None

 

אופציות הרצה מועילות נוספות כשעובדים עם pytest

האופציה:

$ pytest -v -x

גורמת להפסקת הבדיקה אחרי השגיאה הראשונה.

אם לא רוצים לראות את ה-stack trace אז מוסיפים:

$ pytest -v -x --tb=no

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

$ pytest -v --maxfail=2
  • לכל היותר 2 כשלונות

 

סיכום

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

 

מדריכים נוספים בסדרה ללימוד פייתון

דקורטורים של פייתון : דפוס, הבנה ויישום הלכה למעשה

פייתון מונחה עצמים 1: מחלקות, אובייקטים, תכונות ומתודות

טיפול בחריגים בפייתון באמצעות הבלוקים try ו-except

 

לכל המדריכים בסדרה ללימוד פייתון

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

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

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

שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.

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

השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.

הוסף תגובה חדשה

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

איך אומרים בעברית אינטרנט?