टेस्टर्स के लिए कोड पढ़ना

Tester के लिए code पढ़ने का मतलब है यह देखना कि code का एक छोटा हिस्सा किसी एक input के साथ क्या करता है: कौन-सी values store करता है, कौन-से decisions लेता है, कौन-सी lines चलती हैं और आख़िर में क्या वापस आता है। इसके लिए production code लिखना ज़रूरी नहीं। कुछ building blocks ज़्यादातर business logic को cover कर लेते हैं: variables, comparisons, &&, || और ! operators, if और switch, loops, return और throw।

उदाहरण: if (total > 200) { fee = 0; } ऐसे पढ़ा जाता है: "अगर total 200 से ज़्यादा है, तो fee 0 हो जाती है।" अगर requirement कहती है "200 या उससे ज़्यादा के orders पर delivery free", तो code और requirement ठीक एक value, 200, पर अलग हैं। जो tester यह line पढ़ सकता है, वह इसे पकड़ने वाला test design कर सकता है, या कोई test चलाने से पहले ही सवाल पूछ सकता है।

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

Equivalence partitioning और boundary value analysis जैसी black-box techniques requirement से काम करती हैं। वे यह नहीं देख पातीं कि code असल में क्या compare करता है, किस क्रम में decide करता है या कहाँ जल्दी रुक जाता है। Code पढ़ने से वे decisions दिखते हैं जिनसे आपके टेस्ट किए partitions और boundaries बनते हैं, और ऐसा behaviour भी दिखता है जिसका किसी requirement में ज़िक्र नहीं: कोई extra discount, कोई भूला हुआ case, या कोई error जो function को ख़त्म कर देता है।

इससे developers के साथ आपका काम भी बदलता है। आप किसी change को उन rules के लिए review कर सकते हैं जिन्हें वह छूता है, unit test पढ़कर देख सकते हैं कि वह असल में क्या check करता है, और bug report में वह input और line लिख सकते हैं जिस पर आपको शक है। आगे के white-box lessons इसी पर टिके हैं: वे statement, branch और condition coverage को ठीक यहाँ के snippets जैसे code से, उसी तरह line numbers के साथ, समझाते हैं।

Values, variables और operators

Variable किसी value का नाम है। const ऐसा नाम बनाता है जिसे नई value नहीं दी जा सकती; let ऐसा नाम बनाता है जिसे दी जा सकती है। Values के types होते हैं: numbers (200), text strings ('SAVE10' या "SAVE10"), booleans (true और false), और "कुछ नहीं" के दो रूप, undefined (कभी set नहीं हुआ) और null (जानबूझकर खाली)। // के बाद का text comment है: लोगों के लिए एक note, जो कभी नहीं चलता। हर snippet पढ़ें, फिर उसे सीधे शब्दों में कहें:

  • const rate = 0.15; पढ़ें: "rate 0.15 है, और 0.15 ही रहेगा।"
  • let fee = 15; के बाद fee = fee + 10; पढ़ें: "fee 15 से शुरू होती है; मौजूदा fee लो, 10 जोड़ो, 25 store करो।" अकेला = assignment है: यह value बदलता है, कभी compare नहीं करता।
  • const item = { price: 30, qty: 2 }; पढ़ें: "item दो नाम वाली values रखता है, price 30 और qty 2", और item.price पढ़ें: "item की price"। [30, 20] दो values की list (array) है, [] खाली list है, और items.length बताता है कि items में कितनी values हैं। const सिर्फ़ नाम को fix करता है: list या object के अंदर की values फिर भी बदल सकती हैं। Calculation में * गुणा करता है और / भाग देता है: item.price * item.qty पढ़ें "item की price गुणा उसकी qty"।
  • total === 200 पढ़ें: "क्या total ठीक 200 है?", नतीजा true या false; !== का मतलब है "बराबर नहीं"। दोहरा == भी compare करता है, लेकिन पहले types convert करता है, इसलिए '200' == 200 true है जबकि '200' === 200 false है।
  • total >= 200 पढ़ें: "क्या total 200 या उससे ज़्यादा है?"; total > 200 में 200 ख़ुद शामिल नहीं। Boundary defects अक्सर इसी एक character में छिपे होते हैं।
  • member && total >= 100 पढ़ें: "member है और कम से कम 100": सिर्फ़ तब true जब दोनों sides सच हों। express || urgent तब true है जब कम से कम एक side सच हो। !express पढ़ें: "express नहीं"। && पहली false side पर और || पहली true side पर रुक जाता है, इसलिए दूसरी side शायद कभी evaluate ही न हो।
  • a || b && c का मतलब a || (b && c) है, क्योंकि && पहले evaluate होता है और || बाद में। Brackets मतलब बदल देते हैं, इसलिए उन्हें ध्यान से पढ़ें।
  • if (qty) किसी value को condition की तरह इस्तेमाल करता है: 0, खाली string, null, undefined और NaN ("not a number", 0 / 0 या 5 - undefined जैसी calculation का नतीजा) को false माना जाता है। तब quantity 0 को missing quantity जैसा treat किया जाता है। यही नियम qty || 1 में भी छिपा है, जिसे पढ़ें "qty, या 1 अगर qty को false माना जाए": || और && दोनों values में से एक ही लौटाते हैं, सिर्फ़ true या false नहीं, इसलिए quantity 0 चुपचाप 1 बन जाती है।

Decisions: if, else, switch, return और throw

Decision तय करता है कि आगे कौन-सी lines चलेंगी। { और } के बीच की lines एक block बनाती हैं: decision या तो उस block में जाता है या पूरा block छोड़ देता है। Indentation सिर्फ़ आँखों की मदद करता है, JavaScript braces को मानती है: braces के बिना if सिर्फ़ अपने ठीक बाद वाले एक statement को control करता है, अगली lines का indentation चाहे जैसा हो। इसलिए अगर code बिगड़े indentation के साथ या एक लंबी minified line में आए, तो trace करने से पहले उसे फिर से indent करें। if / else if / else chain में conditions ऊपर से check होती हैं; जो पहली condition true हो, उसका block चलता है और बाकी chain छोड़ दी जाती है, भले ही बाद वाली condition भी true होती। इसलिए conditions का क्रम भी logic का हिस्सा है।

  • if (total <= 0) { … } else if (total < 200) { … } else { … } पढ़ें: "zero या कम; वरना 200 से कम; वरना बाकी सब।" ठीक एक block चलता है।
  • switch (coupon) { case 'SAVE10': …; break; case 'HALF': …; break; default: … } पढ़ें: "coupon को हर case से === से compare करो, पहला matching case चलाओ, कोई match न हो तो default।" break के बिना execution अगले case में गिर जाता है (fall-through); अगर यह जानबूझकर न हो, तो यह एक classic defect है।
  • return fee; पढ़ें: "यहीं रुको और fee caller को लौटा दो।" उसी function में इसके बाद कुछ नहीं चलता, इसलिए ऊपर लिखा early return नीचे का सब कुछ छोड़ देता है।
  • throw new Error('Order total must be positive'); पढ़ें: "यहीं error के साथ रुको।" Error उस code तक जाता है जिसने function को call किया था, और वह उसे try / catch से पकड़ सकता है; अगर कोई न पकड़े तो operation रुक जाता है और browser का console uncaught error दिखाता है। Tester के लिए throw एक expected result है जिसे check करना है: message, और user या API client को क्या मिलता है।

आज़माएँ: JavaScript फ़ॉर्मेटर आज़माएँ: डिसीज़न टेबल जनरेटर

Loops, parameters और return values

Function नाम वाला logic है जिसके inputs और एक output होता है। उसके parameters brackets में लिखे नाम हैं, call के arguments वे values हैं जो उसमें भेजी जाती हैं, और return value वह है जो वापस आती है। यह सीधे testing से जुड़ता है: arguments आपके test inputs हैं और return value वह actual result है जिसे आप expected result से compare करते हैं। Coverage lessons TypeScript भी दिखाते हैं, जो नामों के बाद types जोड़ता है: total: number पढ़ें "total एक number है", items: Item[] पढ़ें "items, Item values की list है" (Item एक type है जिसे team ने कहीं और define किया है), और brackets के बाद ): number पढ़ें "number return करता है"। TypeScript इन types को code चलने से पहले check करता है और असल में चलने वाली JavaScript से हटा देता है, इसलिए run time पर call को मिली values, जैसे API response से आई values, check नहीं होतीं।

Loop किसी block को दोहराता है। for loop में एक शुरुआत, हर round से पहले check होने वाली condition और हर round के बाद एक step होता है; while loop तब तक दोहराता है जब तक उसकी condition true है। दोनों शून्य बार भी चल सकते हैं।

  • function deliveryFee(total, express) पढ़ें: "deliveryFee नाम का function, दो inputs के साथ।" Call deliveryFee(200, false) से total 200 और express false हो जाता है; छूटा हुआ argument undefined होता है। Code के अंदर Math.floor(total / 10) जैसा call किसी दूसरे function को चलाता है और उसकी return value इस्तेमाल करता है; built-in functions को MDN पर देखें (Math.floor नीचे की ओर round करता है: Math.floor(12.5) 12 है)।
  • for (let i = 0; i < items.length; i++) { … } पढ़ें: "i को 0 से शुरू करो; जब तक i items की संख्या से कम है, block चलाओ, फिर i में 1 जोड़ो।" List में positions 0 से शुरू होती हैं: items[0] पहला item है, items[i] position i वाला item, और items[items.length - 1] आख़िरी item। 1 से शुरू करने पर पहला item छूट जाता है; <= एक round ज़्यादा चला देता है।
  • for (const item of items) { … } पढ़ें: "items के हर item के लिए, क्रम से, block चलाओ।" इसमें ग़लत set होने वाला कोई counter नहीं है, फिर भी खाली list पर यह शून्य बार ही चलता है।
  • while (attempts < 3) { … } पढ़ें: "जब तक attempts 3 से कम हैं, दोहराओ।" अगर block में कुछ भी attempts को नहीं बदलता, तो loop कभी ख़त्म नहीं होता और page या test अटक जाता है।
  • हर loop को शून्य, एक और कई rounds के साथ trace करें: खाली cart, एक item, कई items।

Worked example: fee function को हाथ से trace करना

Requirement: "Delivery 15.00 SAR की है। 200.00 SAR या उससे ज़्यादा के orders पर delivery free है। Express delivery 10.00 SAR जोड़ती है, free delivery वाले orders पर भी। Zero या उससे कम total reject होता है।" Developer का function नीचे trace steps के बाद दिया है, coverage lessons की तरह line numbers के साथ; steps पढ़ते हुए हर line उसमें देखें।

एक trace table बनाएँ: हर input के लिए एक row, और चली हुई lines, हर decision के outcome और return हुई value के लिए columns। Code में एक-एक line चलें, हर variable की नई value लिखते हुए, और उसके बाद ही return value को requirement से लिए गए expected result से compare करें। हर comparison के दोनों तरफ़ के inputs चुनें:

  • deliveryFee(0, false): line 2 पर 0 <= 0 true है, इसलिए line 3 error throw करती है और lines 5 से 12 कभी नहीं चलतीं। Requirement rejection चाहती है: match करता है।
  • deliveryFee(50, false): तीनों decisions false हैं, fee 15 रहती है और line 12 पर 15 return होता है। Match करता है।
  • deliveryFee(250, true): line 6 true है, इसलिए fee 0 हो जाती है; line 9 true है, इसलिए fee 10 हो जाती है। Return 10, जैसा requirement कहती है।
  • deliveryFee(200, false): 200 > 200 false है, इसलिए line 7 छूट जाती है और function 15 return करता है, जबकि requirement 0 expect करती है। यह input 50 वाला ही path लेता है, और सिर्फ़ boundary value defect दिखाती है। Failure confirm करने के लिए test चलाएँ, फिर input, expected और actual result और line 6 के साथ report करें।
 1  function deliveryFee(total, express) {
 2    if (total <= 0) {
 3      throw new Error('Order total must be positive');
 4    }
 5    let fee = 15;
 6    if (total > 200) {
 7      fee = 0;
 8    }
 9    if (express) {
10      fee = fee + 10;
11    }
12    return fee;
13  }
deliveryFee की चार inputs वाली trace table: 200 वही path लेता है जो 50 लेता है, क्योंकि 200 > 200 false है, इसलिए वह 15 return करता है जबकि requirement free delivery expect करती है।
Call के arguments: total, expressLine 2total <= 0Line 6total > 200Line 9expressचली हुई linesReturn value
0, falsetrueनहीं पहुँचानहीं पहुँचा2, 3Error throw करता हैrequirement: rejectionपास
50, falsefalsefalsefalse2, 5, 6, 9, 1215requirement: 15पास
250, truefalsetruetrue2, 5, 6, 7, 9, 10, 1210requirement: 10पास
200, falsefalsefalsefalse2, 5, 6, 9, 1215requirement: 0फ़ेल
  • Condition true: उसके नीचे का block चलता है
  • Boundary value: 50 वाला ही path, ग़लत result

Dash का मतलब है कि execution उस line तक पहुँचा ही नहीं: पहला call line 3 के throw पर रुक गया।

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

आम गलतियाँ

  • अकेले = को comparison समझना: fee = 0 fee बदलता है; comparison ===, !==, ==, <, <=, > या >= से लिखा जाता है।
  • अभी trace किए code से ही expected result ले लेना। Trace बताता है कि code क्या करता है; उसे क्या करना चाहिए, यह requirement या product owner से आता है।
  • मान लेना कि हर line चलती है: return या throw के बाद function का बाकी हिस्सा छूट जाता है, और if / else if / else chain का सिर्फ़ एक block चलता है।
  • Happy path का एक input trace करके रुक जाना। हर decision को दोनों तरफ़ ले जाएँ, हर comparison के दोनों ओर एक value रखें, और हर loop को शून्य, एक और कई बार चलाएँ।
  • Conditions में brackets और short-circuit को नज़रअंदाज़ करना: a || b && c और (a || b) && c एक नहीं हैं, और पहली side false हो तो && की दूसरी side कभी evaluate नहीं होती।
  • Trace दिमाग़ में रखना। Table लिखें: loops और दोबारा assign होने वाले variables पर ही mental trace ग़लत होता है।

QA का नज़रिया

Team में आप code तब पढ़ते हैं जब कोई change आता है। Pull request में वे conditions, limits और error paths देखें जो वह जोड़ता या बदलता है, और हर एक को test idea या सवाल में बदलें ("line 6 में > 200 है, story कहती है 200 या ज़्यादा: सही कौन-सा है?")। Code को चलाए बिना पढ़ना static testing है; हाथ से trace करना उसका एक तेज़ रूप है, जिसे कभी-कभी dry run या desk check कहते हैं, और यह developer के साथ बैठकर सबसे अच्छा काम करता है।

जो मिले उसे किसी भी दूसरे test की तरह record करें: input, चली हुई lines, requirement से expected result और actual result। Trace एक संभावित defect की ओर इशारा करता है; जहाँ हो सके, test चलाकर उससे होने वाली failure देखें। Bug report में input और जिस line पर शक है उसका नाम developer का समय बचाता है, लेकिन report फिर भी observed failure बताती है, सिर्फ़ "code ग़लत है" नहीं। Fix सुझाना ज़रूरी नहीं; सुझाएँ तो उसे suggestion के रूप में लिखें।

अभ्यास

Cart total function को trace करें

आपका काम

Requirement: "Cart total हर item के price × quantity का जोड़ है। Coupon SAVE10 50.00 SAR या उससे ज़्यादा के totals से 10.00 SAR घटाता है।" नीचे दिए function को उसके नीचे लिखे तीन calls के लिए हाथ से trace करें।

हर call के लिए चली हुई lines, हर decision का outcome, हर step के बाद total की value और return value लिखें। फिर requirement वाले expected result से compare करें और जो defect मिले उसका वर्णन करें।

सामग्री
 1  function cartTotal(items, coupon) {
 2    let total = 0;
 3    for (let i = 1; i < items.length; i++) {
 4      total = total + items[i].price * items[i].qty;
 5    }
 6    if (coupon === 'SAVE10' && total >= 50) {
 7      total = total - 10;
 8    }
 9    return total;
10  }

A  cartTotal([{ price: 30, qty: 2 }], 'SAVE10')
B  cartTotal([{ price: 20, qty: 1 }, { price: 15, qty: 2 }], '')
C  cartTotal([], 'SAVE10')
चरण
  1. Trace table बनाएँ: input, चली हुई lines, decisions, हर step के बाद total, return हुई value।
  2. Calls A, B और C को line-by-line trace करें, loop rounds गिनते हुए।
  3. हर call का expected result सिर्फ़ requirement से निकालें।
  4. Compare करें, जिस line पर शक है उसे पहचानें और एक वाक्य में defect summary लिखें।
मॉडल उत्तर दिखाएँ
मॉडल उत्तर

A: line 2 total को 0 करती है। Line 3 i को 1 से शुरू करती है, और 1 < 1 false है, इसलिए loop body कभी नहीं चलती। Line 6: coupon match करता है, पर 0 >= 50 false है, इसलिए line 7 छूट जाती है। Return 0। Expected: 60 - 10 = 50। Test fail होता है।

B: total 0 से शुरू होता है। Round 1: i 1 है, 1 < 2 true है, total 0 + 15 × 2 = 30 हो जाता है। Round 2: i 2 है, 2 < 2 false है, loop ख़त्म। Line 6: '' === 'SAVE10' false है, इसलिए && वहीं रुक जाता है। Return 30। Expected: 20 + 30 = 50। Test fail होता है।

C: loop शून्य बार चलता है और coupon check false है। Return 0, जैसा expected है। Code ग़लत होने के बावजूद यह call pass होता है: खाली cart defect को छिपा देता है।

Defect: line 3 loop को i = 1 से शुरू करती है, इसलिए पहला item कभी नहीं जुड़ता; शुरुआत i = 0 से होनी चाहिए। Summary: "Cart total पहला item छोड़ देता है: 30.00 × 2 वाला एक item SAVE10 के साथ 50.00 की जगह 0 देता है।" Fix के बाद उसे confirm करने के लिए A, B और C फिर से चलाएँ (confirmation testing)। अब call A पहली बार line 7 तक पहुँचता है, इसलिए coupon को उसकी boundary पर 49.99 और 50.00 वाले totals के साथ भी टेस्ट करें।

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

क्या इस तरह code पढ़ने के लिए programming सीखनी होगी?

नहीं। Logic trace करने के लिए values, comparisons, decisions, loops, return और throw पहचानना काफ़ी है, और यही यह lesson सिखाता है। अपना code लिखना बाद में test automation lessons में आता है। छोटे functions और उन pull requests से शुरू करें जिन पर आपकी team पहले से बात करती है।

अगर मेरी team JavaScript इस्तेमाल नहीं करती तो?

दूसरी languages में भी building blocks वही हैं। Java और C# वही &&, || और !, curly braces और for loops इस्तेमाल करती हैं। Python and, or और not लिखती है, blocks को braces की जगह indentation से दिखाती है, और उसका for loop list के items पर सीधे चलता है। Trace table इन सब पर काम करती है।

== और === में क्या फ़र्क है?

=== (strict equality) तभी true है जब type और value दोनों बराबर हों। == (loose equality) पहले types convert करता है, इसलिए '5' == 5 और 0 == '' दोनों true हैं। इसी वजह से कई teams === अनिवार्य रखती हैं; user input वाले comparison में == दिखे, तो text के रूप में लिखे number और खाली value से टेस्ट करें।

Tester को पढ़ने के लिए code कहाँ मिलता है?

जिस story को आप टेस्ट कर रहे हैं उसके pull request या merge request में, code के पास रखे unit tests में, coverage report में, जो source को line-by-line दिखाती है, और web pages के लिए browser developer tools में, जहाँ Sources या Debugger panel page की loaded scripts दिखाता है। Repository का read access माँगें; कई teams testers को यह देती हैं।

संदर्भ

  • ECMAScript® Language Specification (ECMA-262)Ecma International / TC39JavaScript भाषा का मानक, जिसमें RegExp का व्याकरण और अर्थ, JSON.parse/JSON.stringify और Date ऑब्जेक्ट शामिल हैं।
  • JavaScript — MDNMDN Web Docs (Mozilla)MDN की JavaScript गाइड और reference: भाषा का सिंटैक्स, types, objects और built-ins, जैसे ब्राउज़र उन्हें लागू करते हैं।
  • ISTQB Certified Tester Foundation Level (CTFL) v4.0ISTQB · 2023सॉफ़्टवेयर टेस्टिंग का फ़ाउंडेशन पाठ्यक्रम: टेस्ट स्तर और प्रकार, टेस्ट डिज़ाइन तकनीकें, टेस्ट प्रबंधन और टूल।
  • ISTQB GlossaryISTQBटेस्टिंग शब्दों की मानक परिभाषाएँ — टेस्ट केस, रिग्रेशन टेस्टिंग, सेवेरिटी, प्रायोरिटी, बाउंड्री वैल्यू एनालिसिस आदि।

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

JavaScript और TypeScript कोड को एकसमान इंडेंटेशन और स्पेसिंग के साथ सुंदर बनाएँ।

फ़ॉर्मेटिंग

conditions और actions से decision table बनाएँ, सभी rule संयोजन जनरेट करें और असंभव संयोजनों को समेट दें।

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

दो टेक्स्ट की लाइन-दर-लाइन या शब्द-दर-शब्द तुलना करें और अंतर हाइलाइट करें।

फ़ॉर्मेटिंग

और गहराई से

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

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