ডিফেক্ট/বাগ লাইফসাইকেল
এই পাঠে যা শিখবেন
- একটি সাধারণ ডিফেক্ট/বাগ লাইফসাইকেলের অবস্থাগুলো এবং প্রতিটির অর্থ
ALLOWEDট্রানজিশন ডিকশনারি দিয়ে বৈধ/অবৈধ অবস্থা-পরিবর্তন কীভাবে মডেল করতে হয়- একটি সত্যিকারের
transition(new_state, reason)মেথড যা অবৈধ পরিবর্তন প্রত্যাখ্যান করে - একটি বাস্তবসম্মত সিকোয়েন্স চালিয়ে সম্পূর্ণ ট্রানজিশন হিস্টোরি — রিওপেনসহ — সত্যিকারভাবে যাচাই
১ · একটি ডিফেক্টের জীবনচক্র
একটি বাগ রিপোর্ট হওয়ার মুহূর্ত থেকে চূড়ান্তভাবে বন্ধ হওয়া পর্যন্ত এটি সাধারণত কয়েকটি সুনির্দিষ্ট অবস্থার মধ্য দিয়ে যায় — এলোমেলোভাবে যেকোনো অবস্থা থেকে যেকোনো অবস্থায় লাফ দেয় না। এই শৃঙ্খলাই নিশ্চিত করে একটি বাগ কখনো "হারিয়ে" যায় না — প্রতিটি মুহূর্তে জানা যায় এটি ঠিক কোন পর্যায়ে আছে।
বাগ সবেমাত্র রিপোর্ট হয়েছে, এখনো কেউ পর্যালোচনা করেনি।
QA লিড/টিম যাচাই করেছে এটি সত্যিকারের বাগ কি না, এবং প্রায়োরিটি ঠিক করেছে।
একজন ডেভেলপারকে অ্যাসাইন করা হয়েছে, ফিক্সের কাজ চলছে।
ফিক্স কমিট ও ডিপ্লয় করা হয়েছে (সাধারণত স্টেজিং-এ), কিন্তু এখনো QA দ্বারা যাচাই হয়নি।
QA ফিক্সটি স্বতন্ত্রভাবে পরীক্ষা করে নিশ্চিত করেছে এটি সত্যিই কাজ করছে।
রিলিজে অন্তর্ভুক্ত হয়েছে, বাগটি চূড়ান্তভাবে বন্ধ।
কিন্তু বাস্তবে ফিক্স সবসময় প্রথমবারেই কাজ করে না — প্রোডাকশনে গিয়ে দেখা যায় একই বাগ (বা এর একটি অংশ) এখনো
আছে। তখন দরকার হয় একটি reopened পথ — verified বা closed থেকে
আবার in_progress-এ ফিরিয়ে নেওয়ার।
২ · স্টেট মেশিন দিয়ে মডেল করা
এই ধরনের "নির্দিষ্ট কিছু অবস্থা, নির্দিষ্ট কিছু বৈধ পরিবর্তন" আচরণ মডেল করার সবচেয়ে পরিষ্কার উপায় একটি
স্টেট মেশিন — একটি ALLOWED ডিকশনারি যেখানে প্রতিটি কী (key) একটি বর্তমান
অবস্থা, আর মান (value) হলো সেখান থেকে যেসব অবস্থায় বৈধভাবে যাওয়া যায় তার একটি সেট। একটি
transition(new_state, reason) মেথড অনুরোধ করা পরিবর্তনটি এই ডিকশনারির বিপরীতে যাচাই করে —
বৈধ হলে অবস্থা পরিবর্তন করে ও কারণ লগ করে, অবৈধ হলে এরর তোলে।
# বর্তমান অবস্থা থেকে যেসব অবস্থায় বৈধভাবে যাওয়া যায়
ALLOWED = {
"new": {"triaged"},
"triaged": {"in_progress"},
"in_progress": {"fixed"},
"fixed": {"verified"},
"verified": {"closed", "reopened"},
"closed": {"reopened"},
"reopened": {"in_progress"},
}
class Defect:
def __init__(self, defect_id):
self.defect_id = defect_id
self.state = "new"
self.history = [("new", "ডিফেক্ট রিপোর্ট করা হয়েছে")]
def transition(self, new_state, reason):
allowed = ALLOWED.get(self.state, set())
if new_state not in allowed:
raise ValueError(
f"অবৈধ ট্রানজিশন: '{self.state}' থেকে '{new_state}'-এ যাওয়া যায় না "
f"(অনুমোদিত: {sorted(allowed) if allowed else 'কোনোটিই না'})"
)
self.state = new_state
self.history.append((new_state, reason))
def __repr__(self):
return f"Defect({self.defect_id}, state={self.state!r})"
৩ · একটি বাস্তবসম্মত সিকোয়েন্স চালিয়ে দেখা
এখন উপরের Defect ক্লাস ব্যবহার করে একটি সম্পূর্ণ, বাস্তবসম্মত জীবনচক্র চালিয়ে দেখা যাক —
স্বাভাবিক পথ দিয়ে closed পর্যন্ত যাওয়া, তারপর প্রোডাকশনে একই বাগ আবার দেখা দেওয়ায়
reopened হয়ে আবার পুরো চক্র সম্পন্ন করা। মাঝখানে একটি ইচ্ছাকৃতভাবে অবৈধ
ট্রানজিশনও চেষ্টা করা হয়েছে — verified অবস্থা থেকে সরাসরি in_progress-এ যাওয়ার
চেষ্টা, যা ALLOWED অনুযায়ী বৈধ নয় (শুধু reopened হয়ে তারপর
in_progress-এ যাওয়া যায়, সরাসরি নয়)।
bug = Defect("BUG-1042")
bug.transition("triaged", "QA লিড যাচাই করে ট্রায়াজ করেছেন, প্রায়োরিটি: হাই")
bug.transition("in_progress", "ডেভেলপারকে অ্যাসাইন করা হয়েছে, কাজ শুরু")
bug.transition("fixed", "ফিক্স কমিট ও স্টেজিং-এ ডিপ্লয় করা হয়েছে")
bug.transition("verified", "QA স্টেজিং-এ ফিক্স যাচাই করেছে, সঠিক আচরণ করছে")
# ইচ্ছাকৃতভাবে অবৈধ ট্রানজিশন — verified থেকে সরাসরি in_progress-এ যাওয়ার চেষ্টা
try:
bug.transition("in_progress", "ভুলবশত সরাসরি in_progress-এ ফেরানোর চেষ্টা")
except ValueError as e:
print("প্রত্যাশিতভাবে প্রত্যাখ্যাত:", e)
bug.transition("closed", "রিলিজ ২.৪-এ অন্তর্ভুক্ত হয়েছে, বন্ধ করা হলো")
# --- প্রোডাকশনে একই বাগ আবার দেখা গেছে ---
bug.transition("reopened", "প্রোডাকশনে একই সমস্যা আবার রিপোর্ট হয়েছে")
bug.transition("in_progress", "ডেভেলপার আবার কাজ শুরু করেছেন, রুট কজ ভিন্ন ছিল")
bug.transition("fixed", "নতুন, সম্পূর্ণ ফিক্স কমিট করা হয়েছে")
bug.transition("verified", "QA আবার যাচাই করেছে, এবার সত্যিই ঠিক আছে")
bug.transition("closed", "চূড়ান্তভাবে বন্ধ করা হলো")
print(f"\n{bug.defect_id}-এর চূড়ান্ত অবস্থা: {bug.state}")
print("সম্পূর্ণ ট্রানজিশন হিস্টোরি:")
for i, (state, reason) in enumerate(bug.history, start=1):
print(f" {i}. [{state}] {reason}")
verified → in_progress)
history-তে যোগ হয়নি — কারণ transition() মেথড ভ্যালিডেশন ব্যর্থ হলে
self.state পরিবর্তন করার আগেই ValueError তোলে। চূড়ান্ত হিস্টোরিতে মোট ১১টি
এন্ট্রি দেখা যাবে — প্রথম new এন্ট্রি থেকে শুরু করে দুইবার সম্পূর্ণ চক্র (একটি স্বাভাবিক,
একটি রিওপেনের পর) সম্পন্ন হওয়া পর্যন্ত — প্রতিটি ধাপ ALLOWED ডিকশনারি অনুযায়ী সত্যিই বৈধ
ছিল বলেই সফল হয়েছে।
M2/L08-এ আমরা শিখেছিলাম কীভাবে একটি স্টেট মেশিনের প্রতিটি বৈধ ট্রানজিশন অন্তত একবার পরীক্ষা করতে হয়, আর অন্তত একটি অবৈধ ট্রানজিশন প্রত্যাখ্যাত হচ্ছে কি না তা যাচাই করতে হয়। উপরের সিকোয়েন্স ঠিক সেই নীতিই বাস্তবে প্রয়োগ করছে — শুধু এবার বিমূর্ত উদাহরণ নয়, বরং একটি বাস্তব প্রক্রিয়া (ডিফেক্ট ম্যানেজমেন্ট) মডেল করতে।
একটি ডিফেক্ট এলোমেলোভাবে যেকোনো অবস্থায় যেতে পারে না — একটি স্পষ্ট ALLOWED ট্রানজিশন
ডিকশনারি এই নিয়মগুলো কোডে এনফোর্স করে, ভুল/অসম্ভব পরিবর্তন ঠেকিয়ে দেয় (যেমন সরাসরি new
থেকে closed-এ চলে যাওয়া), এবং reopened পথ বাস্তবতাকে প্রতিফলিত করে — ফিক্স
সবসময় প্রথমবারেই কাজ করে না। বাস্তব JIRA/Bugzilla-এর মতো টুল ঠিক এই ধরনের ওয়ার্কফ্লো ইঞ্জিন ব্যবহার করে,
যদিও তাদের নির্দিষ্ট অবস্থার নাম ও পথ ভিন্ন হতে পারে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
ALLOWED ডিকশনারিতে "new": {"triaged"} আছে, কিন্তু "new":
{"triaged", "closed"} নেই — এই সীমাবদ্ধতা কেন গুরুত্বপূর্ণ?
যদি new থেকে সরাসরি closed-এ যাওয়া বৈধ হতো, তাহলে কেউ ট্রায়াজ বা ফিক্স
ছাড়াই একটি বাগ "বন্ধ" করে দিতে পারতেন — যা ট্র্যাকিং-এর পুরো উদ্দেশ্যকেই নষ্ট করে দেয়। ইচ্ছাকৃতভাবে
বাতিল করা বাগের জন্য (যেমন ডুপ্লিকেট বা "বাগ নয়") বাস্তব টুলে সাধারণত আলাদা একটি
rejected/wont_fix পথ থাকে, একই closed নয়।
প্র ০২
উপরের কোডে verified থেকে সরাসরি in_progress-এ যাওয়ার চেষ্টা কেন
প্রত্যাখ্যাত হলো, অথচ reopened থেকে in_progress-এ যাওয়া বৈধ?
কারণ ALLOWED["verified"] = {"closed", "reopened"} — এতে in_progress নেই।
এই ডিজাইনের উদ্দেশ্য হলো, "ফিক্স আবার কাজ শুরু হচ্ছে" এই ঘটনাটি স্পষ্টভাবে reopened
অবস্থা হিসেবে লগ হোক, যাতে হিস্টোরি দেখেই বোঝা যায় এটি একটি রিগ্রেশন/রিওপেন কেস — সরাসরি
in_progress-এ ফিরে গেলে সেই গুরুত্বপূর্ণ তথ্যটি হিস্টোরি থেকে হারিয়ে যেত।
প্র ০৩
কেন transition() মেথডে ভ্যালিডেশন ব্যর্থ হলে self.state পরিবর্তন করার
আগেই এরর তোলা জরুরি, পরে তোলা নয়?
যদি self.state আগে সেট করে তারপর যাচাই করা হতো, তাহলে একটি ব্যর্থ ভ্যালিডেশনও অবস্থা
পরিবর্তন করে ফেলতে পারত (অথবা এরর হ্যান্ডলিং-এ ভুল হলে অসামঞ্জস্যপূর্ণ অবস্থা থেকে যেত) — অবজেক্টটি
এমন একটি অবস্থায় চলে যেত যা ALLOWED অনুযায়ী কখনোই পৌঁছানো উচিত ছিল না। আগে যাচাই, পরে
পরিবর্তন — এই ক্রম নিশ্চিত করে ব্যর্থ চেষ্টা অবজেক্টের প্রকৃত অবস্থাকে স্পর্শই করে না।
অনুশীলন
-
চিন্তা করুন: আপনি যদি এই স্টেট মেশিনে একটি
rejectedঅবস্থা (ডুপ্লিকেট বা "বাগ নয়" হিসেবে চিহ্নিত হলে) যোগ করতে চান,ALLOWED-এ ঠিক কোথায় কোথায় এন্ট্রি যোগ/পরিবর্তন করতে হবে?অন্তত দুটো পরিবর্তন দরকার:
ALLOWED["triaged"]-কে{"in_progress", "rejected"}-এ পরিবর্তন করা (ট্রায়াজের সময়ই বোঝা যায় এটি বাগ নয় কি না), এবংALLOWED["rejected"] = set()যোগ করা (একবার প্রত্যাখ্যাত হলে সেখান থেকে আর কোথাও যাওয়া যাবে না, একটি "টার্মিনাল" অবস্থা হিসেবে) — যদি না আপনি কাউকে ভুল প্রত্যাখ্যান চ্যালেঞ্জ করে আবারtriaged-এ ফেরানোর সুযোগও দিতে চান। -
পরীক্ষা করুন: উপরের দ্বিতীয় কোড সেলে
bug.transition("closed", "...")-এর ঠিক পরেbug.transition("triaged", "ভুল চেষ্টা")যোগ করে Run চেপে দেখুন কী হয়।এটি একটি
ValueErrorতুলবে, কারণALLOWED["closed"] = {"reopened"}— এতেtriagedনেই। যদি এই কলটিtry/except-এ না জড়ানো হয়, পুরো কোড সেলের বাকি অংশ (রিওপেন সিকোয়েন্সসহ) আর চলবে না, কারণ এরর প্রোগ্রামটিকে থামিয়ে দেবে — এটিই দেখায় কেন বাস্তব কোডে এই ধরনের ইচ্ছাকৃত-অবৈধ কল সবসময়try/except-এ ঘিরে রাখা হয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ টেস্ট ডিজাইন টেকনিক, ইউনিট টেস্টিং, টেস্ট ডাবলস, কভারেজ, অটোমেশন, পারফরম্যান্স ও সিকিউরিটি টেস্টিং, টেস্ট ম্যানেজমেন্ট ও CI/CD ইন্টিগ্রেশন — সব একসাথে।
- পরের পাঠ: রিস্ক-বেসড টেস্টিং ও প্রায়োরিটাইজেশন L49 সীমিত সময়ে কোন টেস্ট এলাকায় সবচেয়ে বেশি মনোযোগ দেওয়া উচিত, তা likelihood × impact দিয়ে গণনা করে ঠিক করা।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks, Mobile App Development, Ethics in Computing & AI Safety ও Software Testing & Quality Assurance — সব এক জায়গায়।