ম্যানুয়াল বনাম অটোমেটেড টেস্টিং ও রিগ্রেশন টেস্টিং
এই পাঠে যা শিখবেন
- ম্যানুয়াল ও অটোমেটেড টেস্টিং-এর প্রকৃত শক্তি ও সীমাবদ্ধতা
- রিগ্রেশন টেস্টিং ঠিক কী লক্ষ্য অর্জন করে এবং কেন এটি রিফ্যাক্টরিংকে নিরাপদ করে
- একটি real
RegressionSuiteক্লাস, দুই রানের ফলাফল ধরে রাখা - দুই রানের মধ্যে DIFF করে নতুন করে ভাঙা, অপ্রাসঙ্গিক একটি টেস্ট শনাক্ত করা
১ · ম্যানুয়াল টেস্টিং — মানুষের চোখ যা মেশিন মিস করে
ম্যানুয়াল টেস্টিংManual Testingএকজন মানুষ সরাসরি সফটওয়্যার ব্যবহার করে, পর্যবেক্ষণের মাধ্যমে আচরণ যাচাই করে। মানে একজন মানুষ সরাসরি সফটওয়্যার ব্যবহার করে আচরণ যাচাই করছেন। একে সেকেলে ভাবার আগে সৎভাবে বলা দরকার — মানুষ এমন সমস্যা লক্ষ্য করতে পারে যা একটি স্ক্রিপ্ট কখনো "চেক করতে" ভাববে না: একটি বাটন সামান্য বাঁকা দেখাচ্ছে, একটি মেসেজ আসলে বিভ্রান্তিকর, একটি ফ্লো "কাজ করছে" কিন্তু আসলে ব্যবহার করা অস্বস্তিকর। এই ধরনের exploratory testing — ইচ্ছাকৃতভাবে অস্বাভাবিক, সৃজনশীল উপায়ে সিস্টেম ব্যবহার করে অজানা বাগ খোঁজা — মানুষের একটি প্রকৃত শক্তি, যা predefined assertion-এ লেখা কোনো স্ক্রিপ্ট প্রতিস্থাপন করতে পারে না।
২ · অটোমেটেড টেস্টিং — গতি, পুনরাবৃত্তিযোগ্যতা, সীমাবদ্ধতা
অটোমেটেড টেস্টিংAutomated Testingটেস্ট কোড হিসেবে লেখা হয় (M10/L44-L47), মেশিন চালায় — প্রতি রানে মানুষের হস্তক্ষেপ ছাড়াই। মানে টেস্ট কোড হিসেবে লেখা হয় (M10/L44-L47-এর সবটাই এর অংশ) এবং মেশিন চালায়, মানুষের হস্তক্ষেপ ছাড়াই। সততার সাথে দুই দিকই বলা দরকার — শক্তি: দ্রুত, পুনরাবৃত্তিযোগ্য (M10/L46-এর FIRST মানদণ্ডের সরাসরি পুনঃব্যবহার), এবং ক্রমাগত চালানো যায় (M12/L53-এর CI-এর সরাসরি ফাউন্ডেশন — প্রতিটি কোড পরিবর্তনে স্বয়ংক্রিয়ভাবে চলে)। সীমাবদ্ধতা: এটি শুধু সেটাই ধরতে পারে যা এক্সপ্লিসিটলি চেক করার জন্য লেখা হয়েছে — মানুষের মতো অপ্রত্যাশিত কিছু "লক্ষ্য করার" ক্ষমতা এর নেই।
Exploratory-তে শক্তিশালী, unexpected সমস্যা ধরে — কিন্তু ধীর, ব্যয়বহুল, প্রতিটি রিলিজে পুনরাবৃত্তি করা কঠিন।
দ্রুত, পুনরাবৃত্তিযোগ্য, ক্রমাগত চালানো যায় — কিন্তু শুধু predefined assertion-ই ধরে, নতুন কিছু "খুঁজে বের করে না"।
৩ · রিগ্রেশন টেস্টিং — কেন এটি রিফ্যাক্টরিংকে নিরাপদ করে
রিগ্রেশন টেস্টিংRegression Testingবিদ্যমান টেস্ট আবার চালানো — একটি নতুন পরিবর্তন যেন আগে থেকে কাজ-করা কিছু ভেঙে না ফেলে, তা যাচাই করার জন্য। একটি নির্দিষ্ট লক্ষ্য (ম্যানুয়াল বা অটোমেটেড — যেকোনো মাধ্যমেই সম্ভব, যদিও বাস্তবে প্রায় সবসময় অটোমেটেড, কারণ পুনরাবৃত্তিযোগ্যতা ও গতি এখানে সবচেয়ে বেশি জরুরি) — বিদ্যমান টেস্ট আবার চালানো, বিশেষভাবে রিগ্রেশন ধরার জন্য: এমন কেস যেখানে একটি পরিবর্তন একটি জিনিস ঠিক করতে/যোগ করতে গিয়ে অজান্তে আগে-কাজ-করা অন্য কিছু ভেঙে ফেলেছে। এখানেই M11/L50-এর রিফ্যাক্টরিং সরাসরি নির্ভরশীল — regression suite ছাড়া কোনো রিফ্যাক্টরিং "নিরাপদ" নয়, কারণ কোনো নিশ্চয়তা নেই কিছু অজান্তে ভেঙে গেছে কিনা।
৪ · বাস্তবে রিগ্রেশন ডিটেকশন — একটি ফিক্স, একটি অপ্রত্যাশিত ভাঙন
নিচের কোড সেলে একটি RegressionSuite ক্লাস আছে যা ৪টি টেস্ট রাখে। প্রথমে (v1) পুরো স্যুট চালিয়ে
একটি "known good" বেসলাইন প্রতিষ্ঠা করা হয় — সবগুলো PASS। তারপর একটি real ফিক্স প্রয়োগ করা হয়:
round_currency()-কে ট্রাংকেশনের (int()) বদলে সঠিক rounding-এ (round())
পাল্টানো হয় — যা একটি real শিপিং-আন্ডারচার্জ বাগ ঠিক করে। কিন্তু এই একই হেল্পার ফাংশন ডিসকাউন্ট ক্যালকুলেশনেও
ব্যবহৃত হয় — এবং সেখানে একটি সম্পূর্ণ অপ্রাসঙ্গিক টেস্ট অজান্তে ভেঙে যায়। কোড দুই রানের ফলাফল DIFF করে ঠিক
কোন টেস্টটি নতুন করে ফেইল করেছে তা বের করে।
class RegressionSuite:
def __init__(self):
self.tests = [] # (name, test_function) জোড়া
def add_test(self, name, test_function):
self.tests.append((name, test_function))
def run_all(self):
results = {}
for name, test_function in self.tests:
try:
test_function()
results[name] = "PASS"
except AssertionError as e:
results[name] = f"FAIL ({e})"
return results
# ---- আসল কোড: round_currency একটি শেয়ার্ড হেল্পার, discount ও shipping দুটোতেই ব্যবহৃত ----
CONFIG = {"use_proper_rounding": False} # শুরুতে v1 (বাগযুক্ত ট্রাংকেশন)
def round_currency(amount):
return round(amount) if CONFIG["use_proper_rounding"] else int(amount)
def calculate_discount(price):
if price > 500:
return round_currency(price * 0.9)
return price
def calculate_shipping(weight_kg):
return round_currency(weight_kg * 49.6)
def test_discount_large_order():
assert calculate_discount(1000) == 900
def test_discount_boundary_order():
assert calculate_discount(513) == 461 # v1 (ট্রাংকেট): int(461.7) == 461
def test_no_discount_small_order():
assert calculate_discount(300) == 300
def test_shipping_standard():
assert calculate_shipping(4) == 198 # int(198.4) == round(198.4) == 198, দুই ভার্সনেই একই
suite = RegressionSuite()
suite.add_test("discount_large_order", test_discount_large_order)
suite.add_test("discount_boundary_order", test_discount_boundary_order)
suite.add_test("no_discount_small_order", test_no_discount_small_order)
suite.add_test("shipping_standard", test_shipping_standard)
print("=== রান ১: বেসলাইন (v1 -- round_currency ট্রাংকেট করে) ===")
results_before = suite.run_all()
for name, status in results_before.items():
print(f" {name}: {status}")
# ---- ফিক্স প্রয়োগ: শিপিং আন্ডারচার্জ বাগ ঠিক করতে round_currency-কে সঠিক rounding-এ পাল্টানো ----
print(f"\nফিক্সের আগে ৩ কেজির শিপিং (v1, ট্রাংকেট): {calculate_shipping(3)} (সঠিক মান 148.8 -- আন্ডারচার্জ বাগ)")
CONFIG["use_proper_rounding"] = True
print(f"ফিক্সের পরে ৩ কেজির শিপিং (v2, round): {calculate_shipping(3)} (এখন সঠিকভাবে round হয়েছে)")
print("\n=== রান ২: ফিক্সের পরে (v2 -- round_currency সঠিকভাবে round করে) ===")
results_after = suite.run_all()
for name, status in results_after.items():
print(f" {name}: {status}")
# ---- দুই রানের ফলাফল DIFF করে নতুন করে ফেইল করা টেস্ট খুঁজে বের করা ----
newly_failing = [n for n in results_after if results_before[n] == "PASS" and results_after[n] != "PASS"]
newly_passing = [n for n in results_after if results_before[n] != "PASS" and results_after[n] == "PASS"]
print("\n=== DIFF রিপোর্ট ===")
print("নতুন করে FAIL করেছে (রিগ্রেশন):", newly_failing)
print("নতুন করে PASS করেছে:", newly_passing)
test_shipping_standard ফিক্সের পরও PASS থাকে (এই ইনপুটে ট্রাংকেট ও round একই
ফলাফল দেয়), কিন্তু test_discount_boundary_order নতুন করে FAIL করে, যদিও ডেভেলপার শুধু শিপিং ঠিক
করতে চেয়েছিলেন, ডিসকাউন্ট নয়। এটাই একটি বাস্তব রিগ্রেশনের ক্লাসিক প্যাটার্ন — একটি শেয়ার্ড, লুকানো নির্ভরতা
(এখানে round_currency) দুটো অসম্পর্কিত ফিচারকে চুপচাপ সংযুক্ত করে রাখে।
একটি regression suite-এর আসল মূল্য শুধু "টেস্ট চালানো"-তে নয় — এর মূল্য হলো দুটো ফলাফল সেট (আগে ও পরে) তুলনা করার ক্ষমতায়। এই তুলনা ছাড়া একটি নতুন FAIL শুধুই একটি FAIL — কিন্তু "এটি আগে PASS করত" জানাটাই বলে দেয় এটি একটি রিগ্রেশন, যা এই নির্দিষ্ট পরিবর্তনের কারণে ঘটেছে। এটাই M11/L50-এর রিফ্যাক্টরিংকে "নিরাপদ" করে তোলার প্রকৃত মেকানিজম।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
উপরের কোডে test_shipping_standard কেন ফিক্সের পরও FAIL করেনি, যদিও round_currency-ই তো পাল্টেছে?
কারণ 4 কেজির হিসাব 4 * 49.6 = 198.4-এর ভগ্নাংশ অংশ (.4) 0.5-এর কম — তাই int()
(ট্রাংকেট, নিচের দিকে) এবং round() (নিকটতম পূর্ণসংখ্যায়, এখানেও নিচের দিকেই) দুটোই একই
ফলাফল 198 দেয়। এটাই দেখায় একটি বাগ-ফিক্স সবসময় সব টেস্টের ফলাফল বদলায় না — শুধু সেই ইনপুটগুলোর জন্যই
বদলায় যেখানে পুরনো ও নতুন লজিক আসলে ভিন্ন ফলাফল দেয়।
প্র ০২ যদি এই রিগ্রেশন স্যুট না চালিয়ে সরাসরি শিপিং ফিক্সটি প্রোডাকশনে ডিপ্লয় করা হতো, বাস্তবে কী হতে পারত?
ডিসকাউন্ট ক্যালকুলেশনের ফলাফল অজান্তেই বদলে যেত — গ্রাহকদের ভুল ডিসকাউন্ট দেখানো হতো (এই ক্ষেত্রে সামান্য বেশি — 461 থেকে 462), এবং এই সমস্যা হয়তো সপ্তাহখানেক পরে কোনো গ্রাহকের অভিযোগ থেকে ধরা পড়ত, ততদিনে কারণ খুঁজে বের করা অনেক কঠিন হয়ে যেত — ঠিক যে সমস্যাটি regression testing প্রতিরোধ করার জন্য ডিজাইন করা।
প্র ০৩ এই রিগ্রেশন ধরা কি ম্যানুয়াল টেস্টিং দিয়েও সম্ভব হতো?
তাত্ত্বিকভাবে হ্যাঁ — একজন টেস্টার হাতে-হাতে সব ৪টি ফিচার আবার চেক করতে পারতেন। কিন্তু বাস্তবে, প্রতিটি ছোট কোড পরিবর্তনের পর সম্পূর্ণ সিস্টেম ম্যানুয়ালি আবার যাচাই করা এত সময়সাপেক্ষ যে বেশিরভাগ টিম এটি করে না — ফলে এই ধরনের সূক্ষ্ম, "অপ্রাসঙ্গিক" রিগ্রেশন ম্যানুয়াল টেস্টিং থেকে সহজেই ফসকে যায়। এটাই মূল কারণ কেন রিগ্রেশন টেস্টিং প্রায় সবসময় অটোমেটেড করা হয় — দ্রুত, প্রতিবার, ঠিক একইভাবে চালানো যায়।
অনুশীলন
-
পরীক্ষা করুন: উপরের কোডে
test_discount_large_order-এর ইনপুট1000-এর বদলে513ব্যবহার করে (অর্থাৎ দুটো টেস্টই একই ইনপুট চেক করলে) কোড রান করুন — কী পরিবর্তন লক্ষ্য করলেন?তখন
test_discount_large_order-ও ফিক্সের পর FAIL করবে, কারণ এখন সেটিও একই boundary-sensitive ইনপুট (513) চেক করছে, যেখানেround()ওint()ভিন্ন ফলাফল দেয়। DIFF রিপোর্টেnewly_failingলিস্টে তখন দুটো নাম থাকবে একটির বদলে — দেখায় রিগ্রেশনের "আকার" নির্ভর করে ঠিক কতগুলো টেস্ট আসলে সেই পরিবর্তিত আচরণের উপর নির্ভরশীল। -
চিন্তা করুন:
newly_passingলিস্ট (যেসব টেস্ট আগে FAIL করত, এখন PASS করে) কেন বাস্তবে গুরুত্বপূর্ণ তথ্য, শুধুnewly_failingনয়?newly_passingনিশ্চিত করে যে আপনার ফিক্সটি আসলে যা ঠিক করতে চেয়েছিল তা সত্যিই ঠিক করেছে (intended effect) — শুধুnewly_failingদেখলে আপনি জানবেন কী ভেঙেছে, কিন্তু জানবেন না ফিক্সটি আদৌ কাজ করেছে কিনা। একটি সম্পূর্ণ DIFF রিপোর্ট সবসময় দুই দিকই দেখানো উচিত — কী নতুন ভেঙেছে এবং কী নতুন ঠিক হয়েছে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ এরপর M11 শুরু — কোড কোয়ালিটি ও রিফ্যাক্টরিং, প্রথম পাঠ কোড স্মেল দিয়ে।
- রিফ্যাক্টরিং টেকনিক M11/L50 এই পাঠের RegressionSuite প্যাটার্নই সেখানে ব্যবহার হয় Extract Method রিফ্যাক্টরিং-এর behavior-preservation যাচাই করতে।
- মকিং ও টেস্ট ডাবল M10/L47 পূর্ববর্তী পাঠ — ইউনিট টেস্টকে আইসোলেটেড রাখার টুল, যা একটি স্বাস্থ্যকর regression suite-এর ভিত্তি।