टेस्ट आइसोलेशन और पैरलल एक्ज़ीक्यूशन
Test isolation का मतलब है कि test अकेले चले, किसी भी क्रम में चले, कई बार चले या दूसरे tests के साथ एक ही पल में चले, उसका result एक जैसा रहे, क्योंकि वह अपनी ज़रूरत की state खुद बनाता है और किसी दूसरे test की छोड़ी हुई चीज़ पर निर्भर नहीं होता। Parallel execution का मतलब है कि test runner एक साथ कई tests चलाता है: एक मशीन पर अलग-अलग workers में, या कई मशीनों पर बँटे shards में। जो checkout suite एक worker के साथ 24 मिनट लेता है, वह चार workers के साथ लगभग उसके एक-चौथाई समय में पूरा हो सकता है, बशर्ते कोई दो tests एक ही पल में एक ही account, cart या file इस्तेमाल न करें।
Isolation ही parallelism को सुरक्षित बनाता है। Test data, fixtures और hooks वाले lesson ने दिखाया कि एक test अपना data कैसे set up और clean up करता है; यह lesson पूरे suite को देखता है: tests अनजाने में क्या share करते हैं, runners tests को workers और मशीनों में कैसे बाँटते हैं, और parallel run से उजागर होने वाली dependencies को कैसे ठीक करें।
यह क्यों ज़रूरी है
एक-दूसरे पर निर्भर tests ऐसे results देते हैं जिन पर कोई भरोसा नहीं कर सकता। कोई test पूरे run में सिर्फ़ इसलिए pass होता है क्योंकि पहले वाले test ने वह order बनाया जिसे वह पढ़ता है, और जब कोई उसे अकेले चलाता है या worker बढ़ाता है तो वह fail हो जाता है; दो tests एक ही पल में एक ही user को edit करते हैं और उनमें से एक हर बीस runs में एक बार fail होता है। ऐसे failures product defects जैसे दिखते हैं, जाँच में घंटों खा जाते हैं और team को लाल builds पढ़ने की बजाय दोबारा चलाना सिखा देते हैं। Martin Fowler isolation की कमी को non-deterministic tests का एक आम कारण बताते हैं।
Speed दूसरा पहलू है। एक घंटे वाला suite कम बार चलता है और तब report करता है जब developer किसी और काम पर जा चुका होता है। Parallel execution बढ़ते suite को इतना तेज़ रखता है कि वह हर change पर चल सके, और यह तभी काम करता है जब tests isolated हों। Isolation bug report में भी मदद करता है: जो test अकेले, हर बार, अपने data के साथ fail होता है, वह developer के लिए तैयार reproduction है।
Runners tests को parallel में कैसे चलाते हैं
Playwright Test, Jest और pytest-xdist में worker operating system का एक अलग process है जो tests को एक के बाद एक चलाता है। चार workers हों तो एक ही पल में चार tests चलते हैं। उनकी memory अलग होती है, लेकिन test environment, उसका database, disk पर files और network ports अलग नहीं होते। Runners इसमें अलग हैं कि वे default रूप से क्या बाँटते हैं, और हर worker को उसके अपने resources कैसे दिए जाते हैं:
- Playwright Test test files को parallel में चलाता है; एक file के अंदर के tests उसी worker में क्रम से चलते हैं, जब तक आप
fullyParallel: trueयाtest.describe.configure({ mode: 'parallel' })set न करें। संख्या config मेंworkersसे या--workersसे तय करें। हर test को नया browser context मिलता है, यानी fresh private window जैसा isolated browser profile, जिसकी अपनी cookies और storage होती हैं, और किसी failure के बाद Playwright worker को नए process से बदल देता है। - pytest को pytest-xdist plugin चाहिए:
pytest -n 4, या हर CPU पर एक worker के लिए-n auto।worker_idfixturegw0,gw1आदि लौटाता है। - Jest test files को worker processes में बाँटता है और एक file के tests क्रम से चलाता है;
--runInBandसब कुछ एक process में serially चलाता है। - JUnit 5 default रूप से tests को एक thread में क्रम से चलाता है। Parallel execution opt-in है: configuration parameter
junit.jupiter.execution.parallel.enabled=trueइसे चालू करता है, लेकिन tests तब भी एक के बाद एक चलते हैं जब तक आप उन्हें concurrent न बनाएँ, जैसेjunit.jupiter.execution.parallel.mode.default=concurrentसे। Parallel tests एक ही JVM के threads होते हैं, इसलिए वे static fields और singletons share करते हैं।@ResourceLockबताता है कि tests ऐसा shared resource इस्तेमाल करते हैं जिसे synchronised access चाहिए। - Per-worker resources: जो चीज़ हर test के लिए नहीं बन सकती, वह हर worker को अलग दें। Playwright का
testInfo.parallelIndex0 से workers − 1 तक जाता है, इसलिए worker 2 run से पहले तैयार pool से account 2 ले सकता है। - Serial अपवाद: जो test कोई global चीज़ बदलता है, जैसे VAT rate या site-wide feature flag, वह दूसरे tests के साथ नहीं चल सकता। पूरे suite को serial बनाने की बजाय उसे
--workers=1वाले छोटे अलग run में ले जाएँ, और उसे ऐसे schedule करें कि वह उसी environment पर main run के साथ-साथ कभी न चले।
Worked example: तीन tests जो सिर्फ़ parallel में fail होते हैं
120 browser tests वाला एक checkout suite एक worker के साथ 24 मिनट लेता है। Team fullyParallel: true और चार workers set करती है; run 7 मिनट का हो जाता है, लेकिन अब तीन tests कुछ runs में fail होते हैं। पूरा suite --workers=1 के साथ अब भी हर बार pass होता है, इसलिए संभावित कारण tests के बीच टकराव है, product नहीं।
हर test qa.shared@example.com से sign in करता है। "Cart total" 150.00 और 80.00 के items जोड़कर 230.00 की उम्मीद करता है, लेकिन कभी-कभी 0.00 देखता है, क्योंकि दूसरे worker पर चल रहा "empty cart" वही cart खाली कर देता है। "Order history" उम्मीद करता है कि list में तीन orders दिखें, वही जो उसकी file के तीन ordering tests बनाते हैं; यह सिर्फ़ इसलिए pass होता था कि यह उसी worker में उनके बाद चलता था, और अकेले चलाने पर भी fail होता है। "Download invoice" downloads/invoice.pdf में save करता है, जहाँ किसी दूसरे worker की invoice उसे overwrite कर सकती है।
एक shared account
- Worker 1 test "cart total"
- Test environment सभी tests के लिए एक cart
- Worker 2 test "empty cart"
- 1 Worker 1 से Test environment तक: 150.00 और 80.00 के items जोड़ें
qa.shared@example.com - 2 Worker 2 से Test environment तक: उसी पल cart खाली करें Account वही, इसलिए cart भी वही
qa.shared@example.com - 3 Worker 2 आंतरिक रूप से: Assert: cart खाली है पास
- 4 Worker 1 से Test environment तक: Cart खोलकर total पढ़ें
- 5 Test environment से Worker 1 तक: Total 0.00 दिखाता है जवाब
- 6 Worker 1 आंतरिक रूप से: Assert: total 230.00 है फ़ेल
परिणाम: सिर्फ़ तब fail होता है जब दोनों tests समय में टकराते हैं; अकेले और एक worker के साथ हर एक pass होता है। फ़ेल
हर worker का अपना नया account
- Worker 1 test "cart total"
- Test environment हर worker का अलग account और cart
- Worker 2 test "empty cart"
- 1 Worker 1 से Test environment तक: 150.00 और 80.00 के items जोड़ें
qa+3f9a…@example.com - 2 Worker 2 से Test environment तक: उसी पल अपना cart खाली करें अलग account, इसलिए अलग cart
qa+c41e…@example.com - 3 Worker 2 आंतरिक रूप से: Assert: cart खाली है पास
- 4 Worker 1 से Test environment तक: Cart खोलकर total पढ़ें
- 5 Test environment से Worker 1 तक: Total 230.00 दिखाता है जवाब
- 6 Worker 1 आंतरिक रूप से: Assert: total 230.00 है पास
परिणाम: Timing वही, carts अलग: दोनों tests pass होते हैं, उनके steps चाहे जैसे भी आपस में मिलें। पास
- हर worker worker-scoped fixture में अपना नया account बनाता है, email में UUID के साथ, ताकि shards भी न टकराएँ।
- एक worker के tests उस account को फिर भी एक के बाद एक share करते हैं, इसलिए
beforeEachhook हर test से पहले cart खाली करता है, पिछले test पर भरोसा करने की बजाय। - "Order history" API से अपने तीन orders बनाता है और उनकी ids पर assert करता है, कुल गिनती पर नहीं।
- Invoice
test.info().outputPath()में save होती है, जो हर test का अपना folder है। - Fix की पुष्टि चार workers पर
--repeat-each=10से होती है, क्योंकि एक green run बहुत कम साबित करता है।
// fixtures.ts: one fresh account per worker process
import { test as base } from '@playwright/test';
import { randomUUID } from 'node:crypto';
type Account = { id: string; email: string };
export const test = base.extend<{}, { account: Account }>({
account: [async ({ playwright }, use) => {
const api = await playwright.request.newContext({ baseURL: process.env.BASE_URL });
const email = `qa+${randomUUID()}@example.com`; // unique in every shard
const res = await api.post('/api/test-accounts', { data: { email } });
const account: Account = await res.json();
await use(account); // shared by this worker's tests
await api.delete(`/api/test-accounts/${account.id}`);
await api.dispose();
}, { scope: 'worker' }],
});
// cart.spec.ts
import { test } from './fixtures';
test.beforeEach(async ({ request, account }) => {
await request.delete(`/api/test-accounts/${account.id}/cart`); // start empty
});
test('download invoice', async ({ page, account }) => {
// ...sign in as account.email and place an order...
const downloading = page.waitForEvent('download');
await page.getByRole('button', { name: 'Download invoice' }).click();
await (await downloading).saveAs(test.info().outputPath('invoice.pdf'));
});आम गलतियाँ
- Tests क्या share करते हैं यह पहले जाँचे बिना parallel execution चालू करना, फिर उजागर हुए failures के लिए runner को दोष देना।
- Order dependency को क्रम थोपकर ठीक करना, serial mode या numbered file names से, बजाय इसके कि हर test अपनी state खुद बनाए।
- Worker index को नाम का अकेला unique हिस्सा बनाना, जिससे हर shard का worker 0 एक ही account इस्तेमाल करता है।
- Shared environment में "list में तीन orders हैं" जैसी global गिनती पर assert करना, बजाय test के बनाए records पर।
- Sharded run को सभी shards की merged report की बजाय एक shard के result से आँकना।
- टकराव से हुए failures को pass होने तक retry करना, जो shared state को हटाने की बजाय छिपा देता है।
QA का नज़रिया
Isolation ऐसी चीज़ है जिसे tester जाँच सकता है और माँग सकता है। Suite को parallel करने से पहले, और जब भी आप कोई automated test review करें, चार checks करें: क्या test अकेले, shuffled क्रम में, लगातार दो बार और कई workers के साथ pass होता है? Jest में --randomize है, pytest-randomly plugin pytest runs का क्रम बदलता है, और Playwright में --repeat-each है। फिर पूछें कि हर test क्या बदलता है और वह बदलाव और कौन देख सकता है। कई fixes के लिए developers या platform team चाहिए: test accounts बनाने का API, cart reset करने का तरीका, हर run के लिए अलग tenant, और ऐसा environment जो चार workers का load झेल सके।
जब कोई test सिर्फ़ parallel में fail हो, तो वह evidence दर्ज करें जो टकराव को product defect से अलग करता है: वह किस worker और shard पर चला, उसने कौन-सा account और कौन-सी record ids इस्तेमाल कीं, उसी समय और क्या चल रहा था, और क्या वह अकेले और एक worker के साथ pass होता है। पुष्टि हुआ टकराव test suite का defect है जिसका owner और fix होना चाहिए, दोबारा चलाने लायक noise नहीं।
अभ्यास
Settings suite को parallel के लिए सुरक्षित बनाएँ
नीचे दिया suite एक worker के साथ काम करता है। Team इसे चार workers और दो shards के साथ चलाना चाहती है। हर test के लिए बताएँ कि वह दूसरे tests के साथ क्या share करता है, आप उसे कैसे isolate करेंगे, और कौन-से tests जैसे हैं वैसे रह सकते हैं और किन्हें अलग चलना होगा। फिर लिखें कि दो shards की वजह से क्या बदलता है।
account-settings.spec.ts (Playwright)
Today: workers: 1
Planned: workers: 4, fullyParallel: true
Planned CI: --shard=1/2 and --shard=2/2, both against the same staging environment
Every test signs in as qa.settings@example.com
1 change name sets the display name to "Sara A."
2 name in header expects "Sara A." in the page header
3 enable 2FA turns on two-factor sign-in for the account
4 export data saves the export to downloads/export.zip, then opens it
5 default theme sets the site-wide default theme to dark in the admin panel
6 language list reads the list of supported languages and changes nothing- हर test के लिए लिखें कि वह क्या पढ़ता है और क्या बदलता है।
- हर उस बदलाव को चिह्नित करें जिसे कोई दूसरा test भी पढ़ता या बदलता है।
- हर एक के लिए fix चुनें: अपना data, अपनी file, per-worker resource, या अलग serial run।
- जाँचें कि दो shards क्या जोड़ते हैं, यह ध्यान में रखते हुए कि दोनों एक ही staging environment इस्तेमाल करते हैं।
मॉडल उत्तर दिखाएँ
Tests 1 और 2: test 2 इस पर निर्भर है कि test 1 उसी account पर पहले चल चुका हो। fullyParallel के साथ वह test 1 से पहले या किसी दूसरे worker पर चल सकता है। Fix: हर test को fixture से नया account मिले, और test 2 header जाँचने से पहले API से नाम खुद set करे।
Test 3: shared account पर two-factor sign-in चालू करना बाक़ी सभी tests का sign-in तोड़ देता है, सिर्फ़ पढ़ने वाले test 6 का भी। उसे अपना नया account चाहिए, जिसे वह बाद में delete कर सके।
Test 4: दो workers एक ही समय downloads/export.zip लिख सकते हैं। इसकी बजाय test.info().outputPath('export.zip') में save करें।
Test 5: site-wide setting को account के हिसाब से isolate नहीं किया जा सकता। उसे --workers=1 वाले अलग run में ले जाएँ जो एक बार चले, shard jobs से पहले या बाद में, उनके साथ-साथ कभी नहीं, क्योंकि सब एक ही staging environment इस्तेमाल करते हैं। वह test शुरुआत में अपनी अपेक्षित theme set करे और teardown में default theme वापस लाए। Test 6 सिर्फ़ shared reference data पढ़ता है और अपने या read-only account से sign in करने के बाद जैसा है वैसा रह सकता है।
Shards: दोनों shards एक ही staging environment इस्तेमाल करते हैं, इसलिए account names सभी shards में unique होने चाहिए, जैसे UUID से, क्योंकि अकेला worker index दोहराया जाता है। Run पर फ़ैसला करने से पहले दोनों shard reports को एक में merge करें, और चार workers पर कई repeated runs से fixes की पुष्टि करें।
अक्सर पूछे जाने वाले सवाल
क्या parallel execution tests को flaky बना देता है?
ज़्यादातर यह उन समस्याओं को उजागर करता है जो पहले से थीं: shared state और order dependencies, जिन्हें तय क्रम और timing ने छिपा रखा था। यह एक असली कारण जोड़ सकता है, load: चार workers एक छोटे test environment को इतना धीमा कर सकते हैं कि waits का timeout हो जाए। अगर कम workers पर failures गायब हो जाएँ और fail हुए test का data किसी दूसरे test ने नहीं छुआ, तो shared state की बजाय load पर शक करें।
Workers और shards में क्या अंतर है?
Workers एक मशीन पर parallel processes हैं (JUnit 5 में threads); shards suite के हिस्से हैं जिन्हें अलग मशीनें या CI jobs चलाते हैं। दोनों साथ इस्तेमाल होते हैं: चार shards, हर एक में चार workers, एक साथ सोलह tests तक चलाते हैं। Workers एक मशीन का CPU और disk share करते हैं; shards सिर्फ़ test environment share करते हैं और उनकी reports merge करनी पड़ती हैं।
क्या हर test का अपना account होना चाहिए?
जब accounts बनाना सस्ता हो, तो हर test के लिए नया account सबसे सुरक्षित विकल्प है। हर worker के लिए एक account आम समझौता है: एक worker के tests एक के बाद एक चलते हैं, इसलिए यह तब काम करता है जब हर test अपनी शुरुआत में वह reset करे जो वह बदलता है। Read-only data, जैसे catalogue जिसे कोई test edit नहीं करता, सब tests share कर सकते हैं।
संदर्भ
- Eradicating Non-Determinism in Tests (Martin Fowler)flaky टेस्ट सूट का मूल्य क्यों नष्ट करते हैं, उनके आम कारण (isolation, asynchrony, remote services, समय, resource leak) और जवाब के रूप में quarantine।
- Playwright DocumentationPlaywright का आधिकारिक दस्तावेज़ — Chromium, WebKit और Firefox चलाने वाला end-to-end टेस्ट फ़्रेमवर्क।
- Best Practices — Playwrightयूज़र को दिखने वाला व्यवहार टेस्ट करें, टेस्ट अलग-थलग रखें, मज़बूत locator और web-first assertions अपनाएँ, और trace से डिबग करें।
- Best Practices — Cypressसमर्पित एट्रिब्यूट से एलिमेंट चुनने, फ़िक्स्ड wait से बचने और टेस्ट को स्वतंत्र रखने पर Cypress की गाइडेंस।
Mutqan tools से अभ्यास करें
UUID v4 (रैंडम) और v7 (समय-क्रमित) पहचानकर्ता थोक में बनाएँ और मौजूदा UUID वैलिडेट करें।
सुरक्षित रिज़र्व्ड डोमेन (example.com, test) और plus-addressing वैरिएंट के साथ टेस्ट ईमेल पते जनरेट करें।
टेस्ट अकाउंट के लिए फ़ेक यूज़र रिकॉर्ड (नाम, username, ईमेल, पासवर्ड, avatar seed, role) जनरेट करें।
सिंथेटिक टेस्ट डेटा की एक ही जगह पर फ़ैक्टरी: users, customers, companies, पते, संख्याएँ, तारीख़ें और स्ट्रिंग — JSON, CSV, SQL या XML में।
और गहराई से
इस topic में आपकी प्रगति
प्रगति ट्रैक करने के लिए साइन इन करें
Topics पूर्ण चिह्नित करें, जहाँ छोड़ा था वहीं से जारी रखें और specialisation परीक्षाएँ खोलें।