মক ও ইন্টারঅ্যাকশন ভেরিফিকেশন
এই পাঠে যা শিখবেন
assert_called_once_with()দিয়ে একটি ডিপেন্ডেন্সি ঠিক একবার, সঠিক আর্গুমেন্ট দিয়ে কল হয়েছে কি না যাচাই করা.call_countও.call_argsপড়ে ইন্টারঅ্যাকশনের বিস্তারিত পরিদর্শন করা- একটি সঠিক বাস্তবায়নের বিপরীতে টেস্ট পাস হতে এবং একটি ভাঙা বাস্তবায়নের বিপরীতে সত্যিই ফেইল হতে দেখা
- ভুল আর্গুমেন্ট দিয়ে কল করা হলে তা কীভাবে ধরা পড়ে তা প্রত্যক্ষ করা
১ · রিটার্ন ভ্যালু যথেষ্ট নয় — কখনো কখনো "কীভাবে কল হলো" সেটাই আসল প্রশ্ন
ধরুন complete_order(order_id, total, email_service) ফাংশনটির কাজ হলো অর্ডার প্রক্রিয়া শেষ করার
পর email_service.send_receipt(order_id, total) কল করে গ্রাহককে রিসিট পাঠানো, এবং সবশেষে
True রিটার্ন করা। এই ফাংশনের রিটার্ন ভ্যালু (True) চেক করলেই কিন্তু বোঝা যাবে না
রিসিট আদৌ পাঠানোর চেষ্টা হয়েছিল কি না — কারণ ফাংশনটি ভুলবশত send_receipt() কল না করেও
True রিটার্ন করে দিতে পারে! এই ধরনের ক্ষেত্রেই Mock-এর ইন্টারঅ্যাকশন-ভেরিফিকেশন
ক্ষমতা দরকার — শুধু ফলাফল নয়, ডিপেন্ডেন্সির সাথে কথোপকথন সঠিক হয়েছিল কি না তা নিশ্চিত করা।
assert_called_once_with(...)ঠিক একবার, ঠিক এই আর্গুমেন্ট দিয়েই কল হয়েছিল কি না যাচাই করে — না মিললে AssertionError।
.call_countমোট কতবার কল হয়েছে তার সংখ্যা — ০ বা প্রত্যাশার চেয়ে বেশি কল শনাক্ত করতে কাজে লাগে।
.call_argsসর্বশেষ কলের পজিশনাল ও কীওয়ার্ড আর্গুমেন্ট ধরে রাখে — সরাসরি পরিদর্শন বা কাস্টম যাচাইয়ের জন্য।
২ · সঠিক বনাম ভাঙা বাস্তবায়ন — দুটোই সত্যিই চালিয়ে দেখা
নিচে দুটো বাস্তবায়ন আছে — complete_order (সঠিক, রিসিট পাঠায়) এবং
complete_order_broken (ইচ্ছাকৃতভাবে ভাঙা, রিসিট পাঠাতে ভুলে গেছে)। একই টেস্ট-প্যাটার্ন
(assert_called_once_with) দুটোর বিপরীতেই চালানো হয়েছে — লক্ষ্য করুন ফলাফল কীভাবে আলাদা হয়।
import unittest
from unittest.mock import Mock
def complete_order(order_id, total, email_service):
# সঠিক বাস্তবায়ন — অর্ডার শেষে রিসিট ইমেইল সার্ভিসকে জানানো হয়
email_service.send_receipt(order_id, total)
return True
def complete_order_broken(order_id, total, email_service):
# বাগ: রিসিট ইমেইল পাঠাতে ভুলে গেছে!
return True
class OrderReceiptTests(unittest.TestCase):
def test_correct_implementation_sends_receipt(self):
mock_email_service = Mock()
complete_order(101, 550, mock_email_service)
mock_email_service.send_receipt.assert_called_once_with(101, 550)
self.assertEqual(mock_email_service.send_receipt.call_count, 1)
def test_broken_implementation_forgets_to_send_receipt(self):
mock_email_service = Mock()
complete_order_broken(101, 550, mock_email_service)
mock_email_service.send_receipt.assert_called_once_with(101, 550)
suite = unittest.TestLoader().loadTestsFromTestCase(OrderReceiptTests)
unittest.TextTestRunner(verbosity=2).run(suite)
test_correct_implementation_sends_receipt পাস করে, কিন্তু
test_broken_implementation_forgets_to_send_receipt সত্যিই ফেইল করে। কারণ
complete_order_broken কখনও email_service.send_receipt() কলই করেনি — তাই
mock_email_service.send_receipt.call_count থাকে 0, আর
assert_called_once_with(101, 550) ঠিক এটাই ধরে ফেলে এবং AssertionError রেইজ করে।
এটাই দেখায় কেন শুধু রিটার্ন ভ্যালু (True, দুটো বাস্তবায়নেই একই) চেক করলে এই বাগ কখনও ধরা পড়ত না।
৩ · call_count ও call_args সরাসরি পরিদর্শন
assert_called_once_with() একটি "সব-বা-কিছুই না" যাচাই — মিললে চুপচাপ পাস করে, না মিললে
এক্সেপশন ছোঁড়ে। মাঝে মাঝে ঠিক কী আর্গুমেন্ট দিয়ে কল হয়েছিল তা নিজে দেখেও বুঝতে চাইতে পারেন — সেজন্য
.call_count ও .call_args সরাসরি পড়া যায়। নিচে একটি তৃতীয়, ভুল-আর্গুমেন্টের
বাস্তবায়নও (complete_order_wrong_amount) দেখানো হয়েছে, যা ডিপেন্ডেন্সিকে কল তো করে, কিন্তু
ভুল টাকার অঙ্ক দিয়ে।
from unittest.mock import Mock
def complete_order_wrong_amount(order_id, total, email_service):
# বাগ: total-এর বদলে ভুল করে 0 পাঠাচ্ছে
email_service.send_receipt(order_id, 0)
return True
mock_a = Mock()
complete_order(101, 550, mock_a)
print("সঠিক বাস্তবায়ন — call_count:", mock_a.send_receipt.call_count)
print("সঠিক বাস্তবায়ন — call_args:", mock_a.send_receipt.call_args)
mock_b = Mock()
complete_order_wrong_amount(101, 550, mock_b)
print("ভুল-আর্গুমেন্ট বাস্তবায়ন — call_args:", mock_b.send_receipt.call_args)
try:
mock_b.send_receipt.assert_called_once_with(101, 550)
print("assert_called_once_with পাস করলো (এটা হওয়ার কথা না!)")
except AssertionError:
print("assert_called_once_with ব্যর্থ (AssertionError) — ভুল আর্গুমেন্ট ধরা পড়েছে")
mock_a.send_receipt.call_args দেখাবে call(101, 550) — সঠিক আর্গুমেন্ট। কিন্তু
mock_b.send_receipt.call_args দেখাবে call(101, 0) — total-এর জায়গায়
ভুলবশত 0 পাঠানো হয়েছে। তাই mock_b-এর উপর assert_called_once_with(101, 550)
কল করলে তা প্রকৃত কলের সাথে না মিলে AssertionError রেইজ করে, যা try/except-এ ধরা
পড়ে এবং সঠিকভাবে রিপোর্ট হয়।
Mock-এর প্রকৃত শক্তি শুধু "একটি বাঁধাধরা উত্তর দেওয়া" নয় (সেটা Stub-এর কাজ) — এটি প্রতিটি কল
মনে রাখে এবং পরে সেই কলগুলো নিয়ে নির্ভুল দাবি (assertion) করার সুযোগ দেয়। যেসব বাগ শুধু আউটপুট দেখে
ধরা পড়ে না — যেমন একটি প্রয়োজনীয় সাইড-ইফেক্ট (ইমেইল পাঠানো, লগ লেখা, পেমেন্ট চার্জ করা) সম্পূর্ণ বাদ পড়ে
যাওয়া বা ভুল আর্গুমেন্ট দিয়ে ঘটা — সেগুলো assert_called_once_with()-এর মতো
ইন্টারঅ্যাকশন-ভেরিফিকেশনেই ধরা পড়ে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
দুটো বাস্তবায়নই (complete_order ও complete_order_broken) একই
True রিটার্ন করে। শুধু রিটার্ন ভ্যালু চেক করা কেন এই বাগ ধরতে ব্যর্থ হতো?
কারণ বাগটি রিটার্ন ভ্যালুতে নেই — এটি একটি মিসিং সাইড-ইফেক্ট (রিসিট পাঠানো ভুলে যাওয়া)।
self.assertTrue(complete_order_broken(...))-জাতীয় একটি টেস্ট নিশ্চিন্তে পাস করত, কারণ
ফাংশনটি সত্যিই True রিটার্ন করে। এই বাগ ধরতে ফাংশনের ভেতরের আচরণ — অর্থাৎ এটি তার
ডিপেন্ডেন্সিকে ঠিকভাবে কল করেছে কি না — তা যাচাই করতে হয়, যা কেবল ইন্টারঅ্যাকশন-ভেরিফিকেশনই দিতে পারে।
প্র ০২
assert_called_once_with()-এর বদলে যদি শুধু self.assertEqual(mock_email_service.send_receipt.call_count, 1)
ব্যবহার করা হতো, তাহলে কি complete_order_wrong_amount-এর ভুল-আর্গুমেন্ট বাগটি ধরা পড়ত?
না। call_count == 1 শুধু নিশ্চিত করে ঠিক একবার কল হয়েছে — কোন আর্গুমেন্ট দিয়ে কল
হয়েছে তা নিয়ে কিছু বলে না। complete_order_wrong_amount-এ send_receipt()
ঠিক একবারই কল হয় (ভুল আর্গুমেন্ট দিয়ে হলেও), তাই শুধু call_count চেক করলে এই বাগ চোখ
এড়িয়ে যেত। এই কারণেই assert_called_once_with(101, 550)-এর মতো আর্গুমেন্ট-সহ যাচাই বেশি
শক্তিশালী।
প্র ০৩
উপরের কোড সেলে ঠিক কোন লাইনে বাগ আছে, এবং এটি ঠিক করতে কী পরিবর্তন করতে হবে যাতে
test_broken_implementation_forgets_to_send_receipt পাস করে?
বাগটি complete_order_broken ফাংশনে — এটি সরাসরি return True করে দেয়,
email_service.send_receipt(order_id, total) কল করাই ভুলে গেছে। ফাংশনের শুরুতে
email_service.send_receipt(order_id, total) লাইনটি যোগ করলেই টেস্টটি পাস করবে —
ঠিক যেমন complete_order-এ আছে।
অনুশীলন
-
চিন্তা করুন: একটি ফাংশন তার ডিপেন্ডেন্সিকে দুইবার কল করলে
assert_called_once_with()-এর কী হবে বলে মনে হয়? কেন?assert_called_once_with()ফেইল করবে — এর নাম অনুযায়ীই এটি নিশ্চিত করে ঠিক একবার কল হয়েছে, একাধিকবার নয়। যদি একাধিকবার কল হওয়া প্রত্যাশিত হয়, তাহলেassert_any_call(...)(অন্তত একবার এই আর্গুমেন্ট দিয়ে কল হয়েছে) বাcall_countওcall_args_listএকসাথে ব্যবহার করাই উপযুক্ত। -
পরীক্ষা করুন: প্রথম কোড সেলে
complete_order_broken-এর ভেতরেemail_service.send_receipt(order_id, total)লাইনটি যোগ করে Run চাপুন — এখন কি দুটো টেস্টই পাস করে?হ্যাঁ — লাইনটি যোগ করার পর
complete_order_brokenকার্যতcomplete_order-এর মতোই আচরণ করবে, তাইmock_email_service.send_receipt.assert_called_once_with(101, 550)এখন মিলে যাবে এবংtest_broken_implementation_forgets_to_send_receipt-সহ দুটো টেস্টই পাস করবে, আউটপুটে "OK" দেখাবে আগের "FAILED (failures=1)"-এর বদলে।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পরের পাঠ L18 কখন মক ব্যবহার করবেন না — ওভার-মকিং সমস্যা — মক যখন নিজেই বাগ আড়াল করে ফেলে।
-
আগের পাঠ L16
স্টাব ও ফেক বাস্তবে ব্যবহার —
return_value/side_effectও একটি dict-backed ফেক রিপোজিটরি। - কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ টেস্ট ডিজাইন টেকনিক, ইউনিট টেস্টিং, টেস্ট ডাবলস, ইন্টিগ্রেশন টেস্টিং, কভারেজ, অটোমেশন, পারফরম্যান্স ও সিকিউরিটি টেস্টিং, অ্যাডভান্সড টেকনিক ও CI/CD ইন্টিগ্রেশন।