ইউজ কেস ও সিনারিও-ভিত্তিক টেস্টিং
এই পাঠে যা শিখবেন
- ইউজ কেস ও সিনারিও-ভিত্তিক টেস্টিং কী, এবং এটি এতদিনের টেকনিকগুলো থেকে কীভাবে আলাদা
- একটি বহু-ধাপ ব্যবহারকারী সিনারিওকে ছোট ছোট স্ট্যান্ড-ইন ফাংশনে ভেঙে বাস্তবায়ন করা
- প্রতিটি ধাপের পরে সিস্টেমের অবস্থা প্রত্যাশার সাথে মেলানো (step-by-step state verification)
- Python-এর
unittestদিয়ে মূল প্রবাহ ও বিকল্প/ব্যতিক্রম প্রবাহ — দুটোই যাচাই করা
১ · ইউজ কেস ও সিনারিও-ভিত্তিক টেস্টিং কী
সিনারিও-ভিত্তিক টেস্টিংScenario-Based Testingএকজন ব্যবহারকারীর একটি বাস্তব, ধারাবাহিক কার্যপ্রবাহকে (একাধিক পরস্পর-নির্ভরশীল ধাপ) একটি একক টেস্ট হিসেবে চালিয়ে, প্রতিটি ধাপের পরে সিস্টেমের অবস্থা প্রত্যাশার সাথে মিলছে কি না যাচাই করা। L05-L08-এর টেকনিকগুলো মূলত একটি একক ফাংশন বা একটি একক ট্রানজিশনের সঠিকতা যাচাই করে। কিন্তু বাস্তব ব্যবহারকারীরা একটি অ্যাপ একক ফাংশন কল হিসেবে ব্যবহার করেন না — তারা একটার পর একটা ধাপ সম্পন্ন করেন। যেমন একটি ই-কমার্স সাইটে: কার্টে আইটেম যোগ করা → কুপন প্রয়োগ করা → চেকআউট করা। প্রতিটি ধাপ আগের ধাপের ফলাফলের উপর নির্ভর করে — তাই এই পুরো প্রবাহটি একসাথে টেস্ট করাই সিনারিও-ভিত্তিক টেস্টিং।
সবকিছু প্রত্যাশা অনুযায়ী ঘটলে ব্যবহারকারী যে ধাপগুলো অতিক্রম করেন — যেমন সফলভাবে কার্ট থেকে চেকআউট পর্যন্ত পৌঁছানো।
কোথাও ভুল হলে বা অস্বাভাবিক কিছু ঘটলে কী হওয়া উচিত — যেমন খালি কার্ট চেকআউট করার চেষ্টা, বা একটি অবৈধ কুপন কোড ব্যবহার করা।
প্রতিটি ধাপের পরে সিস্টেমের অবস্থা (state) প্রত্যাশিত অবস্থার সাথে মেলানো হয় — শুধু চূড়ান্ত ফলাফল নয়, মাঝপথের প্রতিটি ধাপও।
২ · কোড সেলে সম্পূর্ণ সিনারিও চালানো
নিচে add_item, apply_coupon, ও checkout — তিনটি ছোট স্ট্যান্ড-ইন
ফাংশন দিয়ে একটি সম্পূর্ণ শপিং সিনারিও বাস্তবায়ন করা হয়েছে, তারপর প্রতিটি ধাপের পরে কার্টের অবস্থা প্রিন্ট
করে যাচাই করা হয়েছে, এবং সবশেষে একই সিনারিওকে সত্যিকারের unittest টেস্ট কেসে ফরমালাইজ করা
হয়েছে — মূল প্রবাহ এবং দুটো ব্যতিক্রম প্রবাহ সহ।
import unittest
def add_item(cart, name, price):
cart["items"].append({"name": name, "price": price})
return cart
COUPONS = {"SAVE10": 0.10, "SAVE20": 0.20}
def apply_coupon(cart, code):
if code not in COUPONS:
raise ValueError(f"অবৈধ কুপন কোড: {code}")
cart["coupon"] = code
return cart
def calculate_total(cart):
subtotal = sum(item["price"] for item in cart["items"])
discount_pct = COUPONS.get(cart.get("coupon"), 0)
return round(subtotal * (1 - discount_pct), 2)
def checkout(cart):
if not cart["items"]:
raise ValueError("খালি কার্ট চেকআউট করা যায় না")
cart["checked_out"] = True
cart["total"] = calculate_total(cart)
return cart
# সিনারিও: কার্টে আইটেম যোগ করা -> কুপন প্রয়োগ করা -> চেকআউট করা
print("সিনারিও চালনা (মূল প্রবাহ):")
cart = {"items": [], "coupon": None, "checked_out": False}
print(f" ধাপ ০ (শুরু): items={len(cart['items'])}, checked_out={cart['checked_out']}")
add_item(cart, "Keyboard", 1500)
print(f" ধাপ ১ (আইটেম যোগ): items={len(cart['items'])}")
assert len(cart["items"]) == 1
add_item(cart, "Mouse", 500)
print(f" ধাপ ২ (আরেকটি আইটেম যোগ): items={len(cart['items'])}")
assert len(cart["items"]) == 2
apply_coupon(cart, "SAVE10")
print(f" ধাপ ৩ (কুপন প্রয়োগ): coupon={cart['coupon']}")
assert cart["coupon"] == "SAVE10"
checkout(cart)
print(f" ধাপ ৪ (চেকআউট): checked_out={cart['checked_out']}, total={cart['total']}")
assert cart["checked_out"] is True
assert cart["total"] == 1800.0
print("সবগুলো ধাপে প্রত্যাশিত অবস্থা মিলেছে।")
class ShoppingScenarioTests(unittest.TestCase):
def test_full_purchase_scenario(self):
cart = {"items": [], "coupon": None, "checked_out": False}
add_item(cart, "Keyboard", 1500)
add_item(cart, "Mouse", 500)
apply_coupon(cart, "SAVE10")
checkout(cart)
self.assertEqual(len(cart["items"]), 2)
self.assertEqual(cart["coupon"], "SAVE10")
self.assertTrue(cart["checked_out"])
self.assertEqual(cart["total"], 1800.0)
def test_checkout_empty_cart_rejected(self):
cart = {"items": [], "coupon": None, "checked_out": False}
with self.assertRaises(ValueError):
checkout(cart)
def test_invalid_coupon_rejected(self):
cart = {"items": [], "coupon": None, "checked_out": False}
add_item(cart, "Keyboard", 1500)
with self.assertRaises(ValueError):
apply_coupon(cart, "INVALIDCODE")
suite = unittest.TestLoader().loadTestsFromTestCase(ShoppingScenarioTests)
unittest.TextTestRunner(verbosity=2).run(suite)
total-এর হিসাব: সাবটোটাল 1500 + 500 = 2000, তারপর
SAVE10 কুপনে ১০% ছাড় প্রয়োগ হয়ে 2000 × 0.9 = 1800.0 — এটি কোডের নিজস্ব
গণনা, কোথাও হার্ডকোড করা নেই। তিনটি unittest টেস্টই পাস করে: প্রথমটি মূল প্রবাহ (সফল
ক্রয়), আর বাকি দুটো ব্যতিক্রম প্রবাহ (খালি কার্ট চেকআউট, এবং অবৈধ কুপন কোড) — দুটোই সঠিকভাবে
ValueError রেইজ করে প্রত্যাখ্যাত হয়।
সিনারিও-ভিত্তিক টেস্টিং একক ফাংশনের সঠিকতার বদলে পুরো ব্যবহারকারী যাত্রার সঠিকতা যাচাই
করে — এবং শুধু মূল প্রবাহ নয়, বিকল্প/ব্যতিক্রম প্রবাহও পরীক্ষা করা জরুরি, কারণ বাস্তব ব্যবহারকারীরা
মাঝেমধ্যে অপ্রত্যাশিত ক্রমেও কাজ করেন (যেমন খালি কার্টে চেকআউট চাপা)। M2-এর পাঁচটি টেকনিক (ইকুইভ্যালেন্স
পার্টিশনিং, বাউন্ডারি ভ্যালু অ্যানালাইসিস, ডিসিশন টেবিল, স্টেট ট্রানজিশন, সিনারিও-ভিত্তিক) একসাথে
ব্যবহার করলে যেকোনো ফাংশন, রুল, বা যাত্রার জন্য একটি শক্তিশালী, নিয়মতান্ত্রিক টেস্ট ডিজাইন কৌশল পাওয়া
যায় — পরবর্তী মডিউল থেকে আমরা এগুলোকে বাস্তব unittest কাঠামোতে আরও গভীরে নিয়ে যাব।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
যদি সিনারিও টেস্টে শুধু চূড়ান্ত ফলাফল (total == 1800.0) চেক করা হতো, মাঝপথের
ধাপগুলো (যেমন len(cart["items"]) == 2) চেক না করে — তাহলে কী সমস্যা হতে পারত?
চূড়ান্ত ফলাফল ঠিক থাকলেও মাঝপথে কোনো ধাপ ভুল আচরণ করে থাকতে পারে যা কাকতালীয়ভাবে সঠিক চূড়ান্ত মানে পৌঁছে দিয়েছে (যেমন ভুল সংখ্যক আইটেম যোগ হয়েও দাম মিলে যাওয়া)। মাঝপথের ধাপগুলো আলাদাভাবে যাচাই করলে বাগটি ঠিক কোন ধাপে ঘটেছে তা দ্রুত চিহ্নিত করা যায়, যা শুধু শেষ ফলাফল চেক করলে সম্ভব হতো না।
প্র ০২ একটি ইউজ কেসের "মূল প্রবাহ" আর "বিকল্প/ব্যতিক্রম প্রবাহ"-এর মধ্যে পার্থক্য কী, এবং দুটোই টেস্ট করা কেন জরুরি?
মূল প্রবাহ ধরে নেয় সবকিছু প্রত্যাশা অনুযায়ী ঘটছে (happy path) — বিকল্প/ব্যতিক্রম প্রবাহ সেই ক্ষেত্রগুলো কভার করে যেখানে কিছু ভুল হয় বা ব্যবহারকারী অপ্রত্যাশিত কিছু করেন। শুধু মূল প্রবাহ টেস্ট করলে সিস্টেম "স্বাভাবিক" ব্যবহারে ঠিক আছে কি না তা জানা যায়, কিন্তু বাস্তব ব্যবহারকারীরা প্রায়ই অস্বাভাবিক ক্রমে কাজ করেন — তাই ব্যতিক্রম প্রবাহ টেস্ট না করলে প্রোডাকশনে গিয়ে সেগুলোই সবচেয়ে বিরক্তিকর বাগ হিসেবে দেখা দেয়।
প্র ০৩ সিনারিও-ভিত্তিক টেস্টিং কীভাবে স্টেট ট্রানজিশন টেস্টিং (L08)-এর সাথে সম্পর্কিত?
দুটোই "ইতিহাস গুরুত্বপূর্ণ" এই ধারণার উপর ভিত্তি করে তৈরি — কিন্তু স্টেট ট্রানজিশন টেস্টিং একটি এনটিটির সুনির্দিষ্ট কয়েকটি নামকরণ করা "অবস্থা" ও তাদের মধ্যে বৈধ ট্রানজিশন নিয়ে কাজ করে (যেমন অর্ডার স্ট্যাটাস), আর সিনারিও-ভিত্তিক টেস্টিং আরও সাধারণ — এখানে "অবস্থা" মানে যেকোনো ডেটা স্ট্রাকচারের (যেমন কার্টের ভেতরের আইটেম তালিকা, কুপন) পুরো অবস্থা, যা একটি নির্দিষ্ট সংখ্যক নামকরণ করা স্টেটে সীমাবদ্ধ না-ও থাকতে পারে।
অনুশীলন
-
চিন্তা করুন: উপরের শপিং সিনারিওতে আর কোন কোন বিকল্প/ব্যতিক্রম প্রবাহ যোগ করা যেতে পারে
বলে মনে করেন? (হিন্ট: একই কুপন দুইবার প্রয়োগ করার চেষ্টা করলে কী হওয়া উচিত? দাম ঋণাত্মক হলে?)
ভালো প্রার্থী: একবার চেকআউট হয়ে যাওয়া কার্টে আবার আইটেম যোগ করার চেষ্টা (সম্ভবত প্রত্যাখ্যাত হওয়া উচিত), ঋণাত্মক দামের আইটেম যোগ করার চেষ্টা, অথবা চেকআউটের পরে আবার চেকআউট করার চেষ্টা করা — এই প্রতিটিই একটি বাস্তব অ্যাপে ঘটতে পারে এমন যুক্তিসঙ্গত ব্যতিক্রম প্রবাহ।
-
পরীক্ষা করুন: উপরের কোড সেলে
apply_coupon(cart, "SAVE10")-কেapply_coupon(cart, "SAVE20")-এ পরিবর্তন করে Run চেপে দেখুন নতুনtotalকত হয়, এবংtest_full_purchase_scenario-এরassertEqual(cart["total"], 1800.0)লাইনটিও একইভাবে পরিবর্তন না করলে কী হয়।SAVE20-এ ছাড় ২০%, তাই নতুন টোটাল হবে2000 × 0.8 = 1600.0— প্রিন্ট স্টেটমেন্ট ও প্রথমassertঠিকভাবে1600.0দেখাবে যদি সবখানে পরিবর্তন করা হয়। কিন্তু যদি শুধু উপরের ডেমো অংশে পরিবর্তন করেunittest-এরassertEqual(cart["total"], 1800.0)লাইনটি অপরিবর্তিত রেখে দেন, সেই একটি টেস্ট ব্যর্থ (FAIL) হবে — এটি precisely দেখায় কেন টেস্ট কোড ও প্রকৃত স্পেসিফিকেশন সবসময় সিঙ্কে রাখা জরুরি।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
-
পরের পাঠ: Python-এর unittest দিয়ে ইউনিট টেস্টিং ফান্ডামেন্টাল L10
M3 থেকে শুরু হচ্ছে ইউনিট টেস্টিং মডিউল — এতদিন যে
unittestপ্যাটার্ন দেখানো হয়েছে, তার সম্পূর্ণ ফরমাল ভিত্তি। - কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ টেস্ট ডিজাইন টেকনিক, ইউনিট টেস্টিং, টেস্ট ডাবলস, ইন্টিগ্রেশন টেস্টিং, কভারেজ, অটোমেশন, পারফরম্যান্স ও সিকিউরিটি টেস্টিং, অ্যাডভান্সড টেকনিক ও CI/CD ইন্টিগ্রেশন।
- Software Engineering Principles & Git কোর্স সহোদর কোর্স SDLC ও সাধারণ ইঞ্জিনিয়ারিং প্রিন্সিপলের ভিত্তি সেই কোর্সেই তৈরি হয়েছে — এই কোর্স টেস্টিং-এ গভীরে যায়।