आपके टेस्ट झूठ बोल रहे हैं, और आपको पता भी नहीं
\n\nएक स्थिति की कल्पना करें: आपने मॉड्यूलर टेस्ट लिखे, code coverage (कोड कवरेज) 95% है। मैनेजर खुश है, CI/CD पाइपलाइन हरी है। लेकिन शुक्रवार शाम को प्रोडक्शन में एक बग आ जाता है। परिचित लगता है?
\n\nसमस्या यह है कि क्लासिकल कोड कवरेज एक भ्रम है। यह दिखाता है कि कोड की कौन सी पंक्तियाँ निष्पादित हुईं, लेकिन मुख्य प्रश्न का उत्तर नहीं देता: क्या टेस्ट वास्तव में लॉजिक की जाँच कर रहे हैं? यहीं पर mutation testing आता है — एक ऐसी तकनीक जो बेरहमी से आपके कोड को "तोड़ती" है, ताकि यह जाँच सके कि आपके टेस्ट कितने अच्छे हैं।
\n\nइस लेख में, हम समझेंगे कि जावा में म्यूटेशन टेस्टिंग क्या है, यह कैसे काम करता है, और PIT (Pitest) टूल का उपयोग करके वास्तविक उदाहरण दिखाएँगे। आप सीखेंगे कि अपने टेस्ट को "रिपोर्ट में टिक" से बग्स के खिलाफ वास्तविक सुरक्षा में कैसे बदलें।
\n\nmutation testing क्या है और यह क्यों महत्वपूर्ण है?
\n\nMutation testing (या म्यूटेशन टेस्टिंग) टेस्ट की गुणवत्ता का मूल्यांकन करने की एक विधि है, जिसमें स्रोत कोड में छोटे, जानबूझकर गलत बदलाव किए जाते हैं (जिन्हें म्यूटेशन कहा जाता है)। इसके बाद आपके टेस्ट चलाए जाते हैं। यदि कम से कम एक टेस्ट फेल होता है — म्यूटेंट "मारा गया", टेस्ट ने काम किया। यदि टेस्ट पास हो जाते हैं — म्यूटेंट "बच गया", और यह एक चेतावनी है।
\n\nएक सरल उपमा: आप जाँच रहे हैं कि आपका अलार्म चोरों को कितनी अच्छी तरह पकड़ता है। आप एक "चोर" (म्यूटेंट) रखते हैं और देखते हैं कि अलार्म (टेस्ट) बजता है या नहीं। यदि चोर शांति से चला जाता है — अलार्म बेकार है।
\n\ncode coverage एक खराब संकेतक क्यों है?
\n\nऐसे कोड की कल्पना करें:
\n\npublic boolean isAdult(int age) {\n if (age >= 18) {\n return true;\n }\n return false;\n}\n\nऔर टेस्ट:
\n\n@Test\nvoid testIsAdult() {\n assertTrue(isAdult(18));\n}\n\nकोड कवरेज 100% दिखाएगा (क्या if-else की दोनों शाखाएँ निष्पादित हुईं?) नहीं! केवल true शाखा निष्पादित हुई। लेकिन टेस्ट हरा है। अब कल्पना करें कि किसी ने गलती से शर्त को age > 21 में बदल दिया। टेस्ट फिर भी पास होगा, क्योंकि 18 > 21 false है, और assertTrue true की उम्मीद करता है। बग प्रोडक्शन में चला जाएगा।
म्यूटेशन टेस्टिंग तुरंत ऐसी स्थिति को पकड़ लेगी, एक म्यूटेंट बनाकर जो >= को > में बदलता है या शर्त को उलट देता है।
mutation testing कैसे काम करता है: सरल सिद्धांत
\n\nप्रक्रिया तीन चरणों में होती है:
\n\n- \n
- म्यूटेंट का निर्माण। टूल (जैसे, PIT) आपके कोड के कई संस्करण बनाता है जिनमें छोटे बदलाव होते हैं: ऑपरेटर बदलना, मेथड कॉल हटाना, शर्तों को उलटना। \n
- टेस्ट चलाना। प्रत्येक म्यूटेंट के लिए आपके यूनिट टेस्ट चलाए जाते हैं। \n
- परिणामों का विश्लेषण। यदि टेस्ट फेल होता है — म्यूटेंट मारा गया। यदि पास होता है — जीवित। मारे गए म्यूटेंट का प्रतिशत mutation score (म्यूटेशन स्कोर) कहलाता है। \n
अच्छा संकेतक: 80-90% मारे गए म्यूटेंट। इससे कम कुछ भी टेस्ट को फिर से लिखने का कारण है।
\n\nPIT के साथ जावा में व्यावहारिक उदाहरण
\n\nआइए एक वास्तविक उदाहरण लें। मान लीजिए, हमारे पास छूट की गणना के लिए एक सेवा है:
\n\npublic class DiscountService {\n public double calculateDiscount(double amount, boolean isPremium) {\n double discount = 0.0;\n\n if (amount > 100) {\n discount = 0.1; // 10% छूट\n }\n\n if (isPremium) {\n discount += 0.05; // अतिरिक्त 5%\n }\n\n // छूट 30% से अधिक नहीं हो सकती\n if (discount > 0.3) {\n discount = 0.3;\n }\n\n return discount;\n }\n}\n\nएक सरल टेस्ट लिखें:
\n\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\nclass DiscountServiceTest {\n\n private final DiscountService service = new