পাঠ ০৭ · ৫৭-এর মধ্যে · মডিউল ২
Home / Courses / Software Testing & Quality Assurance / ডিসিশন টেবিল টেস্টিং

ডিসিশন টেবিল টেস্টিং

Decision table testing
৯ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ডিসিশন টেবিল টেস্টিং কখন ব্যবহার করতে হয় (একাধিক শর্ত একসাথে ফলাফল নির্ধারণ করলে)
  • একটি স্পেসিফিকেশন থেকে সম্পূর্ণ ডিসিশন টেবিল কীভাবে বানাতে হয়
  • টেবিলের প্রতিটি সারিকে প্রোগ্রাম্যাটিকভাবে একটি ফাংশন-আন্ডার-টেস্টের বিরুদ্ধে যাচাই করা
  • Python-এর unittest দিয়ে একটি সত্যিকারের মিসিং রুল-কম্বিনেশন বাগ ধরে ফেলার ডেমো

১ · ডিসিশন টেবিল টেস্টিং কী

ডিসিশন টেবিল টেস্টিংDecision Table Testingএকাধিক বুলিয়ান শর্তের সবগুলো সম্ভাব্য সমন্বয়কে একটি টেবিলে সাজিয়ে, প্রতিটি সমন্বয়ের জন্য প্রত্যাশিত ফলাফল নির্ধারণ করে, তারপর একটি ফাংশন-আন্ডার-টেস্টের প্রকৃত ফলাফল প্রতিটি সারির সাথে মিলিয়ে দেখা। ধরুন একটি শিপিং নিয়ম নির্ভর করে দুইটি স্বাধীন শর্তের উপর — গ্রাহক কি is_member (মেম্বার), আর অর্ডার টোটাল কি order_over_1000 (১০০০ টাকার বেশি)। এই দুইটি বুলিয়ান শর্তের 2 × 2 = 4টি সম্ভাব্য সমন্বয় আছে — ডিসিশন টেবিল টেস্টিং বলে, "শুধু কিছু সমন্বয় নয়, চারটি সমন্বয়ই আলাদা টেস্ট কেস হিসেবে যাচাই করো।"

সারি is_member order_over_1000 প্রত্যাশিত ফলাফল
১ True True ফ্রি শিপিং + ১৫% ছাড়
২ True False ফ্রি শিপিং + ৫% ছাড়
৩ False True ফ্রি শিপিং + ০% ছাড়
৪ False False পেইড শিপিং + ০% ছাড়

উপরের চারটি সারিই এই স্পেসিফিকেশনের সম্পূর্ণ ডিসিশন টেবিল — নিচের কোড সেলে এটিকে একটি Python তালিকা হিসেবে লিখে প্রোগ্রাম্যাটিকভাবে যাচাই করা হয়েছে।

২ · কোড সেলে টেবিলটি যাচাই করা

নিচের determine_shipping_and_discount ফাংশনটি উপরের নিয়ম বাস্তবায়ন করার চেষ্টা করেছে — কিন্তু দেখা যাক প্রতিটি সারি সত্যিই মিলছে কি না।

Python
import unittest

def determine_shipping_and_discount(is_member, order_over_1000):
    # নিয়ম: মেম্বার + ১০০০+ অর্ডার = ফ্রি শিপিং + ১৫% ছাড়
    #        মেম্বার + ১০০০-এর কম = ফ্রি শিপিং + ৫% ছাড়
    #        নন-মেম্বার + ১০০০+ অর্ডার = ফ্রি শিপিং + ০% ছাড়
    #        নন-মেম্বার + ১০০০-এর কম = পেইড শিপিং + ০% ছাড়
    if is_member and order_over_1000:
        return {"shipping": "free", "discount_pct": 15}
    elif is_member and not order_over_1000:
        return {"shipping": "free", "discount_pct": 5}
    else:
        # বাগ: নন-মেম্বার হয়েও অর্ডার ১০০০+ হলে ফ্রি শিপিং পাওয়ার কথা ছিল, কিন্তু ভুলে সবসময় পেইড রাখা হয়েছে
        return {"shipping": "paid", "discount_pct": 0}

DECISION_TABLE = [
    {"is_member": True,  "order_over_1000": True,  "expected": {"shipping": "free", "discount_pct": 15}},
    {"is_member": True,  "order_over_1000": False, "expected": {"shipping": "free", "discount_pct": 5}},
    {"is_member": False, "order_over_1000": True,  "expected": {"shipping": "free", "discount_pct": 0}},
    {"is_member": False, "order_over_1000": False, "expected": {"shipping": "paid", "discount_pct": 0}},
]

print("ডিসিশন টেবিল যাচাই:")
for i, row in enumerate(DECISION_TABLE, start=1):
    actual = determine_shipping_and_discount(row["is_member"], row["order_over_1000"])
    status = "PASS" if actual == row["expected"] else "FAIL"
    print(f"  [{status}] সারি {i}: is_member={row['is_member']}, order_over_1000={row['order_over_1000']} "
          f"-- প্রত্যাশিত={row['expected']} পেয়েছে={actual}")

class ShippingDecisionTableTests(unittest.TestCase):
    def test_row1_member_over_1000(self):
        self.assertEqual(determine_shipping_and_discount(True, True),
                          {"shipping": "free", "discount_pct": 15})

    def test_row2_member_under_1000(self):
        self.assertEqual(determine_shipping_and_discount(True, False),
                          {"shipping": "free", "discount_pct": 5})

    def test_row3_nonmember_over_1000(self):
        self.assertEqual(determine_shipping_and_discount(False, True),
                          {"shipping": "free", "discount_pct": 0})

    def test_row4_nonmember_under_1000(self):
        self.assertEqual(determine_shipping_and_discount(False, False),
                          {"shipping": "paid", "discount_pct": 0})

suite = unittest.TestLoader().loadTestsFromTestCase(ShippingDecisionTableTests)
unittest.TextTestRunner(verbosity=2).run(suite)

    
লক্ষ্য করুন সারি ৩ ব্যর্থ হয় — is_member=False, order_over_1000=True হলে ফাংশনটি ভুলভাবে {"shipping": "paid", "discount_pct": 0} রিটার্ন করে, অথচ স্পেসিফিকেশন অনুযায়ী {"shipping": "free", "discount_pct": 0} হওয়ার কথা ছিল। কোডের else শাখাটি "নন-মেম্বার + ১০০০+" আর "নন-মেম্বার + ১০০০-এর কম" — দুটো ভিন্ন সমন্বয়কে ভুলভাবে একসাথে মিলিয়ে ফেলেছে। যদি টেস্টার শুধু "সাধারণ" কেসগুলো (যেমন শুধু মেম্বার কেস দুটো) টেস্ট করতেন, এই বাগ কখনোই ধরা পড়ত না — ডিসিশন টেবিল টেস্টিং জোর করে প্রতিটি সমন্বয় যাচাই করায় বলেই এটি ধরা পড়ল।
মূল কথা · Key takeaway

ডিসিশন টেবিল টেস্টিং সবচেয়ে বেশি কার্যকর যখন ফলাফল একাধিক স্বাধীন শর্তের সমন্বয়ে ঠিক হয় — একটি if/elif/else চেইনে একটি সমন্বয় ভুলভাবে অন্য সমন্বয়ের সাথে মিলে যাওয়া একটি খুবই সাধারণ ভুল, আর টেবিলের প্রতিটি সারি আলাদাভাবে যাচাই করাই এই ধরনের ভুল ধরার সবচেয়ে নির্ভরযোগ্য উপায়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ উপরের কোডে ঠিক কীভাবে determine_shipping_and_discount ফাংশনটি ঠিক করবেন যাতে চারটি সারিই পাস করে?

else-এর আগে আরেকটি শাখা যোগ করতে হবে যা শুধু order_over_1000 চেক করে: elif order_over_1000: return {"shipping": "free", "discount_pct": 0} — তারপর else-এ শুধু "নন-মেম্বার + ১০০০-এর কম" কেসটি থাকবে, যা {"shipping": "paid", "discount_pct": 0} রিটার্ন করবে।

প্র ০২ যদি একটি তৃতীয় বুলিয়ান শর্ত যোগ হয় (যেমন has_valid_coupon), টেবিলে কতটি সারি হবে, আর কেন?

2³ = 8টি সারি হবে — কারণ প্রতিটি নতুন স্বাধীন বুলিয়ান শর্ত আগের প্রতিটি সমন্বয়কে দুই ভাগে ভাগ করে দেয় (শর্তটি True হলে একটি, False হলে আরেকটি)। এই কারণেই ব্যবহারিকভাবে অনেকগুলো স্বাধীন শর্ত থাকলে সম্পূর্ণ ডিসিশন টেবিল দ্রুত বড় হয়ে যায়, আর টেস্টাররা তখন প্রায়ই সবচেয়ে গুরুত্বপূর্ণ সমন্বয়গুলো অগ্রাধিকার দিয়ে বেছে নেন।

প্র ০৩ ডিসিশন টেবিল টেস্টিং আর ইকুইভ্যালেন্স পার্টিশনিং (L05)-এর মধ্যে মূল পার্থক্য কী?

ইকুইভ্যালেন্স পার্টিশনিং একটি একক ইনপুট-এর মানের রেঞ্জকে গ্রুপে ভাগ করে (যেমন একটি বয়স ভ্যালু)। ডিসিশন টেবিল টেস্টিং তখন কাজে লাগে যখন ফলাফল একাধিক স্বাধীন শর্তের সমন্বয়ে নির্ধারিত হয় — প্রতিটি শর্তের নিজস্ব True/False মান থাকতে পারে, এবং তাদের সমন্বয়ই আসল ফলাফল ঠিক করে।

অনুশীলন

  1. চিন্তা করুন: একটি লাইব্রেরি সিস্টেমে বই ধার দেওয়ার নিয়ম নির্ভর করে দুইটি শর্তের উপর — has_overdue_book (আগের কোনো বই মেয়াদোত্তীর্ণ কি না) আর membership_active (মেম্বারশিপ সক্রিয় কি না)। এর জন্য সম্পূর্ণ ডিসিশন টেবিলে কয়টি সারি থাকবে, এবং প্রতিটির প্রত্যাশিত ফলাফল কী হতে পারে বলে মনে করেন?

    2² = 4টি সারি: (overdue=False, active=True) → ধার দেওয়া যাবে; (overdue=False, active=False) → না; (overdue=True, active=True) → না (মেয়াদোত্তীর্ণ বই থাকলে নতুন বই দেওয়া উচিত নয়); (overdue=True, active=False) → না। এখানে দুটো আলাদা শর্তই একই ফলাফলে (প্রত্যাখ্যান) নিয়ে যেতে পারে — এটাও ডিসিশন টেবিলে স্পষ্টভাবে ধরা পড়ে।

  2. পরীক্ষা করুন: উপরের কোড সেলে বাগ ঠিক করতে else-এর আগে elif order_over_1000: return {"shipping": "free", "discount_pct": 0} যোগ করে Run চেপে দেখুন চারটি সারিই এখন পাস করে কি না।

    হ্যাঁ — এই শাখা যোগ করার পর সারি ৩ (নন-মেম্বার + ১০০০+) সঠিকভাবে {"shipping": "free", "discount_pct": 0} রিটার্ন করবে, তাই চারটি unittest টেস্টই পাস করবে এবং আউটপুটে "OK" দেখাবে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
বাউন্ডারি ভ্যালু অ্যানালাইসিস