পাঠ ১০ · ৫৭-এর মধ্যে · মডিউল ৩
Home / Courses / Software Testing & Quality Assurance / ইউনিট টেস্টিং

Python-এর unittest দিয়ে ইউনিট টেস্টিং ফান্ডামেন্টাল

Unit testing fundamentals with Python's unittest
৯ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • unittest.TestCase সাবক্লাসিং-এর নিয়ম ও একটি টেস্ট মেথড চেনার শর্ত
  • assertEqual, assertTrue, ও assertRaises — কোন পরিস্থিতিতে কোনটি ব্যবহার করবেন
  • টেস্ট চালানোর দুটি নিরাপদ প্যাটার্ন এবং কেন বেয়ার unittest.main() এড়াতে হবে
  • একটি সম্পূর্ণ, সত্যিকারের, সব-পাস-করা Calculator টেস্ট স্যুট — L01-এর ব্যর্থ ডেমোর বিপরীত চিত্র

১ · TestCase সাবক্লাসিং

L01-এ আমরা unittest দিয়ে একটি বাগ ধরতে দেখেছি, কিন্তু ফরমালি ব্যাখ্যা করিনি কীভাবে গঠন করতে হয়। নিয়ম সহজ: unittest.TestCaseTestCaseunittest মডিউলের একটি ক্লাস, যা সাবক্লাস করে তার মধ্যে test_ দিয়ে শুরু হওয়া মেথড লিখলে প্রতিটি মেথড একটি স্বতন্ত্র, স্বয়ংক্রিয়ভাবে শনাক্তযোগ্য টেস্ট কেস হয়ে যায়। একটি ক্লাস unittest.TestCase-কে সাবক্লাস করে, এবং তার ভেতরে test_ প্রিফিক্স দিয়ে শুরু হওয়া প্রতিটি মেথড unittest-এর টেস্ট-ডিসকভারি ব্যবস্থার কাছে একটি স্বতন্ত্র টেস্ট কেস হিসেবে গণ্য হয়। প্রতিটি টেস্ট মেথডের ভেতরে self.assert... কল করে যাচাই করা হয় প্রকৃত ফলাফল প্রত্যাশিত ফলাফলের সাথে মিলছে কি না।

assertEqual(a, b)
দুটি মান ঠিক সমান কি না যাচাই করে — সবচেয়ে বেশি ব্যবহৃত অ্যাসারশন।
assertTrue(x)
x সত্য (truthy) কি না যাচাই করে — কোনো শর্ত/বুলিয়ান ফলাফল যাচাইয়ে ব্যবহৃত হয়।
assertRaises(Error)
কোড ব্লকটি প্রত্যাশিত ব্যতিক্রম (exception) ছোঁড়ে কি না যাচাই করে — with ব্লক হিসেবে ব্যবহৃত হয়।

২ · টেস্ট চালানোর নিরাপদ প্যাটার্ন

এই কোর্সের প্রতিটি কোড সেল Pyodide স্যান্ডবক্সের ভেতরে চলে, আর সাধারণ unittest.main() ভেতরে sys.exit() ডাকে — এটি টার্মিনালে স্ক্রিপ্ট হিসেবে চালালে ঠিক আছে, কিন্তু একটি চলমান নোটবুক-স্টাইল সেলে ডাকলে পুরো সেলটি ক্র্যাশ করিয়ে দেবে। তাই এই কোর্সে সবসময় দুটির একটি নিরাপদ প্যাটার্ন ব্যবহার করা হয়:

  1. unittest.main(argv=['ignored', '-v'], exit=False) — একটি ভুয়া argv পাস করে ও exit=False সেট করে sys.exit() বন্ধ রাখা হয়।
  2. একটি suite ম্যানুয়ালি তৈরি করে TextTestRunner দিয়ে চালানো — unittest.TestLoader().loadTestsFromTestCase(MyTestCase) দিয়ে সুইট বানিয়ে unittest.TextTestRunner(verbosity=2).run(suite) দিয়ে চালানো হয়।

দুটোই সত্যিকারের পাস/ফেল/এরর ফলাফল প্রিন্ট করে, কোনোটিই স্যান্ডবক্স ক্র্যাশ করায় না — এই কোর্সে দুটোই দেখা যাবে, প্রসঙ্গ অনুযায়ী।

Python Programming কোর্সের সাথে সম্পর্ক

ক্লাস, মেথড, এক্সেপশন হ্যান্ডলিং-এর মতো ভাষাগত ভিত্তি Python Programming কোর্সে ধরে নেওয়া হয়েছে — এই পাঠ শুধু সেই ভিত্তির উপর unittest কীভাবে ব্যবহার করতে হয় তা শেখায়।

৩ · একটি সত্যিকারের "গ্রিন" ডেমো — L01-এর বিপরীত চিত্র

L01-এ calculate_shipping_cost-এর একটি বাউন্ডারি বাগ ধরা পড়েছিল — তিনটির মধ্যে একটি টেস্ট ব্যর্থ (FAIL) হয়েছিল। কিন্তু unittest-এর কাজ শুধু ব্যর্থতা দেখানো নয় — একটি সঠিকভাবে লেখা কোডে সব টেস্ট পাস করাও ঠিক ততটাই সত্যিকারের, স্বাভাবিক ফলাফল। নিচে একটি ছোট্ট Calculator ক্লাস আছে — add, subtract, ও divide মেথডসহ, আর divide শূন্য দিয়ে ভাগ করলে ZeroDivisionError ছোঁড়ে। পাঁচটি টেস্ট কেস — সবগুলো সত্যিকারভাবে চালিয়ে দেখা যাক সব পাস করে কি না।

Python
import unittest

class Calculator:
    def add(self, a, b):
        return a + b

    def subtract(self, a, b):
        return a - b

    def divide(self, a, b):
        if b == 0:
            raise ZeroDivisionError("শূন্য দিয়ে ভাগ করা যায় না")
        return a / b

class CalculatorTests(unittest.TestCase):
    def test_add_returns_sum(self):
        calc = Calculator()
        self.assertEqual(calc.add(2, 3), 5)

    def test_subtract_returns_difference(self):
        calc = Calculator()
        self.assertEqual(calc.subtract(10, 4), 6)

    def test_divide_returns_quotient(self):
        calc = Calculator()
        self.assertEqual(calc.divide(10, 2), 5.0)

    def test_add_result_is_positive(self):
        calc = Calculator()
        self.assertTrue(calc.add(4, 6) > 0)

    def test_divide_by_zero_raises_zerodivisionerror(self):
        calc = Calculator()
        with self.assertRaises(ZeroDivisionError):
            calc.divide(10, 0)

suite = unittest.TestLoader().loadTestsFromTestCase(CalculatorTests)
unittest.TextTestRunner(verbosity=2).run(suite)

    
Run চাপলে দেখবেন পাঁচটি টেস্টই পাস করে — আউটপুটের শেষে OK দেখাবে, L01-এর FAILED (failures=1)-এর ঠিক উল্টো। দুটো ফলাফলই সমান বৈধ ও সমান গুরুত্বপূর্ণ: একটি টেস্ট স্যুট বাগ ধরলে সেটি তার কাজ করছে বলেই ব্যর্থ দেখায়; আর কোড সঠিক থাকলে একই স্যুট সব পাস দেখানোই তার সঠিক আচরণ। "গ্রিন" মানে "টেস্ট লেখা হয়নি" নয় — বরং "যা যাচাই করা হয়েছে তার সবকিছু প্রত্যাশামতো কাজ করছে।"
মূল কথা · Key takeaway

একটি ইউনিট টেস্ট মানে TestCase সাবক্লাসের ভেতরে একটি test_ মেথড, যেখানে assertEqual/assertTrue/assertRaises-এর মতো অ্যাসারশন দিয়ে প্রকৃত ফলাফল যাচাই করা হয়, আর সবসময় নিরাপদ রান-প্যাটার্নে চালানো হয়। পরের পাঠে (L11) আমরা আরও অ্যাসারশন মেথড ও টেস্ট অর্গানাইজেশনের ভালো অভ্যাস দেখব।

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

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

প্র ০১ unittest.main() বেয়ার অবস্থায় ডাকলে কী সমস্যা হয়, আর কেন এই কোর্সের সব কোড সেলে অন্য প্যাটার্ন ব্যবহার করা হয়?

বেয়ার unittest.main() ভেতরে sys.exit() ডাকে, যা টার্মিনাল স্ক্রিপ্টে সমস্যা না হলেও একটি চলমান কোড সেল/নোটবুক পরিবেশে পুরো সেল প্রসেসটিকে থামিয়ে দিতে পারে। তাই argv ও exit=False দিয়ে অথবা TextTestRunner দিয়ে ম্যানুয়ালি সুইট চালিয়ে এই sys.exit() কল এড়ানো হয়, যাতে সেল নিরাপদে চলতে ও ফলাফল দেখাতে পারে।

প্র ০২ উপরের test_divide_by_zero_raises_zerodivisionerror টেস্টে assertEqual ব্যবহার না করে assertRaises কেন দরকার?

কারণ এখানে আমরা কোনো রিটার্ন ভ্যালু যাচাই করছি না — আমরা যাচাই করছি যে কোডটি একটি নির্দিষ্ট ব্যতিক্রম (ZeroDivisionError) ছোঁড়ে কি না। assertEqual দিয়ে এটি ধরা সম্ভব নয়, কারণ ব্যতিক্রম ছোঁড়া হলে ফাংশনটি কোনো মান রিটার্নই করে না — with self.assertRaises(...) ব্লক বিশেষভাবে এই পরিস্থিতির জন্য ডিজাইন করা।

প্র ০৩ L01-এর ডেমোতে একটি টেস্ট ব্যর্থ হয়েছিল, এই পাঠের ডেমোতে সব টেস্ট পাস করেছে — কোনটি "ভালো" unittest ব্যবহার বলে বিবেচিত?

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

অনুশীলন

  1. চিন্তা করুন: Calculator ক্লাসে আর কোন কোন মেথড/আচরণ যোগ করলে আরও অর্থবহ টেস্ট লেখা যেত? (হিন্ট: negative number নিয়ে multiply/power-এর মতো মেথড, অথবা float precision নিয়ে কী সমস্যা হতে পারে?)

    যেমন একটি multiply মেথড negative × negative = positive এই নিয়ম যাচাই করতে পারত, অথবা একটি power মেথড 0**0-এর মতো এজ কেস নিয়ে আলোচনার সুযোগ দিত। float precision নিয়ে সমস্যা (যেমন 0.1 + 0.2 != 0.3) L11-এ assertAlmostEqual দিয়ে বিস্তারিত দেখানো হবে।

  2. পরীক্ষা করুন: উপরের কোড সেলে divide মেথডের if b == 0: লাইনটি সাময়িকভাবে মুছে দিয়ে Run চাপুন — test_divide_by_zero_raises_zerodivisionerror টেস্টের কী হয় দেখুন।

    চেকটি মুছে দিলে a / b সরাসরি চালানো হবে, আর Python নিজেই 10 / 0-এর জন্য স্বাভাবিকভাবে ZeroDivisionError ছোঁড়ে — তাই টেস্টটি তখনও পাস করবে (Python-এর নিজস্ব ডিভিশন অপারেটরই এই ব্যতিক্রম ছোঁড়ে)। এটি দেখায় যে টেস্টটি কোডের একটি নির্দিষ্ট লাইনের উপর নয়, বরং চূড়ান্ত আচরণের উপর নির্ভর করছে — যা ভালো টেস্ট ডিজাইনের একটি লক্ষণ।

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

  • পরের পাঠ L11 অ্যাসারশন ও টেস্ট অর্গানাইজেশন — assertIn, assertAlmostEqual-সহ আরও অ্যাসারশন মেথড এবং নামকরণের ভালো অভ্যাস।
  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ টেস্ট ডিজাইন টেকনিক, ইউনিট টেস্টিং, টেস্ট ডাবলস, ইন্টিগ্রেশন টেস্টিং, কভারেজ, অটোমেশন, পারফরম্যান্স ও সিকিউরিটি টেস্টিং, অ্যাডভান্সড টেকনিক ও CI/CD ইন্টিগ্রেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Python Programming কোর্স সহোদর কোর্স ক্লাস, মেথড ও এক্সেপশন হ্যান্ডলিং-এর ভাষাগত ভিত্তি সেই কোর্সেই তৈরি হয়েছে।
আগের পাঠ
ইউজ কেস ও সিনারিও-ভিত্তিক টেস্টিং