পাঠ ২৮ · ৫৭-এর মধ্যে · মডিউল ৬
Home / Courses / Ethics in Computing & AI Safety / টেস্টিং ও ভেরিফিকেশন

টেস্টিং, ভেরিফিকেশন ও নিশ্চয়তার সীমাবদ্ধতা

Testing, verification and the limits of assurance
১০ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

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

১ · টেস্টিং শুধু উপস্থিতি প্রমাণ করে, অনুপস্থিতি নয়

সফটওয়্যার ইঞ্জিনিয়ারিংয়ে একটি সুপরিচিত, ব্যাপকভাবে উদ্ধৃত পর্যবেক্ষণ আছে, যা এডসগার ডাইকস্ট্রার সাথে ব্যাপকভাবে সম্পর্কিত:

"testing shows the presence, not the absence of bugs"

এর অর্থ হলো -- একটি টেস্ট স্যুট পাস করা মানে শুধু এইটুকু বোঝায় যে আপনি যে নির্দিষ্ট ইনপুট/পরিস্থিতিগুলো পরীক্ষা করার কথা ভেবেছিলেন, সেগুলোতে প্রোগ্রামটি সঠিকভাবে কাজ করেছে। এটি কখনো এই দাবি করতে পারে না যে অন্য, আপনি ভাবেননি এমন কোনো ইনপুট বা পরিস্থিতিতে প্রোগ্রামটি ব্যর্থ হবে না -- সম্ভাব্য ইনপুটের জগৎ সাধারণত এত বিশাল যে সম্পূর্ণভাবে পরীক্ষা করা বাস্তবে অসম্ভব। Therac-25 (L25)-এর রেস কন্ডিশন এর একটি বাস্তব উদাহরণ -- স্বাভাবিক গতিতে করা টেস্ট পাস করেছিল, কিন্তু একটি নির্দিষ্ট, বিরল টাইমিং-ক্রম কখনো যথেষ্ট পরিমাণে পরীক্ষা করা হয়নি।

২ · ফরমাল ভেরিফিকেশন: প্রতিশ্রুতি ও সীমাবদ্ধতা

ফরমাল ভেরিফিকেশনFormal Verificationগাণিতিক পদ্ধতি ব্যবহার করে প্রমাণ করা যে একটি প্রোগ্রাম একটি নির্দিষ্ট, আনুষ্ঠানিকভাবে লেখা স্পেসিফিকেশনের সবগুলো শর্ত সবসময় মেনে চলে। টেস্টিংয়ের একটি শক্তিশালী বিকল্প/পরিপূরক প্রস্তাব করে -- নমুনা ইনপুট পরীক্ষা করার বদলে, এটি গাণিতিকভাবে প্রমাণ করে যে একটি প্রোগ্রাম সব সম্ভাব্য ইনপুটের জন্য একটি নির্দিষ্ট বৈশিষ্ট্য মেনে চলে। এটি টেস্টিংয়ের "শুধু যা পরীক্ষা করা হয়েছে তার প্রমাণ" সীমাবদ্ধতা কাটিয়ে ওঠে।

কিন্তু ফরমাল ভেরিফিকেশনেরও একটি মৌলিক সীমাবদ্ধতা আছে -- এটি প্রমাণ করে যে প্রোগ্রামটি তার স্পেসিফিকেশন মেনে চলে, কিন্তু সেই স্পেসিফিকেশনটি নিজেই ভুল, অসম্পূর্ণ, বা বাস্তব-জগতের প্রকৃত প্রয়োজনীয়তা সঠিকভাবে ধারণ না করলে, প্রোগ্রামটি "প্রমাণিতভাবে সঠিক" হওয়া সত্ত্বেও বাস্তবে ভুল আচরণ করতে পারে। অর্থাৎ, "আমরা কী প্রমাণ করছি" এই প্রশ্নটি "আমরা কীভাবে প্রমাণ করছি" এই প্রশ্নের মতোই গুরুত্বপূর্ণ। এছাড়া, জটিল সিস্টেমের জন্য সম্পূর্ণ ফরমাল ভেরিফিকেশন বাস্তবে ব্যয়বহুল ও সময়সাপেক্ষ, তাই এটি সাধারণত সবচেয়ে ঝুঁকিপূর্ণ, কোর অংশগুলোর জন্য সংরক্ষিত থাকে।

টেস্টিং শুধু চিন্তা-করা-হয়েছে এমন কেস কভার করে ফরমাল ভেরিফিকেশন শুধু স্পেসিফিকেশনের সাপেক্ষে বৈধ সীমা: অ-পরীক্ষিত ইনপুট = অজানা সীমা: ভুল স্পেসিফিকেশন = ভুল প্রমাণ
দুটি কৌশলই মূল্যবান, কিন্তু কোনোটিই একা "সম্পূর্ণ নিশ্চয়তা" দেয় না -- বাস্তব সেফটি-ক্রিটিক্যাল প্র্যাকটিসে সাধারণত উভয় কৌশল, বিভিন্ন হার্ডওয়্যার/প্রক্রিয়াগত সুরক্ষার সাথে মিলিয়ে ব্যবহৃত হয়।
সহোদর কোর্সের টেস্টিং-পিরামিড পাঠের সাথে সম্পর্ক

Full-Stack Web Frameworks ও Mobile App Development কোর্সে ইউনিট/ইন্টিগ্রেশন/এন্ড-টু-এন্ড টেস্টের ধরন ও টেস্টিং পিরামিড বিস্তারিতভাবে কভার করা হয়েছে বলে ধরে নেওয়া হচ্ছে -- এই পাঠ সেই টেস্ট-টাইপ শ্রেণিবিন্যাস পুনরাবৃত্তি করবে না, বরং টেস্টিং ধারণাটির দার্শনিক সীমাবদ্ধতা নিয়ে আলোচনা করছে।

৩ · কোড কভারেজ একা যথেষ্ট নয় -- একটি ইলাস্ট্রেটিভ মডেল

একটি সাধারণ ভুল ধারণা হলো "৯০% কোড কভারেজ মানে ৯০% বাগ-মুক্ত আত্মবিশ্বাস"। বাস্তবে কভারেজ শুধু বলে কতটুকু কোড এক্সিকিউট হয়েছে টেস্টের সময়, তা নয় যে সেই এক্সিকিউশনগুলো প্রকৃত ঝুঁকিপূর্ণ পরিস্থিতি যথেষ্টভাবে পরীক্ষা করেছে কি না। নিচের কোড সেলে একই কভারেজ শতাংশের দুটি দৃশ্যকল্প তুলনা করা হয়েছে, যেখানে না-টেস্ট-করা অংশের প্রকৃত ঝুঁকি সম্পূর্ণ ভিন্ন।

Python
# ইলাস্ট্রেটিভ মডেল -- দেখানোর জন্য যে কোড-কভারেজ শতাংশ একাই "কতটা নিশ্চিত থাকা যায়" তার
# সম্পূর্ণ পরিমাপ নয়। সংখ্যাগুলো একটি ছোট, স্পষ্টভাবে চিহ্নিত সিন্থেটিক উদাহরণ।

def coverage_percent(covered, total):
    return covered / total * 100

# দৃশ্যকল্প ক: বড় কোডবেস, ৯০% কভারেজ; বাদ পড়া ২০টি শাখার মধ্যে মাত্র ২টি সত্যিকারের
# ঝুঁকিপূর্ণ এজ-কেস (বাকিগুলো সহজ, কম-ঝুঁকিপূর্ণ কোড পাথ)
scenario_a = {"total_branches": 200, "covered_branches": 180, "untested_risky_edge_cases": 2}

# দৃশ্যকল্প খ: ছোট কোডবেস, একই ৯০% কভারেজ; কিন্তু বাদ পড়া ৪টি শাখার সবগুলোই ঝুঁকিপূর্ণ
# এজ-কেস (যেমন বিরল এরর-হ্যান্ডলিং পাথ)
scenario_b = {"total_branches": 40, "covered_branches": 36, "untested_risky_edge_cases": 4}

for label, s in [("দৃশ্যকল্প ক", scenario_a), ("দৃশ্যকল্প খ", scenario_b)]:
    cov = coverage_percent(s["covered_branches"], s["total_branches"])
    untested_total = s["total_branches"] - s["covered_branches"]
    risky = s["untested_risky_edge_cases"]
    risky_share = (risky / untested_total * 100) if untested_total else 0
    print(f"{label}: কভারেজ={cov:.1f}%, মোট না-টেস্ট-করা শাখা={untested_total}, "
          f"তার মধ্যে ঝুঁকিপূর্ণ এজ-কেস={risky} ({risky_share:.0f}%)")

print("\nদুটো দৃশ্যকল্পেরই কভারেজ প্রায় সমান (~৯০%), কিন্তু না-টেস্ট-করা অংশের প্রকৃত ঝুঁকি")
print("সম্পূর্ণ ভিন্ন -- তাই কভারেজ শতাংশ একাই আত্মবিশ্বাসের একটি অসম্পূর্ণ পরিমাপ।")

    
লক্ষ্য করুন দৃশ্যকল্প ক-তে (কভারেজ ৯০.০%) না-টেস্ট-করা ২০টি শাখার মধ্যে মাত্র ১০% (২টি) ঝুঁকিপূর্ণ, কিন্তু দৃশ্যকল্প খ-তে (একই ৯০.০% কভারেজ) না-টেস্ট-করা ৪টি শাখার ১০০%-ই ঝুঁকিপূর্ণ। একই শতাংশ সংখ্যা সম্পূর্ণ ভিন্ন মাত্রার বাস্তব ঝুঁকি আড়াল করতে পারে -- এই মডেলটি একটি ইলাস্ট্রেশন, বাস্তব কোনো কোডবেসের পরিমাপ নয়, কিন্তু এর পেছনের যুক্তিটি বাস্তব: কভারেজ পরিমাপ করে "কতটুকু চালানো হয়েছে", "কতটা ঝুঁকিপূর্ণ পরিস্থিতি সত্যিকারভাবে পরীক্ষা করা হয়েছে" তা নয়।
মূল কথা · Key takeaway

কোনো একক নিশ্চয়তা-কৌশল -- টেস্টিং, কোড কভারেজ, বা ফরমাল ভেরিফিকেশন -- একাই "সম্পূর্ণ নিরাপদ" গ্যারান্টি দিতে পারে না। টেস্টিং শুধু চিন্তা-করা-হয়েছে এমন কেস কভার করে, ফরমাল ভেরিফিকেশন শুধু স্পেসিফিকেশনের সাপেক্ষে বৈধ, এবং কভারেজ শতাংশ শুধু এক্সিকিউশন পরিমাপ করে, ঝুঁকি নয়। সেফটি-ক্রিটিক্যাল সিস্টেমে (L24-L26) তাই একাধিক, স্বতন্ত্র নিশ্চয়তা-স্তর -- টেস্টিং, রিভিউ, ফরমাল যাচাই যেখানে সম্ভব, এবং হার্ডওয়্যার রিডানডেন্সি -- একসাথে ব্যবহার করা প্রয়োজন।

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

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

প্র ০১ "টেস্টিং শুধু বাগের উপস্থিতি প্রমাণ করে, অনুপস্থিতি নয়" -- এর ব্যবহারিক অর্থ কী?

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

প্র ০২ ফরমাল ভেরিফিকেশন "প্রমাণিতভাবে সঠিক" একটি প্রোগ্রাম কীভাবে তবুও বাস্তবে ভুল আচরণ করতে পারে?

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

প্র ০৩ উপরের কোড সেলে যদি দৃশ্যকল্প খ-এর covered_branches বাড়িয়ে 39 করা হয় (কভারেজ প্রায় ৯৭.৫%), কিন্তু untested_risky_edge_cases এখনো 1 থাকে (একটি বাকি এজ-কেসও ঝুঁকিপূর্ণ), তাহলে এটি কী প্রমাণ করে?

কভারেজ ৯৭.৫%-এ বাড়লেও, বাকি থাকা একটি মাত্র শাখাই যদি ঝুঁকিপূর্ণ এজ-কেস হয়, তাহলে ঝুঁকি সম্পূর্ণ দূর হয়নি -- এটি প্রমাণ করে যে কভারেজ ১০০%-এর কাছাকাছি পৌঁছানোও নিরাপত্তার গ্যারান্টি দেয় না যদি বাকি থাকা অংশটুকুই ঠিক সবচেয়ে গুরুত্বপূর্ণ অংশ হয়। এমনকি ৯৯% কভারেজেও, বাকি ১% যদি সবচেয়ে বিপজ্জনক কোড পাথ হয়, তাহলে সামগ্রিক ঝুঁকি এখনো উচ্চ থাকতে পারে।

অনুশীলন

  1. চিন্তা করুন: "আমাদের ১০০% কোড কভারেজ আছে, তাই আমাদের সফটওয়্যার নিরাপদ" -- এই দাবিতে কোন কোন সমস্যা লুকিয়ে থাকতে পারে, এই পাঠের আলোচনার ভিত্তিতে?

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

  2. পরীক্ষা করুন: উপরের কোড সেলে একটি তৃতীয় দৃশ্যকল্প scenario_c লিখুন যেখানে কভারেজ কম (যেমন ৭০%) কিন্তু untested_risky_edge_cases শূন্য -- এবং প্রিন্ট লুপে যোগ করে দেখুন এটি কী বার্তা দেয়।

    এই দৃশ্যকল্পটি বিপরীত পয়েন্ট দেখাবে -- কম কভারেজ (৭০%) থাকা সত্ত্বেও যদি বাদ পড়া অংশগুলোর কোনোটিই সত্যিকারের ঝুঁকিপূর্ণ এজ-কেস না হয় (হয়তো সেগুলো ডেড কোড বা অত্যন্ত সাধারণ, নিম্ন-ঝুঁকির পাথ), তাহলে সেই কম কভারেজও উচ্চ-কভারেজ কিন্তু ঝুঁকিপূর্ণ-এজ-কেস-বিহীন দৃশ্যকল্পের চেয়ে খারাপ নাও হতে পারে। এটি আরও জোরালোভাবে প্রমাণ করে যে সংখ্যাটি একা যথেষ্ট নয় -- কোন অংশ বাদ পড়েছে তা বিবেচনা করাটাই আসল কথা।

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

আগের পাঠ
অ্যাকাউন্টেবিলিটি ও "প্রবলেম অফ মেনি হ্যান্ডস"