পাঠ ২৬ · ৫৭-এর মধ্যে · মডিউল ৬
Home / Courses / Software Testing & Quality Assurance / টেস্ট কভারেজ ও মেট্রিক্স

কভারেজের সীমাবদ্ধতা — ১০০% কভারেজ মানে ১০০% সঠিকতা নয়

Coverage's limits — 100% coverage isn't 100% correctness
১১ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কেন ১০০% স্টেটমেন্ট/ব্রাঞ্চ কভারেজ কোডের সঠিকতার প্রমাণ নয়
  • "লাইন চলা" বনাম "ফলাফল যাচাই করা"-এর মধ্যেকার সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ পার্থক্য
  • একটি genuine উদাহরণে দেখা কীভাবে দুর্বল assertion একটি বাস্তব বাগ মিস করে যায়
  • কেন assertion-এর মান (কী যাচাই করা হচ্ছে) কভারেজ সংখ্যার চেয়ে বেশি গুরুত্বপূর্ণ

১ · একটি লাইন চলা, আর তার ফলাফল সঠিক হওয়া — এক জিনিস নয়

M6-এর আগের তিনটি পাঠে (L23-L25) আমরা স্টেটমেন্ট, ব্রাঞ্চ, কন্ডিশন ও ডিসিশন কভারেজ কীভাবে গণনা করা যায় তা শিখেছি। কিন্তু এই সবকটি মেট্রিকের একটি সাধারণ, গুরুত্বপূর্ণ সীমাবদ্ধতা আছে — এরা সবাই শুধু প্রশ্ন করে "কোড এক্সিকিউট হয়েছে কি না", কখনো প্রশ্ন করে না "এক্সিকিউট হওয়া কোডের ফলাফল সঠিক কি না"। একটি টেস্ট ১০০% কভারেজ পেতে পারে অথচ যদি তার assert স্টেটমেন্টগুলো দুর্বল হয় (বা না-ই থাকে), তাহলে সেই টেস্ট একটি বাস্তব, গুরুতর বাগও ধরতে ব্যর্থ হতে পারে।

নিচে একটি ইচ্ছাকৃতভাবে বাগযুক্ত calculate_cart_total() ফাংশন আছে — এটি প্রতিটি আইটেমের দাম (price) যোগ করে, কিন্তু ভুলবশত quantity (পরিমাণ) সম্পূর্ণ উপেক্ষা করে। L23-এর মতোই একটি sys.settrace()-ভিত্তিক ট্র্যাকার দিয়ে প্রথমে নিশ্চিত করা যাক এই বাগযুক্ত ফাংশনের প্রতিটি লাইন সত্যিই এক্সিকিউট হয় কি না।

Python
import sys

def calculate_cart_total(cart_items):
    # বাগ: quantity সম্পূর্ণ ইগনোর করা হয়েছে -- শুধু price যোগ হচ্ছে, price * quantity নয়
    total = 0
    for item in cart_items:
        total = total + item["price"]
    return total

def count_executable_lines(func):
    # func.__code__.co_lines() কম্পাইল-করা বাইটকোড থেকে সরাসরি এক্সিকিউটেবল লাইন-নাম্বার দেয় --
    # inspect.getsource()-এর মতো সোর্স ফাইল পড়ার দরকার নেই, তাই এই লেসন-স্যান্ডবক্সের exec()
    # দিয়ে চালানো কোডেও নির্ভুলভাবে কাজ করে।
    code = func.__code__
    line_numbers = set()
    for _start, _end, lineno in code.co_lines():
        if lineno is not None and lineno != code.co_firstlineno:
            line_numbers.add(lineno)
    return sorted(line_numbers)

def executed_line_set(func, *args, **kwargs):
    target_code = func.__code__
    executed = set()

    def tracer(frame, event, arg):
        if event == 'call':
            return tracer if frame.f_code is target_code else None
        if event == 'line' and frame.f_code is target_code:
            executed.add(frame.f_lineno)
        return tracer

    sys.settrace(tracer)
    try:
        result = func(*args, **kwargs)
    finally:
        sys.settrace(None)   # ট্রেসিং বন্ধ করা আবশ্যক
    return executed, result


cart_items = [{"price": 100, "quantity": 2}, {"price": 50, "quantity": 3}]
correct_expected_total = sum(item["price"] * item["quantity"] for item in cart_items)

executable_lines = count_executable_lines(calculate_cart_total)
executed, buggy_result = executed_line_set(calculate_cart_total, cart_items)
coverage_pct = len(executed) / len(executable_lines) * 100

print("executable লাইন:", executable_lines)
print("এক্সিকিউটেড লাইন:", sorted(executed))
print(f"স্টেটমেন্ট কভারেজ: {coverage_pct:.0f}%")
print(f"\ncalculate_cart_total() রিটার্ন করেছে: {buggy_result}")
print(f"সঠিক প্রত্যাশিত টোটাল হওয়ার কথা ছিল: {correct_expected_total}")

    
cart_items-এর দুটো আইটেমই লুপে প্রবেশ করে বলে ফাংশনের সবকটি এক্সিকিউটেবল লাইন (total = 0, for হেডার, যোগফল লাইন, return) অন্তত একবার চলে যায় — স্টেটমেন্ট কভারেজ genuinely ১০০%, sys.settrace() দিয়ে সত্যিই যাচাই করা। অথচ ফাংশনটি রিটার্ন করেছে ১৫০ (শুধু ১০০ + ৫০), যেখানে সঠিক টোটাল হওয়ার কথা ছিল ৩৫০ (১০০×২ + ৫০×৩)। লাইনটি (total = total + item["price"]) সত্যিই চলেছে — কিন্তু ভুল এক্সপ্রেশন এক্সিকিউট করেছে। কভারেজ ট্র্যাকার এই ভুল ধরতে পারে না — এটি শুধু "চলেছে কি না" দেখে, "সঠিক কি না" দেখে না।

২ · দুর্বল বনাম শক্তিশালী assertion: একই বাগ, দুই ভিন্ন ফলাফল

এখন এই একই বাগযুক্ত ফাংশনের উপর দুটো সত্যিকারের unittest.TestCase চালিয়ে দেখা যাক। প্রথমটির assertion দুর্বল — এটি শুধু চেক করে ফাংশনটি ক্র্যাশ করেনি এবং একটি নন-নেগেটিভ সংখ্যা রিটার্ন করেছে (অনেক বাস্তব প্রজেক্টেও এই ধরনের "শুধু crash না করা" টেস্ট ভুলবশত লেখা হয়)। দ্বিতীয়টির assertion শক্তিশালী — এটি প্রকৃত প্রত্যাশিত মানের সাথে সরাসরি মিলিয়ে দেখে।

Python
# (এই সেলটি আগের সেলে ডিফাইন করা calculate_cart_total(), cart_items, correct_expected_total
#  পুনরায় ব্যবহার করছে)
import unittest

class WeakCartTests(unittest.TestCase):
    # দুর্বল assertion: শুধু চেক করছে ফাংশনটি ক্র্যাশ করেনি এবং একটি নন-নেগেটিভ সংখ্যা রিটার্ন করেছে
    def test_returns_a_non_negative_number(self):
        result = calculate_cart_total(cart_items)
        self.assertIsInstance(result, (int, float))
        self.assertGreaterEqual(result, 0)

class StrongCartTests(unittest.TestCase):
    # শক্তিশালী assertion: প্রকৃত সঠিক প্রত্যাশিত মানের সাথে সরাসরি মিলিয়ে দেখছে
    def test_returns_the_correct_total(self):
        result = calculate_cart_total(cart_items)
        self.assertEqual(result, correct_expected_total)

print("=== দুর্বল assertion-এর টেস্ট ===")
weak_suite = unittest.TestLoader().loadTestsFromTestCase(WeakCartTests)
unittest.TextTestRunner(verbosity=2).run(weak_suite)

print("\n=== শক্তিশালী assertion-এর টেস্ট ===")
strong_suite = unittest.TestLoader().loadTestsFromTestCase(StrongCartTests)
unittest.TextTestRunner(verbosity=2).run(strong_suite)

    
লক্ষ্য করুন WeakCartTests পাস করে ("OK") — কারণ ১৫০ সত্যিই একটি নন-নেগেটিভ সংখ্যা, আর সেই একমাত্র জিনিসটিই এই টেস্ট যাচাই করে। কিন্তু StrongCartTests ফেইল করে — কারণ assertEqual(১৫০, ৩৫০) মিলছে না, এবং এই assertion-ই আসল বাগটি প্রকাশ করে দেয়। দুটো টেস্টই একই বাগযুক্ত ফাংশনের উপর, একই ইনপুট নিয়ে চালানো হয়েছে এবং দুটোই একই ১০০% স্টেটমেন্ট কভারেজ অর্জন করে (Cell ১ দেখুন) — একমাত্র পার্থক্য হলো assertion-এর মান, কভারেজ সংখ্যা নয়। এটিই M6-এর সবচেয়ে গুরুত্বপূর্ণ শিক্ষা।
মূল কথা · Key takeaway

কভারেজ মেট্রিক (স্টেটমেন্ট, ব্রাঞ্চ, কন্ডিশন, ডিসিশন, পাথ — M6-এর সবগুলো) শুধু বলে কোন কোড চলেছে, কখনোই বলে না সেই কোড সঠিক কাজ করেছে কি না। একটি ১০০%-কভারেজপ্রাপ্ত টেস্ট স্যুট তখনই আসলে দরকারী যখন তার প্রতিটি টেস্টের assertion সত্যিই ফলাফলের সঠিকতা যাচাই করে — শুধু "ক্র্যাশ করেনি" বা "কোনো একটা মান রিটার্ন করেছে" চেক করা যথেষ্ট নয়। L01-এর মূল কথাটির সাথেই এটি মিলে যায়: টেস্টিং বাগের উপস্থিতি দেখাতে পারে, কিন্তু শুধু তখনই যখন টেস্ট আসলে সঠিক জিনিসটা যাচাই করার চেষ্টা করে — নাহলে কভারেজ যত বেশিই হোক, একটি মিথ্যা আত্মবিশ্বাস তৈরি হয়। M10-এ "মিউটেশন টেস্টিং" শেখানো হবে — একটি টেকনিক যা ঠিক এই ধরনের দুর্বল টেস্ট স্যুট নিয়মতান্ত্রিকভাবে খুঁজে বের করার জন্য ডিজাইন করা।

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

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

প্র ০১ দুটো টেস্টই (WeakCartTests, StrongCartTests) একই বাগযুক্ত ফাংশন, একই ইনপুট, একই ১০০% কভারেজ — তবু একটা পাস করে আরেকটা ফেইল করে কেন?

কারণ পাস/ফেইল নির্ভর করে assert স্টেটমেন্ট কী যাচাই করছে তার উপর, কোড কতটুকু "চলেছে" তার উপর নয়। WeakCartTests শুধু যাচাই করে ফলাফলটি একটি নন-নেগেটিভ সংখ্যা কি না — বাগযুক্ত ১৫০-ও এই শর্ত পূরণ করে, তাই পাস করে। StrongCartTests যাচাই করে ফলাফলটি ঠিক ৩৫০ কি না — ১৫০ ≠ ৩৫০, তাই ফেইল করে। কভারেজ দুটোতেই সমান, কিন্তু assertion-এর "কঠোরতা" ভিন্ন — এটিই আসল পার্থক্য তৈরি করেছে।

প্র ০২ একজন নতুন ডেভেলপার বলছেন, "আমাদের মডিউলে ১০০% টেস্ট কভারেজ আছে, তাই এটি বাগ-মুক্ত" — এই দাবিতে কী ভুল আছে?

এই দাবিটি ভুল কারণ কভারেজ শুধু বলে কোড এক্সিকিউট হয়েছে কি না, ফলাফল সঠিক কি না তা নয়। উপরের ডেমোতে দেখানো হয়েছে ১০০% কভারেজ থাকা সত্ত্বেও একটি বাস্তব, গুরুতর বাগ (quantity ইগনোর করা) সম্পূর্ণ অলক্ষিত থেকে যেতে পারে যদি assertion দুর্বল হয়। "১০০% কভারেজ" আর "বাগ-মুক্ত" — এই দুটো দাবি সম্পূর্ণ ভিন্ন, এবং দ্বিতীয়টি প্রথমটি থেকে কখনোই স্বয়ংক্রিয়ভাবে অনুসরণ করে না।

প্র ০৩ correct_expected_total কীভাবে গণনা করা হলো, এবং কেন সেই গণনাটি নিজেই বাগযুক্ত ফাংশনের লজিক থেকে সম্পূর্ণ স্বতন্ত্র হওয়া জরুরি?

correct_expected_total গণনা করা হয়েছে sum(item["price"] * item["quantity"] for item in cart_items) দিয়ে — অর্থাৎ price এবং quantity দুটোই বিবেচনা করে, ঠিক যেভাবে স্পেসিফিকেশন অনুযায়ী হওয়ার কথা। এটি বাগযুক্ত ফাংশনের নিজস্ব লজিক (যা quantity ইগনোর করে) থেকে সম্পূর্ণ আলাদাভাবে গণনা করা হয়েছে — যদি "সঠিক উত্তর" নিজেই বাগযুক্ত ফাংশনের একই ভুল লজিক কপি করে গণনা করা হতো, তাহলে টেস্টটি নিজের ভুলকেই "সঠিক" ধরে নিত এবং বাগটি কখনো ধরা পড়ত না — একটি সাধারণ, বিপজ্জনক টেস্টিং ভুল।

অনুশীলন

  1. চিন্তা করুন: WeakCartTests-এর মতো একটি টেস্ট (শুধু নন-নেগেটিভ কি না চেক করে) বাস্তব প্রজেক্টে কখন লেখা হতে পারে, এবং কীভাবে এটি এড়ানো যায়?

    প্রায়ই সময়ের চাপে বা "শুধু কভারেজ সংখ্যা বাড়ানোর" লক্ষ্যে এই ধরনের দুর্বল টেস্ট লেখা হয় — ডেভেলপার জানেন ফাংশনটি কল হবে (কভারেজ বাড়বে) কিন্তু সঠিক প্রত্যাশিত মান আগে থেকে হিসাব করে বসিয়ে দিতে অলসতা বা সময়ের অভাব হয়। এড়ানোর উপায়: টেস্ট লেখার সময় সবসময় নিজেকে প্রশ্ন করুন — "এই assertion যদি পাস করে, তাহলে কি আমি নিশ্চিত থাকতে পারি ফাংশনটি সঠিক কাজ করেছে, নাকি শুধু কিছু একটা করেছে?" — কোড রিভিউতেও এই প্রশ্নটি একটি ভালো চেকলিস্ট আইটেম।

  2. পরীক্ষা করুন: দ্বিতীয় কোড সেলে calculate_cart_total-এর বাগটি ঠিক করে দিন (total = total + item["price"]-কে total = total + item["price"] * item["quantity"] করুন) এবং Run চাপুন — দুটো টেস্টই কী ফলাফল দেয়?

    বাগ ঠিক করার পর calculate_cart_total(cart_items) সঠিকভাবে ৩৫০ রিটার্ন করবে। এখন WeakCartTests আগের মতোই পাস করবে (৩৫০ও একটি নন-নেগেটিভ সংখ্যা), আর StrongCartTests-ও এখন পাস করবে (assertEqual(৩৫০, ৩৫০) মিলে যায়)। এটি দেখায় — একবার কোড সঠিক হয়ে গেলে দুর্বল ও শক্তিশালী উভয় assertion-ই পাস করে; পার্থক্যটা শুধু তখনই ধরা পড়ে যখন কোডে সত্যিই একটি বাগ থাকে — আর তখনই শক্তিশালী assertion-এর আসল মূল্য বোঝা যায়।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ টেস্ট ডিজাইন টেকনিক, ইউনিট টেস্টিং, টেস্ট ডাবলস, ইন্টিগ্রেশন টেস্টিং, কভারেজ, অটোমেশন, পারফরম্যান্স ও সিকিউরিটি টেস্টিং, অ্যাডভান্সড টেকনিক ও CI/CD ইন্টিগ্রেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • মিউটেশন টেস্টিং L42 আজকের পাঠের "দুর্বল assertion বাগ মিস করে" সমস্যাটি নিয়মতান্ত্রিকভাবে খুঁজে বের করার একটি টেকনিক — ইচ্ছাকৃতভাবে কোডে ছোট বাগ ঢুকিয়ে দেখা হয় বিদ্যমান টেস্ট স্যুট সেটি ধরতে পারে কি না।
  • সব 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 — সব এক জায়গায়।
আগের পাঠ
কন্ডিশন ও ডিসিশন কভারেজ