تست مبتنی بر ویژگی را با کتابخانه فرضیه پایتون کشف کنید. از تستهای مبتنی بر مثال فراتر بروید تا موارد خاص را پیدا کرده و نرمافزارهای قویتر و قابل اعتمادتری بسازید.
فراتر از تستهای واحد: بررسی عمیق تست مبتنی بر ویژگی با فرضیه پایتون
در دنیای توسعه نرمافزار، تست کردن، سنگ بنای کیفیت است. برای دههها، پارادایم غالب، تست مبتنی بر مثال بوده است. ما با دقت ورودیها را میسازیم، خروجیهای مورد انتظار را تعریف میکنیم و برای تأیید اینکه کد ما طبق برنامه عمل میکند، ادعاها (assertions) مینویسیم. این رویکرد، که در فریمورکهایی مانند unittest
و pytest
یافت میشود، قدرتمند و ضروری است. اما اگر به شما بگویم یک رویکرد مکمل وجود دارد که میتواند باگهایی را که حتی فکرش را هم نمیکردید پیدا کند؟
به دنیای تست مبتنی بر ویژگی خوش آمدید، پارادایمی که تمرکز را از تست کردن نمونههای خاص به تأیید ویژگیهای عمومی کد شما تغییر میدهد. و در اکوسیستم پایتون، قهرمان بلامنازع این رویکرد، کتابخانهای به نام 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 و تست رانر انتخابی خود را نصب کنید (ما از 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
: این ویژگی ما است. ما ادعا میکنیم که برای هر عدد صحیحی که 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
یا برگرداندن یک مقدار خاص.
حتی بهتر، اگر باگ با یک عدد ممیز شناور بسیار خاص بود چه؟ کوچککننده 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])
برابر نیست. کوچککننده اطمینان حاصل میکند که مثال ناموفق تا حد امکان ساده باشد، و باعث میشود علت باگ بلافاصله آشکار شود.
سناریو 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() # برای اجرای تست، شما به سادگی از ماشین و unittest.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 نمونه، مهلت 1 ثانیه @given(...) ...
- تولید مجدد شکستها: هر اجرای Hypothesis یک مقدار seed (به عنوان مثال،
@reproduce_failure('version', 'seed')
) را چاپ میکند. اگر یک سرور CI یک باگ پیدا کند که نمیتوانید آن را به صورت محلی تولید کنید، میتوانید از این دکوراتور با seed ارائه شده برای اجبار Hypothesis به اجرای دقیقاً همان توالی نمونهها استفاده کنید. - ادغام با CI/CD: Hypothesis یک انتخاب عالی برای هر خط لوله ادغام مداوم است. توانایی آن در یافتن باگهای مبهم قبل از رسیدن به تولید، آن را به یک شبکه ایمنی ارزشمند تبدیل میکند.
تغییر ذهنیت: فکر کردن به صورت ویژگیها
اتخاذ Hypothesis چیزی بیش از یادگیری یک کتابخانه جدید است. این در مورد پذیرش یک روش جدید برای فکر کردن در مورد صحت کد شما است. به جای پرسیدن، "چه ورودیهایی را باید آزمایش کنم؟"، شروع میکنید به پرسیدن، "حقایق جهانی در مورد این کد چیست؟"
در اینجا چند سوال برای راهنمایی شما هنگام تلاش برای شناسایی ویژگیها وجود دارد:
- آیا یک عمل معکوس وجود دارد؟ (به عنوان مثال، سریالیسازی/غیرسریالیسازی، رمزگذاری/رمزگشایی، فشردهسازی/رفع فشردهسازی). ویژگی این است که انجام عمل و معکوس آن باید ورودی اصلی را به دست دهد.
- آیا عمل یکتوان است؟ (به عنوان مثال،
abs(abs(x)) == abs(x)
). اعمال تابع بیش از یک بار باید همان نتیجه را به عنوان اعمال آن یک بار تولید کند. - آیا راه متفاوت و سادهتری برای محاسبه همان نتیجه وجود دارد؟ میتوانید آزمایش کنید که تابع پیچیده و بهینهسازی شده شما همان خروجی را تولید میکند که یک نسخه ساده و بدیهی درست (به عنوان مثال، آزمایش مرتبسازی فانتزی خود در برابر
sorted()
داخلی پایتون). - چه چیزی همیشه باید در مورد خروجی درست باشد؟ (به عنوان مثال، خروجی یک تابع `find_prime_factors` باید فقط شامل اعداد اول باشد و حاصل ضرب آنها باید برابر با ورودی باشد).
- حالت چگونه تغییر میکند؟ (برای تست حالتدار) چه ناورداهایی باید پس از هر عمل معتبر حفظ شوند؟ (به عنوان مثال، تعداد اقلام در یک سبد خرید هرگز نمیتواند منفی باشد).
نتیجهگیری: سطح جدیدی از اطمینان
تست مبتنی بر ویژگی با Hypothesis جایگزین تست مبتنی بر مثال نمیشود. شما هنوز به تستهای خاص و دستنویس برای منطق تجاری حیاتی و الزامات کاملاً درک شده نیاز دارید (به عنوان مثال، "یک کاربر از کشور X باید قیمت Y را ببیند").
آنچه Hypothesis ارائه میدهد یک روش قدرتمند و خودکار برای بررسی رفتار کد شما و محافظت در برابر موارد خاص پیشبینی نشده است. این به عنوان یک شریک خستگیناپذیر عمل میکند، و هزاران تستی را تولید میکند که متنوعتر و موذیانهتر از هر انسانی هستند که میتواند به طور واقعی بنویسد. با تعریف ویژگیهای اساسی کد خود، یک مشخصات قوی ایجاد میکنید که Hypothesis میتواند در برابر آن آزمایش کند، و به شما سطح جدیدی از اطمینان را در نرمافزار خود میدهد.
دفعه بعد که تابعی مینویسید، لحظهای وقت بگذارید تا فراتر از مثالها فکر کنید. از خود بپرسید، "قوانین چیست؟ چه چیزی همیشه باید درست باشد؟" سپس، اجازه دهید Hypothesis کار سخت تلاش برای شکستن آنها را انجام دهد. از آنچه پیدا میکند شگفتزده خواهید شد و کد شما برای آن بهتر خواهد بود.