גלו בדיקות מבוססות מאפיינים עם ספריית Hypothesis בפייתון. עברו מעבר לבדיקות מבוססות דוגמאות כדי למצוא מקרי קצה ולבנות תוכנה יציבה ואמינה יותר.
מעבר לבדיקות יחידה: צלילה עמוקה לבדיקות מבוססות מאפיינים עם Hypothesis בפייתון
בעולם פיתוח התוכנה, בדיקות הן עמוד התווך של איכות. במשך עשורים, הפרדיגמה הדומיננטית הייתה בדיקות מבוססות דוגמאות. אנו יוצרים בקפידה קלט, מגדירים את הפלט הצפוי, וכותבים בדיקות כדי לוודא שהקוד שלנו מתנהג כמתוכנן. גישה זו, הנמצאת במסגרות כמו unittest
ו-pytest
, היא חזקה וחיונית. אבל מה אם הייתי אומר לכם שיש גישה משלימה שיכולה לחשוף באגים שמעולם לא חשבתם לחפש?
ברוכים הבאים לעולם של בדיקות מבוססות מאפיינים, פרדיגמה שמזיזה את הפוקוס מבדיקת דוגמאות ספציפיות לווידוא מאפיינים כלליים של הקוד שלכם. ובאקוסיסטם של פייתון, האלופה הבלתי מעורערת של גישה זו היא ספרייה בשם Hypothesis.
מדריך מקיף זה ייקח אתכם ממתחילים גמורים למתרגלים בטוחים של בדיקות מבוססות מאפיינים עם Hypothesis. נחקור את מושגי הליבה, נצלול לדוגמאות מעשיות, ונלמד כיצד לשלב את הכלי החזק הזה בזרימת העבודה היומיומית שלכם כדי לבנות תוכנה יציבה, אמינה ועמידה יותר בפני באגים.
מהן בדיקות מבוססות מאפיינים? שינוי תפיסה
כדי להבין את Hypothesis, ראשית עלינו לתפוס את הרעיון הבסיסי של בדיקות מבוססות מאפיינים. בואו נשווה זאת לבדיקות מבוססות דוגמאות המסורתיות שכולנו מכירים.
בדיקות מבוססות דוגמאות: הדרך המוכרת
דמיינו שכתבתם פונקציית מיון מותאמת אישית, my_sort()
. עם בדיקות מבוססות דוגמאות, תהליך החשיבה שלכם יהיה:
- "בואו נבדוק אותה עם רשימה פשוטה וממוינת." ->
assert my_sort([1, 2, 3]) == [1, 2, 3]
- "מה לגבי רשימה בסדר הפוך?" ->
assert my_sort([3, 2, 1]) == [1, 2, 3]
- "איך לגבי רשימה ריקה?" ->
assert my_sort([]) == []
- "רשימה עם כפילויות?" ->
assert my_sort([5, 1, 5, 2]) == [1, 2, 5, 5]
- "והאם יש רשימה עם מספרים שליליים?" ->
assert my_sort([-1, -5, 0]) == [-5, -1, 0]
זה יעיל, אבל יש לו מגבלה יסודית: אתם בודקים רק את המקרים שאתם יכולים לחשוב עליהם. הבדיקות שלכם טובות רק כמו הדמיון שלכם. אתם עלולים לפספס מקרי קצה הכוללים מספרים גדולים מאוד, אי-דיוקים בנקודה צפה, תווים ספציפיים של יוניקוד, או שילובים מורכבים של נתונים המובילים להתנהגות בלתי צפויה.
בדיקות מבוססות מאפיינים: חשיבה על קבועים
בדיקות מבוססות מאפיינים הופכות את התסריט. במקום לספק דוגמאות ספציפיות, אתם מגדירים את המאפיינים, או הקבועים, של הפונקציה שלכם—כללים שאמורים להיות נכונים עבור כל קלט חוקי. עבור פונקציית my_sort()
שלנו, המאפיינים הללו יכולים להיות:
- הפלט ממוין: עבור כל רשימת מספרים, כל איבר ברשימת הפלט קטן או שווה לאיבר הבא אחריו.
- הפלט מכיל את אותם איברים כמו הקלט: הרשימה הממוינת היא רק תמורה של הרשימה המקורית; לא מתווספים ולא נאבדים איברים.
- הפונקציה אידמפוטנטית: מיון רשימה שכבר ממוינת לא אמור לשנות אותה. כלומר,
my_sort(my_sort(some_list)) == my_sort(some_list)
.
עם גישה זו, אתם לא כותבים את נתוני הבדיקה. אתם כותבים את הכללים. לאחר מכן אתם נותנים למסגרת, כמו Hypothesis, ליצור מאות או אלפי קלטים אקראיים, מגוונים, ולעיתים קרובות ערמומיים, כדי לנסות להוכיח את המאפיינים שלכם כשגויים. אם היא מוצאת קלט שמפר מאפיין, היא מצאה באג.
הכר את Hypothesis: מחולל נתוני הבדיקה האוטומטי שלך
Hypothesis היא ספריית הבדיקות מבוססות המאפיינים המובילה לפייתון. היא לוקחת את המאפיינים שאתם מגדירים ועושה את העבודה הקשה של יצירת נתוני בדיקה כדי לאתגר אותם. היא לא רק מחולל נתונים אקראיים; זהו כלי אינטליגנטי ועוצמתי שנועד למצוא באגים ביעילות.
תכונות מפתח של Hypothesis
- יצירת מקרי בדיקה אוטומטית: אתם מגדירים את הצורה של הנתונים שאתם צריכים (למשל, "רשימת מספרים שלמים", "מחרוזת המכילה רק אותיות", "תאריך ושעה בעתיד"), ו-Hypothesis יוצרת מגוון רחב של דוגמאות התואמות לצורה זו.
- כיווץ אינטליגנטי: זוהי תכונת הקסם. כאשר Hypothesis מוצאת מקרה בדיקה כושל (למשל, רשימה של 50 מספרים מורכבים שקורסת את פונקציית המיון שלכם), היא לא רק מדווחת על הרשימה הענקית הזו. היא מכווצת באופן אינטליגנטי ואוטומטי את הקלט כדי למצוא את הדוגמה הקטנה ביותר האפשרית שעדיין גורמת לכשל. במקום רשימה של 50 איברים, היא עשויה לדווח שהכשל מתרחש רק עם
[inf, nan]
. זה הופך את הדיבוג למהיר ויעיל להפליא. - אינטגרציה חלקה: Hypothesis משתלב בצורה מושלמת עם מסגרות בדיקה פופולריות כמו
pytest
ו-unittest
. אתם יכולים להוסיף בדיקות מבוססות מאפיינים לצד הבדיקות מבוססות הדוגמאות הקיימות שלכם מבלי לשנות את זרימת העבודה שלכם. - ספריית אסטרטגיות עשירה: היא מגיעה עם אוסף עצום של "אסטרטגיות" מובנות ליצירת כל דבר, החל ממספרים שלמים ומחרוזות פשוטים ועד למבני נתונים מורכבים ומקוננים, תאריכים ושעות עם מידע אזורי, ואפילו מערכי NumPy.
- בדיקות מבוססות מצב: עבור מערכות מורכבות יותר, Hypothesis יכולה לבדוק רצפים של פעולות כדי למצוא באגים במעברי מצב, דבר שקשה להחריד עם בדיקות מבוססות דוגמאות.
התחלה: בדיקת ה-Hypothesis הראשונה שלך
בואו נתלכלך. הדרך הטובה ביותר להבין את Hypothesis היא לראות אותה בפעולה.
התקנה
ראשית, תצטרכו להתקין את Hypothesis ואת בודק הבדיקות שתבחרו (נשתמש ב-pytest
). זה פשוט כמו:
pip install pytest hypothesis
דוגמה פשוטה: פונקציית ערך מוחלט
בואו נחשוב על פונקציה פשוטה שאמורה לחשב את הערך המוחלט של מספר. יישום מעט פגום עשוי להיראות כך:
# בקובץ בשם `my_math.py` def custom_abs(x): """יישום מותאם אישית של פונקציית הערך המוחלט.""" if x < 0: return -x return x
עכשיו, בואו נכתוב קובץ בדיקה, test_my_math.py
. ראשית, הגישה המסורתית של pytest
:
# test_my_math.py (מבוסס דוגמאות) def test_abs_positive(): assert custom_abs(5) == 5 def test_abs_negative(): assert custom_abs(-5) == 5 def test_abs_zero(): assert custom_abs(0) == 0
בדיקות אלו עוברות. הפונקציה שלנו נראית נכונה בהתבסס על הדוגמאות הללו. אבל עכשיו, בואו נכתוב בדיקה מבוססת מאפיינים עם Hypothesis. מהו מאפיין ליבה של פונקציית הערך המוחלט? התוצאה לעולם לא אמורה להיות שלילית.
# test_my_math.py (מבוסס מאפיינים עם Hypothesis) from hypothesis import given from hypothesis import strategies as st from my_math import custom_abs @given(st.integers()) def test_abs_property_is_non_negative(x): """מאפיין: הערך המוחלט של כל מספר שלם הוא תמיד >= 0.""" assert custom_abs(x) >= 0
בואו נפרק את זה:
from hypothesis import given, strategies as st
: אנו מייבאים את הרכיבים הנחוצים.given
הוא דקורטור שהופך פונקציית בדיקה רגילה לבדיקה מבוססת מאפיינים.strategies
הוא המודול שבו אנו מוצאים את מחוללי הנתונים שלנו.@given(st.integers())
: זוהי ליבת הבדיקה. הדקורטור@given
אומר ל-Hypothesis להריץ את פונקציית הבדיקה הזו מספר פעמים. עבור כל הרצה, הוא ייצור ערך באמצעות האסטרטגיה שסופקה,st.integers()
, ויעביר אותו כארגומנטx
לפונקציית הבדיקה שלנו.assert custom_abs(x) >= 0
: זהו המאפיין שלנו. אנו טוענים שעבור כל מספר שלםx
ש-Hypothesis תמציא, התוצאה של הפונקציה שלנו חייבת להיות גדולה או שווה לאפס.
כאשר תריצו זאת עם pytest
, זה כנראה יעבור עבור ערכים רבים. Hypothesis תנסה 0, -1, 1, מספרים חיוביים גדולים, מספרים שליליים גדולים, ועוד. הפונקציה הפשוטה שלנו מטפלת בכל אלה כראוי. עכשיו, בואו ננסה אסטרטגיה אחרת כדי לראות אם נוכל למצוא חולשה.
# בואו נבדוק עם מספרי נקודה צפה @given(st.floats()) def test_abs_floats_property(x): assert custom_abs(x) >= 0
אם תריצו זאת, Hypothesis תמצא במהירות מקרה כושל!
Falsifying example: test_abs_floats_property(x=nan) ... assert custom_abs(nan) >= 0 AssertionError: assert nan >= 0
Hypothesis גילתה שהפונקציה שלנו, כאשר מקבלת float('nan')
(Not a Number), מחזירה nan
. הטענה nan >= 0
שגויה. זה עתה מצאנו באג עדין שאנו כנראה לא היינו חושבים לבדוק ידנית. נוכל לתקן את הפונקציה שלנו לטפל במקרה זה, אולי על ידי העלאת ValueError
או החזרת ערך ספציפי.
אפילו טוב יותר, מה אם הבאג היה עם נקודה צפה ספציפית מאוד? ה-shrinker של Hypothesis היה לוקח מספר פגום גדול ומורכב ומצמצם אותו לגרסה הפשוטה ביותר שעדיין מפעילה את הבאג.
כוחן של אסטרטגיות: יצירת נתוני הבדיקה שלך
אסטרטגיות הן הלב של Hypothesis. הן מתכונים ליצירת נתונים. הספרייה כוללת מגוון עצום של אסטרטגיות מובנות, ואתה יכול לשלב ולהתאים אותן כדי ליצור כמעט כל מבנה נתונים שניתן לדמיין.
אסטרטגיות מובנות נפוצות
- מספרי:
st.integers(min_value=0, max_value=1000)
: יוצר מספרים שלמים, אופציונלית בטווח ספציפי.st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False)
: יוצר מספרים שלמים, עם שליטה גרעינית על ערכים מיוחדים.st.fractions()
,st.decimals()
- טקסט:
st.text(min_size=1, max_size=50)
: יוצר מחרוזות יוניקוד באורך מסוים.st.text(alphabet='abcdef0123456789')
: יוצר מחרוזות מקבוצת תווים ספציפית (למשל, עבור קודי הקסדצימליים).st.characters()
: יוצר תווים בודדים.
- אוספים:
st.lists(st.integers(), min_size=1)
: יוצר רשימות שבהן כל איבר הוא מספר שלם. שימו לב כיצד אנו מעבירים אסטרטגיה אחרת כארגומנט! זה נקרא קומפוזיציה.st.tuples(st.text(), st.booleans())
: יוצר טאפלים עם מבנה קבוע.st.sets(st.integers())
st.dictionaries(keys=st.text(), values=st.integers())
: יוצר מילונים עם סוגי מפתחות וערכים מוגדרים.
- זמניים:
st.dates()
,st.times()
,st.datetimes()
,st.timedeltas()
. אלה יכולים להיות עם מידע אזורי.
- שונות:
st.booleans()
: יוצרTrue
אוFalse
.st.just('constant_value')
: תמיד מייצר ערך יחיד קבוע. שימושי לקומפוזיציה של אסטרטגיות מורכבות.st.one_of(st.integers(), st.text())
: מייצר ערך מאחת האסטרטגיות שסופקו.st.none()
: מייצר רקNone
.
שילוב וטרנספורמציה של אסטרטגיות
הכוח האמיתי של Hypothesis נובע מיכולתה לבנות אסטרטגיות מורכבות מאסטרטגיות פשוטות יותר.
שימוש ב-.map()
השיטה .map()
מאפשרת לך לקחת ערך מאסטרטגיה אחת ולהפוך אותו למשהו אחר. זה מושלם ליצירת אובייקטים של המחלקות המותאמות אישית שלך.
# מחלקת נתונים פשוטה from dataclasses import dataclass @dataclass class User: user_id: int username: str # אסטרטגיה ליצירת אובייקטי User user_strategy = st.builds( User, user_id=st.integers(min_value=1), username=st.text(min_size=3, alphabet='abcdefghijklmnopqrstuvwxyz') ) @given(user=user_strategy) def test_user_creation(user): assert isinstance(user, User) assert user.user_id > 0 assert user.username.isalpha()
שימוש ב-.filter()
ו-assume()
לפעמים אתה צריך לדחות ערכים שנוצרו מסוימים. לדוגמה, ייתכן שתזדקק לרשימת מספרים שלמים שהסכום שלהם אינו אפס. אתה יכול להשתמש ב-.filter()
:
st.lists(st.integers()).filter(lambda x: sum(x) != 0)
עם זאת, השימוש ב-.filter()
יכול להיות לא יעיל. אם התנאי שגוי לעתים קרובות, Hypothesis עשויה להשקיע זמן רב בניסיון ליצור דוגמה חוקית. גישה טובה יותר היא לעתים קרובות להשתמש ב-assume()
בתוך פונקציית הבדיקה שלך:
from hypothesis import assume @given(st.lists(st.integers())) def test_something_with_non_zero_sum_list(numbers): assume(sum(numbers) != 0) # ... הלוגיקה של הבדיקה שלך כאן ...
assume()
אומר ל-Hypothesis: "אם התנאי הזה לא מתקיים, פשוט זרוק את הדוגמה הזו ונסה אחת חדשה." זוהי דרך ישירה יותר ולעיתים קרובות יעילה יותר להגביל את נתוני הבדיקה שלך.
שימוש ב-st.composite()
ליצירת נתונים מורכבים באמת שבהם ערך אחד שנוצר תלוי בערך אחר, st.composite()
הוא הכלי הדרוש לך. הוא מאפשר לך לכתוב פונקציה שמקבלת פונקציית draw
מיוחדת כארגומנט, שבה אתה יכול להשתמש כדי למשוך ערכים מאסטרטגיות אחרות צעד אחר צעד.
דוגמה קלאסית היא יצירת רשימה ואינדקס חוקי לרשימה זו.
@st.composite def list_and_index(draw): # ראשית, צייר רשימה לא ריקה my_list = draw(st.lists(st.integers(), min_size=1)) # לאחר מכן, צייר אינדקס שמובטח להיות חוקי עבור אותה רשימה index = draw(st.integers(min_value=0, max_value=len(my_list) - 1)) return (my_list, index) @given(data=list_and_index()) def test_list_access(data): my_list, index = data # גישה זו מובטחת להיות בטוחה בגלל האופן שבו בנינו את האסטרטגיה element = my_list[index] assert element is not None # בדיקה פשוטה
Hypothesis בפעולה: תרחישים מהעולם האמיתי
בואו נחיל את המושגים הללו על בעיות ריאליות יותר שמפתחי תוכנה מתמודדים איתם מדי יום.
תרחיש 1: בדיקת פונקציית סריאליזציה של נתונים
דמיינו פונקציה שמסרבת פרופיל משתמש (מילון) למחרוזת בטוחה ל-URL ופונקציה אחרת שמבטלת את הסריאליזציה שלה. מאפיין מרכזי הוא שהתהליך צריך להיות ניתן להפיכה באופן מושלם.
import json import base64 def serialize_profile(data: dict) -> str: """מסרב מילון למחרוזת base64 בטוחה ל-URL.""" json_string = json.dumps(data) return base64.urlsafe_b64encode(json_string.encode('utf-8')).decode('utf-8') def deserialize_profile(encoded_str: str) -> dict: """מבטל סריאליזציה של מחרוזת חזרה למילון.""" json_string = base64.urlsafe_b64decode(encoded_str.encode('utf-8')).decode('utf-8') return json.loads(json_string) # עכשיו לבדיקה # אנו זקוקים לאסטרטגיה שמייצרת מילונים תואמים JSON json_dictionaries = st.dictionaries( keys=st.text(), values=st.recursive(st.none() | st.booleans() | st.floats(allow_nan=False) | st.text(), lambda children: st.lists(children) | st.dictionaries(st.text(), children), max_leaves=10) ) @given(profile=json_dictionaries) def test_serialization_roundtrip(profile): """מאפיין: ביטול סריאליזציה של פרופיל מקודד אמור להחזיר את הפרופיל המקורי.""" encoded = serialize_profile(profile) decoded = deserialize_profile(encoded) assert profile == decoded
בדיקה אחת זו תדפוק את הפונקציות שלנו עם מגוון עצום של נתונים: מילונים ריקים, מילונים עם רשימות מקוננות, מילונים עם תווים של יוניקוד, מילונים עם מפתחות מוזרים, ועוד. זה הרבה יותר יסודי מאשר כתיבת כמה דוגמאות ידניות.
תרחיש 2: בדיקת אלגוריתם מיון
בואו נחזור לדוגמת המיון שלנו. כך תבדקו את המאפיינים שהגדרנו קודם.
from collections import Counter def my_buggy_sort(numbers): # בואו נציג באג עדין: הוא מפיל כפילויות return sorted(list(set(numbers))) @given(st.lists(st.integers())) def test_sorting_properties(numbers): sorted_list = my_buggy_sort(numbers) # מאפיין 1: הפלט ממוין for i in range(len(sorted_list) - 1): assert sorted_list[i] <= sorted_list[i+1] # מאפיין 2: האיברים זהים (זה ימצא את הבאג) assert Counter(numbers) == Counter(sorted_list) # מאפיין 3: הפונקציה אידמפוטנטית assert my_buggy_sort(sorted_list) == sorted_list
כאשר תריצו בדיקה זו, Hypothesis ימצא במהירות דוגמה כושלת עבור מאפיין 2, כמו numbers=[0, 0]
. הפונקציה שלנו מחזירה [0]
, ו-Counter([0, 0])
לא שווה ל-Counter([0])
. ה-shrinker יבטיח שהדוגמה הכושלת תהיה פשוטה ככל האפשר, מה שהופך את הגורם לבאג למיידי.
תרחיש 3: בדיקות מבוססות מצב
עבור אובייקטים עם מצב פנימי המשתנה לאורך זמן (כמו חיבור למסד נתונים, עגלת קניות, או מטמון), מציאת באגים יכולה להיות קשה להחריד. ייתכן שיהיה צורך ברצף ספציפי של פעולות כדי להפעיל כשל. Hypothesis מספקת `RuleBasedStateMachine` בדיוק למטרה זו.
דמיינו API פשוט לחנות מפתח-ערך בזיכרון:
class SimpleKeyValueStore: def __init__(self): self._data = {} def set(self, key, value): self._data[key] = value def get(self, key): return self._data.get(key) def delete(self, key): if key in self._data: del self._data[key] def size(self): return len(self._data)
אנו יכולים למדל את התנהגותו ולבדוק אותה באמצעות מכונת מצב:
from hypothesis.stateful import RuleBasedStateMachine, rule, Bundle class KeyValueStoreMachine(RuleBasedStateMachine): def __init__(self): super().__init__() self.model = {} self.sut = SimpleKeyValueStore() # Bundle() משמש להעברת נתונים בין כללים keys = Bundle('keys') @rule(target=keys, key=st.text(), value=st.integers()) def set_key(self, key, value): self.model[key] = value self.sut.set(key, value) return key @rule(key=keys) def delete_key(self, key): del self.model[key] self.sut.delete(key) @rule(key=st.text()) def get_key(self, key): model_val = self.model.get(key) sut_val = self.sut.get(key) assert model_val == sut_val @rule() def check_size(self): assert len(self.model) == self.sut.size() # כדי להריץ את הבדיקה, פשוט ירשת ממכונת TestCase # ב-pytest, אתה פשוט משייך את הבדיקה למחלקת המכונה TestKeyValueStore = KeyValueStoreMachine.TestCase
Hypothesis יריץ כעת רצפים אקראיים של פעולות `set_key`, `delete_key`, `get_key`, ו-`check_size`, וינסה ללא הרף למצוא רצף שגורם לאחת הטענות להיכשל. הוא יבדוק אם קבלת מפתח שנמחק מתנהג כראוי, אם הגודל עקבי לאחר מספר פעולות הגדרה ומחיקה, ותרחישים רבים אחרים שאולי לא תחשבו לבדוק ידנית.
שיטות עבודה מומלצות וטיפים מתקדמים
- מאגר הדוגמאות: Hypothesis חכמה. כשהיא מוצאת באג, היא שומרת את הדוגמה הכושלת בספרייה מקומית (
.hypothesis/
). בפעם הבאה שתריצו את הבדיקות שלכם, היא תנגן מחדש את הדוגמה הכושלת תחילה, ותיתן לכם משוב מיידי שהבאג עדיין קיים. ברגע שתתקנו אותו, הדוגמה לא תשוחזר יותר. - שליטה על הרצת הבדיקות עם
@settings
: אתם יכולים לשלוט בהיבטים רבים של הרצת הבדיקה באמצעות הדקורטור@settings
. אתם יכולים להגדיל את מספר הדוגמאות, להגדיר מועד אחרון לכמה זמן דוגמה בודדת יכולה לרוץ (כדי לתפוס לולאות אינסופיות), ולכבות בדיקות בריאות מסוימות.@settings(max_examples=500, deadline=1000) # הרץ 500 דוגמאות, מועד אחרון של שנייה @given(...) ...
- שחזור כשלים: כל הרצת Hypothesis מדפיסה ערך זרע (למשל,
@reproduce_failure('version', 'seed')
). אם שרת CI מוצא באג שאינכם יכולים לשחזר מקומית, אתם יכולים להשתמש בדקורטור הזה עם הזרע שסופק כדי לכפות על Hypothesis להריץ את אותו הרצף בדיוק של דוגמאות. - שילוב עם CI/CD: Hypothesis מתאימה באופן מושלם לכל צינור אינטגרציה רציפה. יכולתה למצוא באגים מעורפלים לפני שהם מגיעים לייצור הופכת אותה לרשת בטיחות שלא יסולא בפז.
שינוי התפיסה: חשיבה בקטגוריות
אימוץ Hypothesis הוא יותר מסתם לימוד ספרייה חדשה; זוהי אימוץ דרך חדשה לחשוב על נכונות הקוד שלכם. במקום לשאול, "אילו קלטים עלי לבדוק?" אתם מתחילים לשאול, "מהן האמיתות האוניברסליות לגבי הקוד הזה?"
להלן מספר שאלות שינחו אתכם כאשר תנסו לזהות מאפיינים:
- האם יש פעולה הפוכה? (למשל, סריאליזציה/ביטול סריאליזציה, הצפנה/פענוח, דחיסה/פריסת דחיסה). המאפיין הוא שהפעלת הפעולה והפוכה שלה אמורות להניב את הקלט המקורי.
- האם הפעולה אידמפוטנטית? (למשל,
abs(abs(x)) == abs(x)
). הפעלת הפונקציה יותר מפעם אחת אמורה להניב את אותה תוצאה כמו הפעלתה פעם אחת. - האם יש דרך אחרת, פשוטה יותר, לחשב את אותה תוצאה? אתם יכולים לבדוק שפונקציית הפעולה המורכבת והאופטימלית שלכם מניבה את אותה תוצאה כמו גרסה פשוטה, ברור ש נכונה (למשל, בדיקת המיון המתוחכם שלכם מול
sorted()
המובנה בפייתון). - מה תמיד צריך להיות נכון לגבי הפלט? (למשל, הפלט של פונקציית
find_prime_factors
אמור להכיל רק מספרים ראשוניים, והמכפלה שלהם אמורה להיות שווה לקלט). - כיצד המצב משתנה? (עבור בדיקות מבוססות מצב) אילו קבועים חייבים להישמר לאחר כל פעולה חוקית? (למשל, מספר הפריטים בעגלת קניות לעולם לא יכול להיות שלילי).
מסקנה: רמת ביטחון חדשה
בדיקות מבוססות מאפיינים עם Hypothesis אינן מחליפות בדיקות מבוססות דוגמאות. אתם עדיין זקוקים לבדיקות ספציפיות, שנכתבו ידנית, עבור היגיון עסקי קריטי ודרישות מובנות היטב (למשל, "משתמש ממדינה X חייב לראות מחיר Y").
מה ש-Hypothesis מספקת היא דרך אוטומטית ועוצמתית לחקור את התנהגות הקוד שלכם ולהגן מפני מקרי קצה בלתי צפויים. היא משמשת כשותפה עייפה, היוצרת אלפי בדיקות שהן מגוונות וערמומיות יותר ממה שכל אדם יכול לכתוב באופן ריאלי. על ידי הגדרת המאפיינים הבסיסיים של הקוד שלכם, אתם יוצרים מפרט יציב ש-Hypothesis יכולה לבדוק מולו, מה שמעניק לכם רמת ביטחון חדשה בתוכנה שלכם.
בפעם הבאה שתכתבו פונקציה, קחו רגע לחשוב מעבר לדוגמאות. שאלו את עצמכם, "מהם הכללים? מה חייב תמיד להיות נכון?" ואז, תנו ל-Hypothesis לעשות את העבודה הקשה בלנסות לשבור אותם. תופתעו ממה שהיא תמצא, והקוד שלכם יהיה טוב יותר בזכותה.