কভারেজের সীমাবদ্ধতা — ১০০% কভারেজ মানে ১০০% সঠিকতা নয়
এই পাঠে যা শিখবেন
- কেন ১০০% স্টেটমেন্ট/ব্রাঞ্চ কভারেজ কোডের সঠিকতার প্রমাণ নয়
- "লাইন চলা" বনাম "ফলাফল যাচাই করা"-এর মধ্যেকার সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ পার্থক্য
- একটি genuine উদাহরণে দেখা কীভাবে দুর্বল assertion একটি বাস্তব বাগ মিস করে যায়
- কেন assertion-এর মান (কী যাচাই করা হচ্ছে) কভারেজ সংখ্যার চেয়ে বেশি গুরুত্বপূর্ণ
১ · একটি লাইন চলা, আর তার ফলাফল সঠিক হওয়া — এক জিনিস নয়
M6-এর আগের তিনটি পাঠে (L23-L25) আমরা স্টেটমেন্ট, ব্রাঞ্চ, কন্ডিশন ও ডিসিশন কভারেজ কীভাবে গণনা করা যায় তা
শিখেছি। কিন্তু এই সবকটি মেট্রিকের একটি সাধারণ, গুরুত্বপূর্ণ সীমাবদ্ধতা আছে — এরা সবাই শুধু প্রশ্ন করে
"কোড এক্সিকিউট হয়েছে কি না", কখনো প্রশ্ন করে না "এক্সিকিউট হওয়া কোডের ফলাফল সঠিক
কি না"। একটি টেস্ট ১০০% কভারেজ পেতে পারে অথচ যদি তার assert স্টেটমেন্টগুলো দুর্বল হয়
(বা না-ই থাকে), তাহলে সেই টেস্ট একটি বাস্তব, গুরুতর বাগও ধরতে ব্যর্থ হতে পারে।
নিচে একটি ইচ্ছাকৃতভাবে বাগযুক্ত calculate_cart_total() ফাংশন আছে — এটি প্রতিটি আইটেমের দাম
(price) যোগ করে, কিন্তু ভুলবশত quantity (পরিমাণ) সম্পূর্ণ উপেক্ষা করে। L23-এর
মতোই একটি sys.settrace()-ভিত্তিক ট্র্যাকার দিয়ে প্রথমে নিশ্চিত করা যাক এই বাগযুক্ত ফাংশনের
প্রতিটি লাইন সত্যিই এক্সিকিউট হয় কি না।
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 শক্তিশালী — এটি
প্রকৃত প্রত্যাশিত মানের সাথে সরাসরি মিলিয়ে দেখে।
# (এই সেলটি আগের সেলে ডিফাইন করা 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-এর সবচেয়ে গুরুত্বপূর্ণ শিক্ষা।
কভারেজ মেট্রিক (স্টেটমেন্ট, ব্রাঞ্চ, কন্ডিশন, ডিসিশন, পাথ — 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 ইগনোর করে) থেকে সম্পূর্ণ আলাদাভাবে গণনা করা হয়েছে — যদি "সঠিক উত্তর" নিজেই বাগযুক্ত
ফাংশনের একই ভুল লজিক কপি করে গণনা করা হতো, তাহলে টেস্টটি নিজের ভুলকেই "সঠিক" ধরে নিত এবং বাগটি কখনো ধরা
পড়ত না — একটি সাধারণ, বিপজ্জনক টেস্টিং ভুল।
অনুশীলন
-
চিন্তা করুন:
WeakCartTests-এর মতো একটি টেস্ট (শুধু নন-নেগেটিভ কি না চেক করে) বাস্তব প্রজেক্টে কখন লেখা হতে পারে, এবং কীভাবে এটি এড়ানো যায়?প্রায়ই সময়ের চাপে বা "শুধু কভারেজ সংখ্যা বাড়ানোর" লক্ষ্যে এই ধরনের দুর্বল টেস্ট লেখা হয় — ডেভেলপার জানেন ফাংশনটি কল হবে (কভারেজ বাড়বে) কিন্তু সঠিক প্রত্যাশিত মান আগে থেকে হিসাব করে বসিয়ে দিতে অলসতা বা সময়ের অভাব হয়। এড়ানোর উপায়: টেস্ট লেখার সময় সবসময় নিজেকে প্রশ্ন করুন — "এই assertion যদি পাস করে, তাহলে কি আমি নিশ্চিত থাকতে পারি ফাংশনটি সঠিক কাজ করেছে, নাকি শুধু কিছু একটা করেছে?" — কোড রিভিউতেও এই প্রশ্নটি একটি ভালো চেকলিস্ট আইটেম।
-
পরীক্ষা করুন: দ্বিতীয় কোড সেলে
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 — সব এক জায়গায়।