कंपोनेंट (यूनिट) टेस्टिंग

Component testing, जिसे ISTQB unit testing भी कहता है, वह test level है जो software के सबसे छोटे, अलग से टेस्ट किए जा सकने वाले हिस्सों को अकेले-अकेले जाँचता है: एक function, एक class, एक module या एक अकेला UI component। यहाँ tests ख़ुद code होते हैं जो component को चुने हुए inputs के साथ सीधे call करते हैं और उसके लौटाए result की तुलना expected result से करते हैं; आम तौर पर ये JUnit, pytest, Jest या Vitest जैसे unit test framework में लिखे जाते हैं।

उदाहरण: 200.00 SAR की basket पर 10% coupon लगाने वाले function को 180.00 लौटाना चाहिए। Component test function को ठीक इन्हीं values के साथ call करता है और जवाब जाँचता है, बिना browser खोले, पूरी application चलाए या असली coupon service से संपर्क किए। ऐसे tests को और कुछ नहीं चाहिए, इसलिए हर एक आम तौर पर milliseconds में चलता है और team इन सबको हर बदलाव पर चला सकती है।

यह क्यों ज़रूरी है

कई defects, जैसे गलत formula, छूटी हुई condition या handle न की गई खाली value, किसी एक component के अंदर ही होते हैं। जब component test ऐसा defect पकड़ता है, तो उसकी जगह तुरंत पता चल जाती है: fail होने वाला test function और input का नाम बता देता है। वही defect बाद में user interface से मिले, तो किसी को उसकी जगह पता चलने से पहले bug report, reproduction और कई layers में जाँच की ज़रूरत पड़ती है।

Tester के लिए component tests team की test strategy का पहला safety net हैं। ये क्या cover करते हैं, यह जानकर आप system और acceptance tests को उन चीज़ों पर लगा सकते हैं जिन्हें सिर्फ़ वही levels पकड़ सकते हैं, बजाय उन checks को दोहराने के जो हर commit पर पहले से चलते हैं। ये क्या cover नहीं कर सकते, यानी stubs और mocks के पीछे छिपा behaviour, यह जानकर पता चलता है कि integration risk कहाँ बाकी है।

Component test क्या है और उसे कौन लिखता है

ISTQB Foundation syllabus v4.0 के अनुसार component testing का focus components को isolation में टेस्ट करने पर होता है। इसके लिए अक्सर ख़ास support चाहिए, जैसे test harness या unit test framework, और इसे आम तौर पर developers अपने development environment में करते हैं। व्यवहार में यहाँ test basis वह material होता है जो code के सबसे क़रीब है: component का detailed design या specification, ख़ुद code और data model।

Component किसे माना जाए, यह code पर निर्भर है। Back end में यह आम तौर पर एक function या class होता है; front end में यह date picker जैसा एक अकेला UI component हो सकता है। Teams शब्दों का इस्तेमाल भी अलग-अलग करती हैं: कुछ "component test" किसी बड़ी चीज़ के लिए कहती हैं, जैसे एक पूरी service जिसके आस-पास की हर चीज़ बदल दी गई हो, इसलिए numbers की तुलना करने से पहले पूछें कि आपकी team का मतलब क्या है।

ज़्यादातर unit tests की बनावट तीन हिस्सों वाली होती है, जिसे अक्सर Arrange, Act, Assert कहा जाता है: inputs और dependencies तैयार करें, component को एक बार call करें, और result की तुलना expected value से करें। अच्छा unit test तेज़ होता है, एक behaviour जाँचता है, हर machine और हर run पर एक ही result देता है, और उसका नाम expected behaviour बताता है।

Isolation और test doubles: stubs, mocks और बाकी

असली components दूसरी चीज़ों पर निर्भर होते हैं: database, payment provider, आज की तारीख़, किसी दूसरी team की service। किसी एक component को isolation में टेस्ट करने के लिए developer इन dependencies को test doubles से बदल देता है। यह Gerard Meszaros का सामान्य term है, जिसका नाम फ़िल्मों के stunt double से आया है। Isolation test को तेज़ और repeatable बनाता है, और उन हालात को टेस्ट करने लायक बनाता है जिन्हें असली dependency के साथ पैदा करना मुश्किल है, जैसे timeout या expired coupon।

ISTQB glossary में दो पुराने terms हैं: stub उस component की जगह लेता है जिसे component under test call करता है, और driver component under test को उसके असली caller की जगह call करता है। Drivers और stubs मिलकर test harness बनाते हैं। नीचे के बारीक अंतर Meszaros से आते हैं, जैसा Martin Fowler ने समझाया है:

  • Dummy: सिर्फ़ parameter list भरने के लिए pass किया जाता है; असल में कभी इस्तेमाल नहीं होता।
  • Stub: test के दौरान होने वाली calls के तैयार जवाब देता है, जैसे "SAVE10 एक 10% coupon है"। फिर test component का result जाँचता है (state verification)।
  • Spy: ऐसा stub जो यह भी record करता है कि उसे कैसे call किया गया, ताकि test बाद में calls जाँच सके।
  • Mock: उन calls के साथ पहले से programmed होता है जिनकी वह उम्मीद करता है, और वे न हों तो test fail होता है, जैसे "ठीक एक confirmation e-mail भेजो" (behaviour verification)।
  • Fake: काम करने वाला लेकिन सरल किया गया implementation, जैसे in-memory database।

आज़माएँ: Mock API रिस्पॉन्स जनरेटर आज़माएँ: Mock एरर रिस्पॉन्स जनरेटर

Worked example: stubbed service के साथ coupon component

Requirement: "Valid coupon basket के subtotal से अपना percentage घटाता है, coupon की maximum discount तक। Unknown या expired coupon subtotal को नहीं बदलता और invalid के रूप में report होता है।" Component applyCoupon code को couponApi के ज़रिए lookup करता है, जो production में एक remote coupon service को call करता है। नीचे का code पढ़ने के लिए JavaScript जानना ज़रूरी नहीं: await जवाब का इंतज़ार करता है, test(...) नाम वाला एक test define करता है, और expect(actual).toEqual(expected) actual result की तुलना expected result से करता है।

Tests में couponApi एक stub है जो सिर्फ़ एक coupon जानता है, SAVE10: 10% छूट, अधिकतम 50.00 SAR। अब results न असली service के चालू होने पर निर्भर हैं, न shared database में मौजूद coupons पर, न आज की तारीख़ पर। Developer हर rule के लिए एक test लिखता है, हर एक में requirement से निकाली गई expected value के साथ:

  • SAVE10 के साथ 200.00 → total 180.00 (10% यानी 20.00, cap से कम)।
  • SAVE10 के साथ 1000.00 → total 950.00 (10% यानी 100.00 होता, इसलिए 50.00 की cap लागू होती है)।
  • ऐसा stub जो coupon को expired बताकर लौटाता है → total 200.00, error COUPON_INVALID।
  • ऐसा stub जिसे code FAKE99 के लिए कुछ नहीं मिलता → total 200.00, error COUPON_INVALID।
  • ऐसा stub जो "service unavailable" error फेंकता है → error applyCoupon से बाहर निकल जाता है। Requirement यह नहीं बताती कि तब checkout को क्या करना चाहिए, इसलिए tester यह सवाल product owner के पास ले जाता है। Double ने इस path को seconds में टेस्ट करने लायक बना दिया; असली service के साथ इसे जानबूझकर पैदा करना मुश्किल है।
// Component under test
async function applyCoupon(code, subtotal, couponApi) {
  const coupon = await couponApi.lookup(code);
  if (!coupon || coupon.expired) return { total: subtotal, error: 'COUPON_INVALID' };
  const discount = Math.min((subtotal * coupon.percent) / 100, coupon.maxDiscount);
  return { total: subtotal - discount, error: null };
}

// Arrange: a stub gives a canned answer instead of the remote coupon service
const couponApi = {
  lookup: async (code) =>
    code === 'SAVE10' ? { percent: 10, maxDiscount: 50, expired: false } : null,
};

test('SAVE10 takes 10% off a 200.00 basket', async () => {
  const result = await applyCoupon('SAVE10', 200, couponApi);  // Act
  expect(result).toEqual({ total: 180, error: null });          // Assert
});
Stubbed coupon service के साथ applyCoupon का unit test: तैयार 10% coupon से जाँचा हुआ total 180.00 मिलता है, और error फेंकने वाला stub ऐसा failure path दिखाता है जिसे requirement ने कभी तय नहीं किया।
  • Stub एक तय coupon लौटाता है

    • यूनिट टेस्ट
    • applyCoupon टेस्ट हो रहा component
    • couponApi का stub
    1. 1 यूनिट टेस्ट आंतरिक रूप से: Arrange: SAVE10 जानने वाला stub
    2. 2 यूनिट टेस्ट से applyCoupon तक: Act: component को एक बार call करें applyCoupon('SAVE10', 200, couponApi)
    3. 3 applyCoupon से couponApi का stub तक: Code lookup करता है lookup('SAVE10')
    4. 4 couponApi का stub से applyCoupon तक: तैयार जवाब, कोई network call नहीं जवाब { percent: 10, maxDiscount: 50 }
    5. 5 applyCoupon से यूनिट टेस्ट तक: Total निकालकर लौटाता है जवाब 200.00 का 10% यानी 20.00, जो 50.00 की cap से कम है { total: 180, error: null }
    6. 6 यूनिट टेस्ट आंतरिक रूप से: Assert: अपेक्षित 180.00 के बराबर पास

    परिणाम: Milliseconds में जाँच हुई; असली coupon service को कभी call नहीं किया गया। पास

  • Stub service outage की नकल करता है

    • यूनिट टेस्ट
    • applyCoupon टेस्ट हो रहा component
    • couponApi का stub
    1. 1 यूनिट टेस्ट आंतरिक रूप से: Arrange: error फेंकने वाला stub
    2. 2 यूनिट टेस्ट से applyCoupon तक: Act: component को एक बार call करें applyCoupon('SAVE10', 200, couponApi)
    3. 3 applyCoupon से couponApi का stub तक: Code lookup करता है lookup('SAVE10')
    4. 4 couponApi का stub से applyCoupon तक: Error फेंकता है जवाब Error('service unavailable')
    5. 5 applyCoupon से यूनिट टेस्ट तक: Error बाहर निकल जाता है: न total, न error code जवाब
    6. 6 यूनिट टेस्ट आंतरिक रूप से: Assert: requirement कोई अपेक्षित परिणाम तय नहीं करती सावधानी

    परिणाम: Product owner के लिए खुला सवाल: coupon service बंद हो तो checkout को क्या करना चाहिए? सावधानी

Test-driven development: पहले test

ISTQB syllabus test-driven development (TDD) को, acceptance test-driven development (ATDD) और behaviour-driven development (BDD) के साथ, उन approaches में गिनता है जिनमें tests development को दिशा देते हैं। TDD में developer छोटे cycles में काम करता है: behaviour के अगले छोटे हिस्से के लिए component test लिखता है और उसे fail होते देखता है, उसे pass कराने लायक बस उतना code लिखता है, फिर सारे tests green रखते हुए code की structure सुधारता है। Practitioners इस cycle को red, green, refactor कहते हैं।

Coupon example में developer पहले 180.00 वाला test एक ऐसे function के लिए लिखेगा जो अभी मौजूद ही नहीं, फिर cap वाला test, फिर expired coupon वाला, और हर test को pass कराने लायक ही code जोड़ेगा। TDD पीछे तेज़ regression tests का एक suite छोड़ता है और design को छोटे, आसानी से isolate होने वाले components की ओर ले जाता है। यह development का एक तरीका है जो early testing लागू करता है; यह दूसरे test levels की जगह नहीं लेता, क्योंकि developer के tests requirement की सिर्फ़ developer की अपनी समझ को जाँच सकते हैं।

Tester को क्या जानना और review करना चाहिए

Unit tests शायद ही कभी tester के ज़िम्मे होते हैं, लेकिन उन्हें पढ़ना team के सबसे सस्ते reviews में से एक है। अच्छे सवाल पूछने के लिए ख़ुद code लिखना ज़रूरी नहीं; tests के नाम और inputs आम तौर पर बता देते हैं कि क्या जाँचा गया। इन चीज़ों को देखें:

  • Rules: हर acceptance criterion को किसी test से जोड़ें। जिस rule का कोई test नहीं, जैसे कोई cap या default value, वह इस level पर एक gap है।
  • Inputs: खाली values, शून्य, requirement में लिखी exact limit और invalid formats, सिर्फ़ एक typical value नहीं।
  • Assertions: हर test result की तुलना requirement से निकाली गई value से करता है, code के output से copy की गई value से नहीं, और सिर्फ़ "कोई error नहीं आया" से भी नहीं।
  • Doubles: stubs वही लौटाते हैं जो असली dependency सच में लौटाती है, उसके errors और खाली results समेत। बाद में integration tests और contract tests (जो जाँचते हैं कि असली service अब भी वैसे ही जवाब देती है जैसा doubles मानते हैं) पुष्टि करते हैं कि दोनों मेल खाते हैं।
  • Independence: run order, असली clock, random values या shared data पर कोई निर्भरता नहीं। जो test code बदले बिना कभी pass और कभी fail हो, वह flaky है।
  • Results: जानें कि हर build की unit test report कहाँ है; जिस build के unit tests fail हुए या skip किए गए, वह system testing के लिए तैयार नहीं है।

आज़माएँ: Requirements → टेस्ट केस आज़माएँ: बाउंड्री वैल्यू जनरेटर

आम गलतियाँ

  • Unit tests को developers का निजी मामला मानना और उन्हें कभी न पढ़ना, जिससे team एक ही rules दो बार टेस्ट करती है और वे rules छूट जाते हैं जिन्हें किसी ने टेस्ट नहीं किया।
  • यह मान लेना कि green unit tests का मतलब है feature काम करता है। Doubles असली dependencies की जगह लेते हैं, इसलिए असली service, database या दूसरी team की API के बारे में गलत धारणाएँ सिर्फ़ integration level पर दिखती हैं।
  • Developer के लिखे हर test को unit test कहना। जो test असली database या network से बात करता है उसे आम तौर पर integration test माना जाता है: धीमा, और component से बाहर की वजहों से fail हो सकता है।
  • ऐसे stubs जो सिर्फ़ happy path लौटाते हैं, या ऐसे fields लौटाते हैं जो असली service कभी नहीं भेजती; tests pass होते हैं और production में failure होता है।
  • Over-mocking: हर internal call जाँचना, जिससे tests हर refactoring पर टूटते हैं जबकि असली defects कम पकड़ते हैं।
  • Expected values requirement से निकालने की बजाय code ने पहली बार जो लौटाया उससे copy कर लेना; तब test defect की ही रक्षा करता है।

QA का नज़रिया

Component testing को बातचीत में जल्दी लाएँ। Refinement में, जब किसी story में calculation या input rules हों, सुझाएँ कि उनमें से कौन-से unit tests में जाँचे जाएँ, और developer को अपनी test conditions दें: limits, खाली और invalid inputs, dependencies के error responses। Code review या pairing में tests के नाम acceptance criteria से मिलाकर पढ़ें, और पूछें कि जिस dependency की जगह stub लगा है, वह fail हो तो हर stub क्या लौटाता है।

जो component level पहले से cover करता है उसे record करें, जैसे story के test notes में या traceability matrix (वह table जो हर requirement को उसके tests से जोड़ती है) में, ताकि system tests हर rule को UI से दोहराने की बजाय flows, integration और user को दिखने वाले behaviour पर ध्यान दें। वह behaviour भी लिखें जो सिर्फ़ doubles के सामने टेस्ट होता है: contract tests और integration tests के लिए सबसे पहला उम्मीदवार वही है; integration testing वाला lesson इसके बाद आता है।

अभ्यास

Shipping-fee component के unit tests review करें

आपका काम

एक developer ने नीचे वाला component और उसके तीन unit tests लिखे हैं। तीनों pass होते हैं। इन्हें tester की नज़र से review करें: तय करें कि ये किस तरह का test double इस्तेमाल करते हैं, जाँचें कि requirement R-12 के कौन-से हिस्से टेस्ट होते हैं, और पता करें कि क्या छूट गया है।

छूटे हुए tests को input, stub के behaviour और expected result के रूप में लिखें। Code लिखने की ज़रूरत नहीं है।

सामग्री
// Requirement R-12
// 300.00 SAR या उससे ज़्यादा के orders की shipping free है। बाकी orders वह fee
// देते हैं जो rate service delivery city के लिए लौटाती है। अगर rate service
// fail हो, तो default fee 25.00 SAR लगाएँ।

async function shippingFee(total, city, rateService) {
  if (total > 300) return 0;
  try {
    return await rateService.feeFor(city);
  } catch {
    return 25;
  }
}

// Unit tests: all three pass
const rates = { feeFor: async (city) => 18 };

test('free shipping for large orders', async () => {
  expect(await shippingFee(500, 'Riyadh', rates)).toBe(0);
});

test('small order pays the city fee', async () => {
  expect(await shippingFee(120, 'Riyadh', rates)).toBe(18);
});

test('fee is a number', async () => {
  const fee = await shippingFee(120, 'Jeddah', rates);
  expect(typeof fee).toBe('number');
});
चरण
  1. Tests जो test double इस्तेमाल करते हैं उसका नाम लिखें और बताएँ कि वह किस प्रकार का है।
  2. R-12 के हर वाक्य को उस test से जोड़ें जो उसे जाँचता है।
  3. हर test के लिए पूछें: अगर code गलत होता, तो क्या यह fail होता?
  4. छूटे हुए tests लिखें, और developer या product owner के लिए एक सवाल।
मॉडल उत्तर दिखाएँ
मॉडल उत्तर

rates एक stub है: यह हर call का एक ही तैयार जवाब, 18, देता है और कुछ record नहीं करता। Tests लौटाई गई value जाँचते हैं, यानी यह state verification है।

Rule 1 (300.00 या उससे ज़्यादा पर free shipping) सिर्फ़ 500 से टेस्ट होता है, जो limit से बहुत दूर है। Rule 2 (city की fee) दूसरा test जाँचता है। Rule 3 (service fail होने पर default fee) का कोई test है ही नहीं।

छूटे हुए tests: total ठीक 300.00 → fee 0। यह test मौजूदा code पर fail होता है, क्योंकि total > 300 300.00 पर fee लगाता है: app खोले जाने से पहले ही मिला defect। साथ ही 299.99 → 18, और ऐसा stub जिसका feeFor error फेंके → fee 25।

तीसरा test सिर्फ़ यह जाँचता है कि fee एक number है, इसलिए किसी भी गलत fee के साथ pass होगा; इसकी जगह exact expected value रखें। Stub हर city के लिए 18 लौटाता है, इसलिए गलत city pass होने पर किसी को पता नहीं चलेगा: हर city के लिए अलग fee वाला stub (Riyadh 18, Jeddah 22) या मिली हुई city record करने वाला spy इस्तेमाल करें।

सवाल: rate service का fail होना किसे माना जाए: error, timeout, या unknown city? क्या fee SAR में और सादा number है, जैसा stub मानता है? कौन-सा integration या contract test यह जाँचता है?

अक्सर पूछे जाने वाले सवाल

क्या component testing और unit testing एक ही हैं?

ISTQB की terminology में हाँ: component testing को unit testing भी कहा जाता है। लेकिन कुछ teams "component test" किसी बड़ी चीज़ के लिए कहती हैं, जैसे अपने child components के साथ render किया गया UI component, या पूरी service जिसके आस-पास की हर चीज़ test doubles से बदली गई हो। पूछें कि आपकी team का मतलब क्या है।

क्या testers unit tests लिखते हैं?

आम तौर पर developers इन्हें लिखते और maintain करते हैं; ISTQB component testing को आम तौर पर developers द्वारा किया जाने वाला काम बताता है। Testers test conditions देते हैं, tests और उनके results review करते हैं, और कुछ teams में developers के साथ pairing करते हैं या ख़ुद tests लिखते हैं। उनका सबसे बड़ा योगदान independent नज़रिया है: developer वही टेस्ट करता है जो उसे लगता है कि code को करना चाहिए।

Mock और stub में क्या अंतर है?

Stub तैयार जवाब देता है ताकि component चल सके, और test component का result जाँचता है। Mock में उन calls की उम्मीदें होती हैं जो उसे मिलनी चाहिए, और calls गलत हों या न हों तो test fail होता है। कई mocking libraries हर double को mock कहती हैं, इसलिए देखें कि double असल में क्या करता है।

अगर developers TDD इस्तेमाल करते हैं, तो क्या team को अब भी testers चाहिए?

हाँ। TDD tests code के छोटे हिस्सों को developer की समझ के हिसाब से जाँचते हैं। वे यह नहीं जाँचते कि components साथ में कैसे काम करते हैं, पूरा system अपनी requirements पूरी करता है या नहीं, या requirement ख़ुद सही है या नहीं। ये सवाल integration, system और acceptance testing के हैं।

संदर्भ

  • ISTQB Certified Tester Foundation Level (CTFL) v4.0ISTQB · 2023सॉफ़्टवेयर टेस्टिंग का फ़ाउंडेशन पाठ्यक्रम: टेस्ट स्तर और प्रकार, टेस्ट डिज़ाइन तकनीकें, टेस्ट प्रबंधन और टूल।
  • ISTQB GlossaryISTQBटेस्टिंग शब्दों की मानक परिभाषाएँ — टेस्ट केस, रिग्रेशन टेस्टिंग, सेवेरिटी, प्रायोरिटी, बाउंड्री वैल्यू एनालिसिस आदि।
  • Test Double (Martin Fowler)martinfowler.com · 2006test doubles के लिए Gerard Meszaros की शब्दावली — dummy, fake, stub, spy और mock — जैसा Martin Fowler ने सारांशित किया।
  • Mocks Aren't Stubs (Martin Fowler)martinfowler.com · 2007state verification और behavior verification का अंतर, और test-driven development की classical और mockist शैलियों का अंतर।

Mutqan tools से अभ्यास करें

प्लेसहोल्डर वाले टेम्पलेट से mock JSON बनाएँ ({{name}}, {{uuid}}, {{int:1-10}}, {{date}}, {{pick:a|b}})।

मॉकिंग

क्रमांकित आवश्यकताओं की सूची को requirement-to-test मैट्रिक्स के साथ ट्रेस करने योग्य टेस्ट केस में बदलें।

टेस्ट डिज़ाइन

और गहराई से

इस topic में आपकी प्रगति

QA रोडमैप का हिस्सा