પાયથોનની હાઈપોથિસિસ લાઈબ્રેરી સાથે પ્રોપર્ટી-આધારિત ટેસ્ટિંગ શોધો. ઉદાહરણ-આધારિત ટેસ્ટથી આગળ વધીને એજ કેસ શોધો અને વધુ મજબૂત, ભરોસાપાત્ર સોફ્ટવેર બનાવો.
યુનિટ ટેસ્ટ્સથી આગળ: પાયથોનની હાઈપોથિસિસ સાથે પ્રોપર્ટી-આધારિત ટેસ્ટિંગમાં ઊંડાણપૂર્વકનું સંશોધન
સોફ્ટવેર ડેવલપમેન્ટની દુનિયામાં, ટેસ્ટિંગ એ ગુણવત્તાનો પાયો છે. દાયકાઓથી, પ્રબળ પ્રતિમાન ઉદાહરણ-આધારિત ટેસ્ટિંગ રહ્યું છે. આપણે કાળજીપૂર્વક ઇનપુટ્સ બનાવીએ છીએ, અપેક્ષિત આઉટપુટ્સ વ્યાખ્યાયિત કરીએ છીએ, અને આપણા કોડ યોજના મુજબ વર્તે છે તેની ચકાસણી કરવા માટે એસર્શન્સ લખીએ છીએ. unittest
અને pytest
જેવા ફ્રેમવર્કમાં જોવા મળતો આ અભિગમ શક્તિશાળી અને આવશ્યક છે. પરંતુ જો હું તમને કહું કે એક પૂરક અભિગમ છે જે એવી ભૂલો શોધી શકે છે જેની તમે ક્યારેય કલ્પના પણ નહોતી કરી?
પ્રોપર્ટી-આધારિત ટેસ્ટિંગની દુનિયામાં આપનું સ્વાગત છે, એક પ્રતિમાન જે ચોક્કસ ઉદાહરણોનું ટેસ્ટિંગ કરવાથી તમારા કોડના સામાન્ય ગુણધર્મોને ચકાસવા તરફ ધ્યાન કેન્દ્રિત કરે છે. અને પાયથોન ઇકોસિસ્ટમમાં, આ અભિગમનો નિર્વિવાદ ચેમ્પિયન 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)
.
આ અભિગમ સાથે, તમે ટેસ્ટ ડેટા લખી રહ્યા નથી. તમે નિયમો લખી રહ્યા છો. પછી તમે હાઈપોથિસિસ જેવા ફ્રેમવર્કને સેંકડો અથવા હજારો રેન્ડમ, વૈવિધ્યસભર અને ઘણીવાર કપટી ઇનપુટ્સ જનરેટ કરવા દો છો જેથી તમારા ગુણધર્મો ખોટા સાબિત કરવાનો પ્રયાસ કરી શકાય. જો તે કોઈ ગુણધર્મને તોડે તેવું ઇનપુટ શોધે છે, તો તેણે એક બગ શોધી કાઢ્યો છે.
હાઈપોથિસિસનો પરિચય: તમારું સ્વચાલિત ટેસ્ટ ડેટા જનરેટર
હાઈપોથિસિસ એ પાયથોન માટેની પ્રીમિયર પ્રોપર્ટી-આધારિત ટેસ્ટિંગ લાઈબ્રેરી છે. તે તમે વ્યાખ્યાયિત કરેલા ગુણધર્મો લે છે અને તેમને પડકારવા માટે ટેસ્ટ ડેટા જનરેટ કરવાનું મુશ્કેલ કાર્ય કરે છે. તે ફક્ત રેન્ડમ ડેટા જનરેટર નથી; તે બગ્સને કાર્યક્ષમ રીતે શોધવા માટે રચાયેલ એક બુદ્ધિશાળી અને શક્તિશાળી સાધન છે.
હાઈપોથિસિસની મુખ્ય વિશેષતાઓ
- સ્વચાલિત ટેસ્ટ કેસ જનરેશન: તમે તમને જોઈતા ડેટાનો *આકાર* વ્યાખ્યાયિત કરો છો (દા.ત., "પૂર્ણાંકોની સૂચિ," "ફક્ત અક્ષરો ધરાવતી સ્ટ્રિંગ," "ભવિષ્યમાં ડેટટાઈમ"), અને હાઈપોથિસિસ તે આકારને અનુરૂપ વિવિધ પ્રકારના ઉદાહરણો જનરેટ કરે છે.
- બુદ્ધિશાળી સંકોચન (Shrinking): આ જાદુઈ સુવિધા છે. જ્યારે હાઈપોથિસિસ નિષ્ફળ ટેસ્ટ કેસ શોધે છે (દા.ત., 50 જટિલ સંખ્યાઓની સૂચિ જે તમારા સૉર્ટ ફંક્શનને ક્રેશ કરે છે), ત્યારે તે ફક્ત તે વિશાળ સૂચિની જાણ કરતું નથી. તે નિષ્ફળતાનું કારણ બને તેવા સૌથી નાના શક્ય ઉદાહરણને શોધવા માટે ઇનપુટને બુદ્ધિપૂર્વક અને આપમેળે સરળ બનાવે છે. 50-તત્વની સૂચિને બદલે, તે કદાચ જાણ કરશે કે નિષ્ફળતા ફક્ત
[inf, nan]
સાથે થાય છે. આ ડિબગીંગને અતિ ઝડપી અને કાર્યક્ષમ બનાવે છે. - સીમલેસ ઇન્ટિગ્રેશન: હાઈપોથિસિસ
pytest
અનેunittest
જેવા લોકપ્રિય ટેસ્ટિંગ ફ્રેમવર્ક સાથે સંપૂર્ણ રીતે સંકલિત થાય છે. તમે તમારા વર્કફ્લોને બદલ્યા વિના તમારા હાલના ઉદાહરણ-આધારિત ટેસ્ટની સાથે પ્રોપર્ટી-આધારિત ટેસ્ટ ઉમેરી શકો છો. - સ્ટ્રેટેજીસની સમૃદ્ધ લાઈબ્રેરી: તે સરળ પૂર્ણાંકો અને સ્ટ્રિંગ્સથી લઈને જટિલ, નેસ્ટેડ ડેટા સ્ટ્રક્ચર્સ, ટાઈમઝોન-અવેર ડેટટાઈમ્સ, અને NumPy એરે સુધી બધું જ જનરેટ કરવા માટે બિલ્ટ-ઇન "સ્ટ્રેટેજીસ"નો વિશાળ સંગ્રહ ધરાવે છે.
- સ્ટેટફુલ ટેસ્ટિંગ: વધુ જટિલ સિસ્ટમો માટે, હાઈપોથિસિસ સ્ટેટ ટ્રાન્ઝિશનમાં ભૂલો શોધવા માટે ક્રિયાઓના અનુક્રમોનું ટેસ્ટિંગ કરી શકે છે, જે ઉદાહરણ-આધારિત ટેસ્ટિંગ સાથે અત્યંત મુશ્કેલ છે.
શરૂઆત કરવી: તમારો પહેલો હાઈપોથિસિસ ટેસ્ટ
ચાલો આપણે વ્યવહારુ બનીએ. હાઈપોથિસિસને સમજવાનો શ્રેષ્ઠ માર્ગ તેને કાર્યમાં જોવાનો છે.
ઇન્સ્ટોલેશન
સૌ પ્રથમ, તમારે હાઈપોથિસિસ અને તમારી પસંદગીના ટેસ્ટ રનર (આપણે pytest
નો ઉપયોગ કરીશું) ઇન્સ્ટોલ કરવાની જરૂર પડશે. તે આટલું સરળ છે:
pip install pytest hypothesis
એક સરળ ઉદાહરણ: નિરપેક્ષ મૂલ્ય (Absolute Value) ફંક્શન
ચાલો એક સરળ ફંક્શન ધ્યાનમાં લઈએ જે સંખ્યાના નિરપેક્ષ મૂલ્યની ગણતરી કરવાનું માનવામાં આવે છે. થોડું બગી (buggy) અમલીકરણ આના જેવું દેખાઈ શકે છે:
# `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
આ ટેસ્ટ પાસ થાય છે. આપણું ફંક્શન આ ઉદાહરણોના આધારે સાચું લાગે છે. પરંતુ હવે, ચાલો હાઈપોથિસિસ સાથે પ્રોપર્ટી-આધારિત ટેસ્ટ લખીએ. નિરપેક્ષ મૂલ્ય ફંક્શનનો મુખ્ય ગુણધર્મ શું છે? પરિણામ ક્યારેય નકારાત્મક ન હોવું જોઈએ.
# test_my_math.py (હાઈપોથિસિસ સાથે પ્રોપર્ટી-આધારિત) 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
ડેકોરેટર હાઈપોથિસિસને આ ટેસ્ટ ફંક્શનને ઘણી વખત ચલાવવા માટે કહે છે. દરેક રન માટે, તે પ્રદાન કરેલી સ્ટ્રેટેજી,st.integers()
નો ઉપયોગ કરીને એક મૂલ્ય જનરેટ કરશે અને તેને આપણા ટેસ્ટ ફંક્શનમાંx
આર્ગ્યુમેન્ટ તરીકે પાસ કરશે.assert custom_abs(x) >= 0
: આ આપણો ગુણધર્મ છે. આપણે દાવો કરીએ છીએ કે હાઈપોથિસિસ દ્વારા કલ્પના કરાયેલ કોઈપણ પૂર્ણાંકx
માટે, આપણા ફંક્શનનું પરિણામ શૂન્ય કરતાં મોટું અથવા બરાબર હોવું જોઈએ.
જ્યારે તમે આને pytest
સાથે ચલાવો છો, ત્યારે તે ઘણા મૂલ્યો માટે પાસ થવાની શક્યતા છે. હાઈપોથિસિસ 0, -1, 1, મોટી ધન સંખ્યાઓ, મોટી ઋણ સંખ્યાઓ, અને વધુ પ્રયાસ કરશે. આપણું સરળ ફંક્શન આ બધાને યોગ્ય રીતે હેન્ડલ કરે છે. હવે, ચાલો કોઈ નબળાઈ શોધી શકાય છે કે કેમ તે જોવા માટે એક અલગ સ્ટ્રેટેજી અજમાવીએ.
# ચાલો ફ્લોટિંગ પોઈન્ટ સંખ્યાઓ સાથે ટેસ્ટ કરીએ @given(st.floats()) def test_abs_floats_property(x): assert custom_abs(x) >= 0
જો તમે આ ચલાવો છો, તો હાઈપોથિસિસ ઝડપથી નિષ્ફળ કેસ શોધી કાઢશે!
Falsifying example: test_abs_floats_property(x=nan) ... assert custom_abs(nan) >= 0 AssertionError: assert nan >= 0
હાઈપોથિસિસએ શોધી કાઢ્યું કે જ્યારે float('nan')
(સંખ્યા નથી) આપવામાં આવે ત્યારે આપણું ફંક્શન nan
પરત કરે છે. nan >= 0
નો દાવો ખોટો છે. આપણે હમણાં જ એક સૂક્ષ્મ બગ શોધી કાઢ્યો છે જેનું આપણે કદાચ મેન્યુઅલી ટેસ્ટ કરવાનું વિચાર્યું ન હોત. આપણે આ કેસને હેન્ડલ કરવા માટે આપણા ફંક્શનને સુધારી શકીએ છીએ, કદાચ ValueError
ઉભું કરીને અથવા ચોક્કસ મૂલ્ય પરત કરીને.
તો પણ, જો બગ ખૂબ જ ચોક્કસ ફ્લોટ સાથે હતો તો શું? હાઈપોથિસિસનું શ્રિંકર એક મોટી, જટિલ નિષ્ફળ સંખ્યા લીધી હોત અને તેને સૌથી સરળ શક્ય સંસ્કરણમાં ઘટાડી દીધું હોત જે હજુ પણ બગને ટ્રિગર કરે છે.
સ્ટ્રેટેજીસની શક્તિ: તમારા ટેસ્ટ ડેટાનું નિર્માણ
સ્ટ્રેટેજીસ એ હાઈપોથિસિસનું હૃદય છે. તે ડેટા જનરેટ કરવા માટેની રેસિપી છે. લાઈબ્રેરીમાં બિલ્ટ-ઇન સ્ટ્રેટેજીસનો વિશાળ સંગ્રહ શામેલ છે, અને તમે કલ્પના કરી શકો તે કોઈપણ ડેટા સ્ટ્રક્ચરને જનરેટ કરવા માટે તમે તેમને જોડી અને કસ્ટમાઇઝ કરી શકો છો.
સામાન્ય બિલ્ટ-ઇન સ્ટ્રેટેજીસ
- સંખ્યાત્મક (Numeric):
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()
- ટેક્સ્ટ (Text):
st.text(min_size=1, max_size=50)
: ચોક્કસ લંબાઈની યુનિકોડ સ્ટ્રિંગ્સ જનરેટ કરે છે.st.text(alphabet='abcdef0123456789')
: ચોક્કસ અક્ષર સમૂહમાંથી સ્ટ્રિંગ્સ જનરેટ કરે છે (દા.ત., હેક્સ કોડ્સ માટે).st.characters()
: વ્યક્તિગત અક્ષરો જનરેટ કરે છે.
- સંગ્રહો (Collections):
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())
: નિર્દિષ્ટ કી અને મૂલ્ય પ્રકારો સાથે ડિક્શનરીઓ જનરેટ કરે છે.
- કાલિક (Temporal):
st.dates()
,st.times()
,st.datetimes()
,st.timedeltas()
. આને ટાઈમઝોન-અવેર બનાવી શકાય છે.
- વિવિધ (Miscellaneous):
st.booleans()
:True
અથવાFalse
જનરેટ કરે છે.st.just('constant_value')
: હંમેશા સમાન એકલ મૂલ્ય જનરેટ કરે છે. જટિલ સ્ટ્રેટેજીસ કમ્પોઝ કરવા માટે ઉપયોગી છે.st.one_of(st.integers(), st.text())
: પ્રદાન કરેલી સ્ટ્રેટેજીસમાંથી એકમાંથી મૂલ્ય જનરેટ કરે છે.st.none()
: ફક્તNone
જનરેટ કરે છે.
સ્ટ્રેટેજીસને જોડવું અને રૂપાંતરિત કરવું
હાઈપોથિસિસની વાસ્તવિક શક્તિ તેની સરળ સ્ટ્રેટેજીસમાંથી જટિલ સ્ટ્રેટેજીસ બનાવવાની ક્ષમતામાંથી આવે છે.
.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()
નો ઉપયોગ અકાર્યક્ષમ હોઈ શકે છે. જો શરત વારંવાર ખોટી હોય, તો હાઈપોથિસિસ માન્ય ઉદાહરણ જનરેટ કરવાનો પ્રયાસ કરવામાં લાંબો સમય વિતાવી શકે છે. વધુ સારો અભિગમ ઘણીવાર તમારી ટેસ્ટ ફંક્શનની અંદર assume()
નો ઉપયોગ કરવાનો છે:
from hypothesis import assume @given(st.lists(st.integers())) def test_something_with_non_zero_sum_list(numbers): assume(sum(numbers) != 0) # ... તમારી ટેસ્ટ લોજિક અહીં ...
assume()
tells Hypothesis: "If this condition isn't met, just discard this example and try a new one." It's a more direct and often more performant way to constrain your test data.
st.composite()
નો ઉપયોગ કરવો
ખરેખર જટિલ ડેટા જનરેશન માટે જ્યાં એક જનરેટ થયેલ મૂલ્ય બીજા પર આધાર રાખે છે, ત્યાં st.composite()
એ તમને જોઈતું સાધન છે. તે તમને એક ફંક્શન લખવાની મંજૂરી આપે છે જે draw
ફંક્શનને આર્ગ્યુમેન્ટ તરીકે લે છે, જેનો ઉપયોગ તમે અન્ય સ્ટ્રેટેજીસમાંથી સ્ટેપ-બાય-સ્ટેપ મૂલ્યો ખેંચવા માટે કરી શકો છો.
એક ક્લાસિક ઉદાહરણ એ એક સૂચિ અને તે સૂચિમાં માન્ય અનુક્રમણિકા (index) જનરેટ કરવાનું છે.
@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 # એક સરળ દાવો
કાર્યમાં હાઈપોથિસિસ: વાસ્તવિક-વિશ્વના દૃશ્યો
ચાલો આ ખ્યાલોને વધુ વાસ્તવિક સમસ્યાઓ પર લાગુ કરીએ જેનો સોફ્ટવેર ડેવલપર્સ દરરોજ સામનો કરે છે.
દૃશ્ય 1: ડેટા સિરીયલાઈઝેશન ફંક્શનનું ટેસ્ટિંગ
એક ફંક્શનની કલ્પના કરો જે વપરાશકર્તા પ્રોફાઇલ (એક ડિક્શનરી) ને URL-સુરક્ષિત સ્ટ્રિંગમાં સિરીયલાઈઝ કરે છે અને બીજું જે તેને ડિસિરીયલાઈઝ કરે છે. એક મુખ્ય ગુણધર્મ એ છે કે પ્રક્રિયા સંપૂર્ણપણે રિવર્સિબલ હોવી જોઈએ.
import json import base64 def serialize_profile(data: dict) -> str: """ડિક્શનરીને URL-સુરક્ષિત base64 સ્ટ્રિંગમાં સિરીયલાઈઝ કરે છે.""" 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
જ્યારે તમે આ ટેસ્ટ ચલાવો છો, ત્યારે હાઈપોથિસિસ ગુણધર્મ 2 માટે ઝડપથી એક નિષ્ફળ ઉદાહરણ શોધી કાઢશે, જેમ કે numbers=[0, 0]
. આપણું ફંક્શન [0]
પરત કરે છે, અને Counter([0, 0])
એ Counter([0])
બરાબર નથી. શ્રિંકર ખાતરી કરશે કે નિષ્ફળ ઉદાહરણ શક્ય તેટલું સરળ છે, જે બગનું કારણ તાત્કાલિક સ્પષ્ટ બનાવશે.
દૃશ્ય 3: સ્ટેટફુલ ટેસ્ટિંગ
આંતરિક સ્થિતિ ધરાવતા ઑબ્જેક્ટ્સ માટે જે સમય જતાં બદલાય છે (જેમ કે ડેટાબેઝ કનેક્શન, શોપિંગ કાર્ટ, અથવા કેશ), ભૂલો શોધવી અતિ મુશ્કેલ હોઈ શકે છે. ખામીને ટ્રિગર કરવા માટે ઑપરેશન્સના ચોક્કસ અનુક્રમની જરૂર પડી શકે છે. હાઈપોથિસિસ આ હેતુ માટે જ `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
હાઈપોથિસિસ હવે `set_key`, `delete_key`, `get_key`, અને `check_size` ઑપરેશન્સના રેન્ડમ અનુક્રમોને અમલમાં મૂકશે, નિરંતર એવા અનુક્રમ શોધવાનો પ્રયાસ કરશે જે કોઈપણ દાવાને નિષ્ફળ બનાવે. તે તપાસશે કે કાઢી નાખેલી કી મેળવવાથી યોગ્ય રીતે વર્તે છે કે કેમ, બહુવિધ સેટ અને ડિલીટ પછી કદ સુસંગત છે કે કેમ, અને ઘણા અન્ય દૃશ્યો જેનું તમે મેન્યુઅલી ટેસ્ટ કરવાનું વિચારી પણ ન શકો.
શ્રેષ્ઠ પ્રથાઓ અને અદ્યતન ટિપ્સ
- ઉદાહરણ ડેટાબેઝ: હાઈપોથિસિસ સ્માર્ટ છે. જ્યારે તે કોઈ બગ શોધે છે, ત્યારે તે નિષ્ફળ ઉદાહરણને સ્થાનિક ડિરેક્ટરી (
.hypothesis/
) માં સાચવે છે. આગલી વખતે જ્યારે તમે તમારા ટેસ્ટ ચલાવો છો, ત્યારે તે પહેલા તે નિષ્ફળ ઉદાહરણને ફરીથી ચલાવશે, તમને તાત્કાલિક પ્રતિસાદ આપશે કે બગ હજી પણ હાજર છે. એકવાર તમે તેને ઠીક કરી લો, પછી ઉદાહરણ ફરીથી ચલાવવામાં આવતું નથી. @settings
સાથે ટેસ્ટ એક્ઝેક્યુશનને નિયંત્રિત કરવું: તમે@settings
ડેકોરેટરનો ઉપયોગ કરીને ટેસ્ટ રનના ઘણા પાસાઓને નિયંત્રિત કરી શકો છો. તમે ઉદાહરણોની સંખ્યા વધારી શકો છો, એક ઉદાહરણ કેટલો સમય ચાલી શકે તે માટે સમયમર્યાદા સેટ કરી શકો છો (અનંત લૂપ્સ પકડવા માટે), અને અમુક હેલ્થ ચેક્સ બંધ કરી શકો છો.@settings(max_examples=500, deadline=1000) # 500 ઉદાહરણો ચલાવો, 1-સેકન્ડની સમયમર્યાદા @given(...) ...
- નિષ્ફળતાઓને પુનરાવર્તિત કરવી: દરેક હાઈપોથિસિસ રન એક સીડ મૂલ્ય છાપે છે (દા.ત.,
@reproduce_failure('version', 'seed')
). જો CI સર્વર કોઈ બગ શોધે છે જે તમે સ્થાનિક રીતે પુનરાવર્તિત કરી શકતા નથી, તો તમે પ્રદાન કરેલા સીડ સાથે આ ડેકોરેટરનો ઉપયોગ કરીને હાઈપોથિસિસને ઉદાહરણોનો બરાબર સમાન અનુક્રમ ચલાવવા માટે દબાણ કરી શકો છો. - CI/CD સાથે એકીકરણ: હાઈપોથિસિસ કોઈપણ કન્ટિન્યુઅસ ઇન્ટિગ્રેશન પાઇપલાઇન માટે સંપૂર્ણ રીતે બંધબેસે છે. ઉત્પાદન સુધી પહોંચતા પહેલા અસ્પષ્ટ બગ્સ શોધવાની તેની ક્ષમતા તેને એક અમૂલ્ય સલામતી જાળ બનાવે છે.
માનસિકતામાં પરિવર્તન: ગુણધર્મોમાં વિચારવું
હાઈપોથિસિસ અપનાવવું એ ફક્ત નવી લાઈબ્રેરી શીખવા કરતાં વધુ છે; તે તમારા કોડની શુદ્ધતા વિશે વિચારવાની નવી રીત અપનાવવા વિશે છે. "મારે કયા ઇનપુટ્સનું ટેસ્ટ કરવું જોઈએ?" પૂછવાને બદલે, તમે પૂછવાનું શરૂ કરો છો, "આ કોડ વિશે સાર્વત્રિક સત્યો શું છે?"
ગુણધર્મો ઓળખવાનો પ્રયાસ કરતી વખતે તમને માર્ગદર્શન આપવા માટે અહીં કેટલાક પ્રશ્નો આપેલા છે:
- શું કોઈ વિપરીત ઑપરેશન છે? (દા.ત., સિરીયલાઈઝ/ડિસિરીયલાઈઝ, એન્ક્રિપ્ટ/ડિક્રિપ્ટ, કમ્પ્રેસ/ડિકમ્પ્રેસ). ગુણધર્મ એ છે કે ઑપરેશન અને તેનું વિપરીત કરવાથી મૂળ ઇનપુટ મળવું જોઈએ.
- શું ઑપરેશન ઇડેમ્પોટન્ટ છે? (દા.ત.,
abs(abs(x)) == abs(x)
). ફંક્શનને એક કરતા વધુ વાર લાગુ કરવાથી તેને એકવાર લાગુ કરવા જેવું જ પરિણામ મળવું જોઈએ. - શું તે જ પરિણામની ગણતરી કરવાની કોઈ અલગ, સરળ રીત છે? તમે ટેસ્ટ કરી શકો છો કે તમારું જટિલ, ઑપ્ટિમાઇઝ્ડ ફંક્શન એક સરળ, સ્પષ્ટપણે સાચા સંસ્કરણ જેવું જ આઉટપુટ આપે છે (દા.ત., તમારા ફેન્સી સૉર્ટને પાયથોનના બિલ્ટ-ઇન
sorted()
સામે ટેસ્ટ કરવું). - આઉટપુટ વિશે હંમેશા શું સાચું હોવું જોઈએ? (દા.ત., `find_prime_factors` ફંક્શનના આઉટપુટમાં ફક્ત પ્રાઈમ નંબર્સ જ હોવા જોઈએ, અને તેમનો ગુણાકાર ઇનપુટ જેટલો હોવો જોઈએ).
- સ્થિતિ કેવી રીતે બદલાય છે? (સ્ટેટફુલ ટેસ્ટિંગ માટે) કોઈપણ માન્ય ઑપરેશન પછી કયા ઇનવેરિયન્ટ્સ જાળવી રાખવા જોઈએ? (દા.ત., શોપિંગ કાર્ટમાં વસ્તુઓની સંખ્યા ક્યારેય નકારાત્મક હોઈ શકે નહીં).
નિષ્કર્ષ: આત્મવિશ્વાસનું નવું સ્તર
હાઈપોથિસિસ સાથે પ્રોપર્ટી-આધારિત ટેસ્ટિંગ ઉદાહરણ-આધારિત ટેસ્ટિંગનું સ્થાન લેતું નથી. તમારે હજુ પણ જટિલ વ્યવસાયિક તર્ક અને સારી રીતે સમજાતી જરૂરિયાતો માટે ચોક્કસ, હાથથી લખેલા ટેસ્ટની જરૂર છે (દા.ત., "દેશ X ના વપરાશકર્તાને કિંમત Y દેખાડવી જ જોઈએ").
હાઈપોથિસિસ તમારા કોડના વર્તનને અન્વેષણ કરવા અને અણધારી એજ કેસો સામે રક્ષણ આપવા માટે એક શક્તિશાળી, સ્વચાલિત રીત પ્રદાન કરે છે. તે એક અથાક ભાગીદાર તરીકે કાર્ય કરે છે, હજારો ટેસ્ટ જનરેટ કરે છે જે કોઈપણ મનુષ્ય વાસ્તવિક રીતે લખી શકે તેના કરતાં વધુ વૈવિધ્યસભર અને કપટી હોય છે. તમારા કોડના મૂળભૂત ગુણધર્મોને વ્યાખ્યાયિત કરીને, તમે એક મજબૂત સ્પષ્ટીકરણ બનાવો છો જેની સામે હાઈપોથિસિસ ટેસ્ટ કરી શકે છે, જે તમને તમારા સોફ્ટવેરમાં આત્મવિશ્વાસનું નવું સ્તર આપે છે.
આગલી વખતે જ્યારે તમે કોઈ ફંક્શન લખો, ત્યારે ઉદાહરણોથી આગળ વિચારવા માટે થોડો સમય કાઢો. તમારી જાતને પૂછો, "નિયમો શું છે? હંમેશા શું સાચું હોવું જોઈએ?" પછી, હાઈપોથિસિસને તેમને તોડવાનો પ્રયાસ કરવાનું મુશ્કેલ કાર્ય કરવા દો. તે શું શોધે છે તે જોઈને તમે આશ્ચર્યચકિત થશો, અને તમારો કોડ તેનાથી વધુ સારો બનશે.