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

টেস্ট ফিক্সচার — setUp ও tearDown

Test fixtures — setUp and tearDown
৯ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • setUp() ও tearDown() ঠিক কখন, কতবার চলে
  • টেস্ট ফিক্সচার কেন প্রয়োজন — বারবার একই সেটআপ কোড না লিখে পুনর্ব্যবহারযোগ্য করা
  • টেস্ট আইসোলেশন কী, এবং কেন এটি একটি নির্ভরযোগ্য টেস্ট স্যুটের জন্য অপরিহার্য
  • একটি সত্যিকারের ডেমো — id() প্রিন্ট করে প্রমাণ করা যে প্রতিটি টেস্ট একটি ফ্রেশ অবজেক্ট পায়

১ · setUp() ও tearDown() কী করে

একটি টেস্ট ফিক্সচারTest Fixtureএকটি টেস্টের জন্য প্রয়োজনীয় নির্দিষ্ট, পূর্বনির্ধারিত পরিবেশ/অবস্থা তৈরি ও পরীক্ষা শেষে পরিষ্কার করার ব্যবস্থা — যাতে প্রতিটি টেস্ট একই পরিচিত শুরুর বিন্দু থেকে চলে। একাধিক টেস্ট মেথডে প্রায়ই একই সেটআপ কোড লাগে — যেমন একটি নতুন অবজেক্ট তৈরি করা, একটি ফাইল খোলা, বা একটি ডেটাবেস কানেকশন বানানো। এই পুনরাবৃত্তি এড়াতে unittest.TestCase দুটি বিশেষ মেথড দেয়:

setUp(self)
ক্লাসের প্রতিটি test_... মেথড শুরু হওয়ার ঠিক আগে স্বয়ংক্রিয়ভাবে চলে।
tearDown(self)
প্রতিটি টেস্ট মেথড শেষ হওয়ার পর (এমনকি টেস্টটি ব্যর্থ/এরর হলেও) স্বয়ংক্রিয়ভাবে চলে।

গুরুত্বপূর্ণ বিষয়: এগুলো ক্লাসে একবার চলে না — বরং ক্লাসে যতগুলো টেস্ট মেথড আছে, ঠিক ততবার চলে, প্রতিটি টেস্টের জন্য আলাদাভাবে। এর মানে setUp()-এ তৈরি করা যেকোনো অবজেক্ট প্রতিটি টেস্টের জন্য নতুনভাবে তৈরি হয়।

২ · টেস্ট আইসোলেশন — কেন এটি গুরুত্বপূর্ণ

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

নিচের কোড সেলে একটি সাধারণ Counter ক্লাস আছে — increment() মেথড দিয়ে মান বাড়ানো যায়। setUp() প্রতিটি টেস্টের আগে একটি নতুন Counter তৈরি করে এবং তার id() (Python-এ প্রতিটি অবজেক্টের একটি অনন্য মেমরি-ভিত্তিক পরিচয়) প্রিন্ট করে — যদি প্রতিটি টেস্টে ভিন্ন id() দেখা যায়, তাহলে প্রমাণ হয় সত্যিই একটি নতুন, স্বতন্ত্র অবজেক্ট তৈরি হচ্ছে।

Python
import unittest

class Counter:
    def __init__(self):
        self.value = 0

    def increment(self, by=1):
        self.value += by
        return self.value

class CounterFixtureTests(unittest.TestCase):
    def setUp(self):
        self.counter = Counter()
        print(f"[setUp] নতুন Counter তৈরি — id={id(self.counter)}, শুরুর value={self.counter.value}")

    def tearDown(self):
        print(f"[tearDown] Counter id={id(self.counter)} পরিষ্কার হচ্ছে, শেষ value={self.counter.value}")

    def test_fresh_counter_starts_at_zero(self):
        self.assertEqual(self.counter.value, 0)

    def test_increment_once(self):
        self.assertEqual(self.counter.value, 0)   # আগের টেস্টের কোনো পরিবর্তন এখানে লিক করেনি
        self.counter.increment()
        self.assertEqual(self.counter.value, 1)

    def test_increment_thrice(self):
        self.assertEqual(self.counter.value, 0)   # এখানেও শুরুতে 0 — সম্পূর্ণ ফ্রেশ অবজেক্ট
        self.counter.increment()
        self.counter.increment()
        self.counter.increment()
        self.assertEqual(self.counter.value, 3)

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

    
Run চাপলে আউটপুটে তিনবার [setUp] ... id=... লাইন দেখবেন — প্রতিটির id() মান ভিন্ন, কারণ প্রতিটি টেস্টের জন্য একটি নতুন Counter তৈরি হয়েছে। এ কারণেই test_increment_thrice-এ তিনবার increment() ডাকার পরও test_increment_once-এর উপর কোনো প্রভাব পড়ে না (এবং unittest ডিফল্টভাবে টেস্ট মেথড নাম অনুযায়ী বর্ণানুক্রমিকভাবে চালায়, তাই ক্রম যা-ই হোক, প্রতিটি টেস্ট সবসময় value = 0 দিয়েই শুরু করে) — এটিই সত্যিকারের, প্রমাণিত টেস্ট আইসোলেশন।
মূল কথা · Key takeaway

setUp()/tearDown() প্রতিটি টেস্ট মেথডের আগে/পরে চলে, যাতে প্রতিটি টেস্ট একটি ফ্রেশ, পূর্বনির্ধারিত অবস্থা থেকে শুরু হয় — এটিই টেস্ট আইসোলেশনের ভিত্তি এবং একটি নির্ভরযোগ্য টেস্ট স্যুটের অন্যতম মৌলিক স্তম্ভ। পরের পাঠে (L13) আমরা দেখব কীভাবে একই টেস্ট মেথডের ভেতরে একাধিক ইনপুট-প্রত্যাশা জোড়া subTest দিয়ে পরীক্ষা করা যায়।

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

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

প্র ০১ যদি Counter() অবজেক্টটি setUp()-এর বদলে ক্লাসের বাইরে একবার তৈরি করে সব টেস্টে শেয়ার করা হতো, তাহলে কী সমস্যা হতে পারত?

তাহলে একটি টেস্টের increment() কল পরবর্তী টেস্টেও থেকে যেত — যেমন test_increment_thrice আগে চললে value ইতিমধ্যে 3 হয়ে যেত, আর তারপর test_fresh_counter_starts_at_zero চললে সেটি ব্যর্থ হতো, যদিও কোডে কোনো বাগ নেই। ফলাফল তখন টেস্ট চালানোর ক্রমের উপর নির্ভরশীল হয়ে যেত — এটি একটি অনির্ভরযোগ্য, "flaky" টেস্ট স্যুটের সাধারণ কারণ।

প্র ০২ tearDown() কেন দরকার, যেখানে setUp() এমনিতেই প্রতিটি টেস্টে ফ্রেশ অবজেক্ট দিচ্ছে?

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

প্র ০৩ উপরের কোড সেলে test_increment_once ও test_increment_thrice-এর Counter অবজেক্টের id() মান কি একই হবে, নাকি ভিন্ন? কেন?

ভিন্ন হবে — কারণ setUp() প্রতিটি টেস্ট মেথডের আগে একটি সম্পূর্ণ নতুন Counter() তৈরি করে, আর Python-এ প্রতিটি নতুন অবজেক্টের নিজস্ব, অনন্য মেমরি অবস্থান (এবং তাই ভিন্ন id()) থাকে। Run চাপলে প্রিন্ট হওয়া তিনটি id=... মান তিনটি ভিন্ন সংখ্যা হবে — এটিই সরাসরি প্রমাণ করে যে টেস্টগুলো একটি শেয়ার্ড অবজেক্ট নয়, বরং প্রতিটি নিজস্ব ফ্রেশ অবজেক্ট নিয়ে কাজ করছে।

অনুশীলন

  1. চিন্তা করুন: একটি "bank account" ক্লাসের জন্য setUp()-এ কী কী করা উচিত বলে মনে হয়? (হিন্ট: শুরুর ব্যালেন্স কত হওয়া উচিত পরীক্ষার জন্য?)

    setUp()-এ সাধারণত একটি নির্দিষ্ট, পরিচিত শুরুর ব্যালেন্স (যেমন 1000 টাকা) দিয়ে একটি নতুন অ্যাকাউন্ট অবজেক্ট তৈরি করা হতো — যাতে প্রতিটি টেস্ট (যেমন "withdraw করলে ব্যালেন্স কমে," "overdraft হলে ব্যতিক্রম ছোঁড়ে") একই পরিচিত শুরুর বিন্দু থেকে যাচাই করা যায়, আগের কোনো টেস্টের লেনদেনের প্রভাব ছাড়াই।

  2. পরীক্ষা করুন: উপরের কোড সেলে setUp()-এর ভেতরে self.counter = Counter()-এর বদলে ক্লাসের বাইরে একটি গ্লোবাল shared_counter = Counter() তৈরি করে সব টেস্টে সেটিই ব্যবহার করলে (এবং setUp() সরিয়ে ফেললে) কী হয় Run চেপে দেখুন।

    এখন টেস্টগুলো আর আইসোলেটেড থাকবে না — টেস্ট চালানোর ক্রম অনুযায়ী value-এর শুরুর মান পাল্টে যাবে, আর test_fresh_counter_starts_at_zero-এর মতো টেস্ট (যা value == 0 আশা করে) অন্য টেস্ট আগে চললে ব্যর্থ হতে পারে। এটিই ঠিক সেই সমস্যা যা setUp() প্রতিরোধ করার জন্য ডিজাইন করা হয়েছে।

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

  • পরের পাঠ L13 প্যারামিটারাইজড ও ডেটা-ড্রিভেন ইউনিট টেস্ট — self.subTest() দিয়ে একাধিক ইনপুট-কেস একটিমাত্র টেস্ট মেথডে যাচাই করা।
  • আগের পাঠ L11 অ্যাসারশন ও টেস্ট অর্গানাইজেশন — assertEqual, assertIn, assertAlmostEqual-সহ মূল অ্যাসারশন মেথড।
  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ টেস্ট ডিজাইন টেকনিক, ইউনিট টেস্টিং, টেস্ট ডাবলস, ইন্টিগ্রেশন টেস্টিং, কভারেজ, অটোমেশন, পারফরম্যান্স ও সিকিউরিটি টেস্টিং, অ্যাডভান্সড টেকনিক ও CI/CD ইন্টিগ্রেশন — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
আগের পাঠ
অ্যাসারশন ও টেস্ট অর্গানাইজেশন