পাঠ ৪৬ · ৫৮-এর মধ্যে · মডিউল ১০
Home / Courses / Software Engineering Principles & Git / ইউনিট টেস্ট ও কভারেজ

ভালো ইউনিট টেস্ট লেখা ও টেস্ট কভারেজ

Writing good unit tests & test coverage
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • একটি ভালো ইউনিট টেস্টের FIRST নীতি এবং প্রতিটির ব্যবহারিক গুরুত্ব
  • Arrange-Act-Assert কাঠামো এবং এটি কেন টেস্ট পঠনযোগ্যতা বাড়ায়
  • টেস্ট কভারেজের সঠিক গণনা-সূত্র
  • একটি বাস্তব উদাহরণে দেখা — কীভাবে একটি "ফাঁপা" টেস্ট আর একটি প্রকৃত assertion-সহ টেস্ট একই কভারেজ শতাংশ দেখাতে পারে

১ · FIRST — একটি ভালো ইউনিট টেস্টের পাঁচটি গুণ

একটি ইউনিট টেস্ট শুধু "কাজ করলেই" যথেষ্ট নয় — বাস্তবে দলগুলো একটি ভালো ইউনিট টেস্টের গুণাবলি সংক্ষেপে বলার জন্য FIRST মনেমনিক ব্যবহার করে:

Fast
মিলিসেকেন্ডে চলে, যাতে ডেভেলপমেন্টের সময় বারবার চালানো যায় কোনো ঘর্ষণ ছাড়াই — M10/L44-এর "উল্টানো পিরামিড" সমস্যার সরাসরি বিপরীত গুণ।
Independent / Isolated
অন্য কোনো টেস্টের এক্সিকিউশন বা ক্রমের উপর নির্ভর করে না — M10/L44-এর বিচ্ছিন্নতার থিমের সরাসরি পুনরাবৃত্তি।
Repeatable
প্রতিবার, যেকোনো পরিবেশে একই ফলাফল দেয় — সরাসরি এই কোর্সের CLAUDE.md-এর "unseeded randomness নয়" নিয়মের সমান্তরাল: real random বা real current-time ব্যবহারকারী টেস্ট এই নীতি ভঙ্গ করে।
Self-validating
টেস্ট নিজেই পাস/ফেল স্পষ্টভাবে রিপোর্ট করে (assertion-এর মাধ্যমে) — আউটপুট ম্যানুয়ালি চোখে দেখে যাচাই করার প্রয়োজন নেই।
Timely
কোডের প্রায় একই সময়ে লেখা — L45-এর TDD দর্শনের সরাসরি পুনরাবৃত্তি, টেস্ট পরে যোগ করা "যদি সময় থাকে" এমন কিছু নয়।

২ · Arrange-Act-Assert (AAA)

একটি ভালো ইউনিট টেস্টের গঠনও গুরুত্বপূর্ণ। প্রমিত Arrange-Act-Assert (AAA) কনভেনশন প্রতিটি টেস্টকে তিনটি স্পষ্ট অংশে ভাগ করে:

Arrange ইনপুট/পূর্বশর্ত সেটআপ Act প্রকৃত ফাংশন কল করুন Assert ফলাফল প্রত্যাশার সাথে মেলান
তিনটি অংশ স্পষ্টভাবে আলাদা রাখা (এমনকি শুধু কমেন্ট/ফাঁকা লাইন দিয়েও) টেস্টকে অনেক বেশি সহজে পড়া ও রক্ষণাবেক্ষণযোগ্য করে তোলে।

৩ · টেস্ট কভারেজ — সূত্র ও একটি গুরুত্বপূর্ণ সতর্কতা

টেস্ট কভারেজTest Coverageটেস্ট চালানোর সময় প্রোগ্রামের কতটুকু কোড আসলে এক্সিকিউট হয়েছে, তার একটি শতাংশ পরিমাপ। একটি প্রমিত মেট্রিক যা পরিমাপ করে কোডের কতটুকু অংশ টেস্ট চালানোর সময় আসলে এক্সিকিউট হয়েছে:

$$Coverage\% = \frac{\text{এক্সিকিউট হওয়া লাইন}}{\text{মোট এক্সিকিউটযোগ্য লাইন}} \times 100$$

গুরুত্বপূর্ণ, প্রায়ই ভুল বোঝা সতর্কতা

উচ্চ কভারেজ শতাংশ ভালো টেস্টের নিশ্চয়তা দেয় না — একটি টেস্ট একটি লাইন এক্সিকিউট করতে পারে কোনো অর্থবহ কিছু যাচাই না করেই। কভারেজ টুল শুধু "কী চালানো হয়েছিল" পরিমাপ করে, "কী যাচাই করা হয়েছিল" নয় — এটি একটি বাস্তব, সাধারণ মিথ্যা-নিরাপত্তাবোধের উৎস।

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

Python
# টেস্ট কভারেজ গণনা, এবং কেন উচ্চ কভারেজ "ভালো" টেস্টের নিশ্চয়তা দেয় না -- একটি বাস্তব উদাহরণ।

def calculate_coverage(total_lines, executed_lines_set):
    return (len(executed_lines_set) / total_lines) * 100

def apply_discount(price, is_member):
    # লাইন ১
    if is_member:
        # লাইন ২
        discount = price * 0.10
    else:
        # লাইন ৩
        discount = 0
    # লাইন ৪
    return price - discount

TOTAL_LINES = 4

# AAA-স্টাইল ভালো টেস্ট -- অর্থবহ assertion সহ
def test_apply_discount_for_member():
    # Arrange
    price = 200
    is_member = True
    # Act
    result = apply_discount(price, is_member)
    # Assert
    assert result == 180

test_apply_discount_for_member()
executed_by_good_test = {1, 2, 4}
good_coverage = calculate_coverage(TOTAL_LINES, executed_by_good_test)
print(f"ভালো টেস্ট -- coverage: {good_coverage:.1f}% (assertion সহ, প্রকৃত আচরণ যাচাই করে)")

# ফাঁপা টেস্ট -- লাইন এক্সিকিউট করে কিন্তু কিছুই যাচাই করে না
def test_apply_discount_no_real_assertion():
    result = apply_discount(200, True)
    assert True  # ফাঁপা assertion -- ফলাফলের সাথে কোনো সম্পর্ক নেই

test_apply_discount_no_real_assertion()
executed_by_hollow_test = {1, 2, 4}
hollow_coverage = calculate_coverage(TOTAL_LINES, executed_by_hollow_test)
print(f"ফাঁপা টেস্ট  -- coverage: {hollow_coverage:.1f}% (একই লাইন এক্সিকিউট, কিন্তু কিছুই যাচাই হয়নি)")

print("\nদুটো টেস্টেরই coverage টুল অনুযায়ী রিপোর্ট একই -- কিন্তু শুধু প্রথমটি প্রকৃতপক্ষে সঠিকতা যাচাই করে।")

    
লক্ষ্য করুন উভয় টেস্টই apply_discount(200, True) কল করে একই চারটি লাইন এক্সিকিউট করেছে, তাই calculate_coverage উভয়ের জন্যই ৭৫.০% রিপোর্ট করে (৪ লাইনের মধ্যে ৩টি — শুধু else শাখাটি অচেষ্টিত রয়ে গেছে)। কিন্তু test_apply_discount_no_real_assertion-এর assert True কখনোই ব্যর্থ হতে পারে না, ফলাফল যাই হোক না কেন — এটি ইমপ্লিমেন্টেশনে যেকোনো বাগ থাকলেও ধরবে না, অথচ কভারেজ রিপোর্টে এটি সম্পূর্ণ "স্বাস্থ্যকর" দেখাবে।
মূল কথা · Key takeaway

FIRST নীতি ও AAA কাঠামো একটি ইউনিট টেস্টকে দ্রুত, নির্ভরযোগ্য ও পঠনযোগ্য রাখে; টেস্ট কভারেজ একটি উপযোগী কিন্তু সীমাবদ্ধ মেট্রিক — এটি বলে দেয় কী চালানো হয়েছিল, কিন্তু কী প্রকৃতপক্ষে যাচাই করা হয়েছিল তা নয়। উচ্চ কভারেজ শতাংশকে কখনোই "ভালো টেস্টিং"-এর একমাত্র প্রমাণ হিসেবে ধরা উচিত নয় — প্রতিটি assertion আসলেই অর্থবহ কিনা, তা মানুষকেই যাচাই করতে হয়।

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

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

প্র ০১ একটি টেস্ট যদি বর্তমান তারিখ/সময় (datetime.now()) ব্যবহার করে ফলাফল যাচাই করে, তাহলে FIRST-এর কোন নীতিটি ভাঙে, এবং কেন?

এটি Repeatable নীতি ভাঙে — টেস্টটি চালানোর সময়ের উপর নির্ভরশীল হয়ে যায়, ফলে আজ পাস করলেও আগামীকাল, বা এমনকি কয়েক মিলিসেকেন্ড পরে, ভিন্ন ফলাফল দিতে পারে (বিশেষ করে সময়-সংবেদনশীল লজিকে, যেমন "যদি আজ সোমবার হয়")। এটিই ঠিক এই কোর্সের CLAUDE.md-এর "no unseeded randomness" নিয়মের পেছনের যুক্তি — একটি টেস্ট যেকোনো পরিবেশে, যেকোনো সময়ে একই ফলাফল দেওয়া উচিত, নাহলে এটি নির্ভরযোগ্য নয়।

প্র ০২ উপরের কোড সেলে apply_discount-এর else শাখা (লাইন ৩) কোনো টেস্টেই এক্সিকিউট হয়নি। এটি বাস্তবে কী ঝুঁকি তৈরি করে?

এর মানে is_member=False কেসের জন্য কোনো টেস্টই কখনো যাচাই করেনি ফাংশনটি সঠিকভাবে কাজ করে কিনা — যদি সেই শাখায় একটি বাগ থাকে (যেমন ভুলবশত discount = price * 0.10 লেখা হয়ে যায় else-এও), তা কখনো ধরা পড়বে না, ৭৫% কভারেজ থাকা সত্ত্বেও। এটি ঠিক দেখায় কেন ৭৫% কভারেজ মানে "৭৫% নিশ্চিত সঠিক" নয় — বাকি ২৫% (এখানে else শাখা) সম্পূর্ণ অপরীক্ষিত থেকে যায়।

প্র ০৩ Arrange-Act-Assert কাঠামোয় "Act" ধাপে সাধারণত ঠিক কতগুলো ফাংশন/মেথড কল করা উচিত, এবং কেন সেটি একটির বেশি না হওয়াই ভালো?

সাধারণত একটি মাত্র — যে জিনিসটি আসলে টেস্ট করা হচ্ছে ঠিক সেই একটি কল। যদি "Act" ধাপে একাধিক ফাংশন কল করা হয়, তাহলে টেস্টটি ব্যর্থ হলে বোঝা কঠিন হয়ে যায় ঠিক কোন কলটি সমস্যার উৎস — এটি Fast ও Self-validating নীতির সাথেও যুক্ত: একটি স্পষ্ট, একক-উদ্দেশ্যের টেস্ট ব্যর্থতার কারণ তাৎক্ষণিকভাবে স্পষ্ট করে, একাধিক কলের একটি জট নয়।

অনুশীলন

  1. চিন্তা করুন: একটি টিম গর্বের সাথে ঘোষণা করে তাদের কোডবেসের টেস্ট কভারেজ ৯৮%। এই একটি সংখ্যা থেকে আপনি তাদের টেস্ট স্যুটের মান সম্পর্কে ঠিক কী জানতে পারেন, আর কী জানতে পারেন না?

    আপনি জানতে পারেন কোডবেসের প্রায় সব লাইনই অন্তত একবার কোনো টেস্ট চালানোর সময় এক্সিকিউট হয়েছে। কিন্তু আপনি জানতে পারেন না সেই টেস্টগুলো প্রকৃতপক্ষে অর্থবহ assertion করে কিনা, এজ কেস (যেমন খালি ইনপুট, নেগেটিভ সংখ্যা) কভার করে কিনা, বা টেস্টগুলো FIRST নীতি মেনে চলে কিনা। ৯৮% কভারেজ একটি ভালো সংকেত হতে পারে, কিন্তু এটি একা কখনোই টেস্ট স্যুটের প্রকৃত মানের প্রমাণ নয় — এই পাঠের কোড সেলের ফাঁপা-টেস্ট উদাহরণটি ঠিক এই সীমাবদ্ধতাই দেখায়।

  2. পরীক্ষা করুন: উপরের কোড সেলে apply_discount-এর else শাখা কভার করার জন্য একটি তৃতীয় AAA-স্টাইল টেস্ট যোগ করুন (is_member=False দিয়ে), এবং নতুন executed_lines সেট গণনা করে দেখুন কভারেজ কত বাড়ে।

    apply_discount(200, False) কল করলে লাইন ১, ৩ ও ৪ এক্সিকিউট হবে (লাইন ২ নয়, কারণ if is_member মিথ্যা)। আগের ভালো টেস্টের {1, 2, 4}-এর সাথে এটি একত্রিত করলে (সেট ইউনিয়ন) পুরো সেটটি হয় {1, 2, 3, 4} — অর্থাৎ calculate_coverage(4, {1, 2, 3, 4}) এখন ১০০.০% দেবে, এবং এবার প্রতিটি শাখাই অন্তত একটি অর্থবহ assertion-সহ টেস্ট দিয়ে সত্যিই যাচাই করা হয়েছে।

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

আগের পাঠ
টেস্ট-ড্রিভেন ডেভেলপমেন্ট (TDD)