सर्वोत्तम प्रैक्टिस: सेटअप () में या घोषणा में JUnit वर्ग के खेतों की शुरुआत करें?


120

क्या मुझे इस तरह से घोषणा पर वर्ग क्षेत्रों को इनिशियलाइज़ करना चाहिए?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

या सेटअप () में इस तरह से?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

मैं पहले फॉर्म का उपयोग करता हूं क्योंकि यह अधिक संक्षिप्त है, और मुझे अंतिम क्षेत्रों का उपयोग करने की अनुमति देता है। अगर मुझे सेट-अप के लिए सेटअप () विधि का उपयोग करने की आवश्यकता नहीं है , तो क्या मुझे अभी भी इसका उपयोग करना चाहिए, और क्यों?

स्पष्टता: JUnit परीक्षण विधि के अनुसार एक बार परीक्षण वर्ग को तुरंत रोक देगा। इसका मतलब है कि listप्रति परीक्षण एक बार बनाया जाएगा, भले ही मैं इसे घोषित करूं। इसका मतलब यह भी है कि परीक्षणों के बीच कोई अस्थायी निर्भरता नहीं है। तो ऐसा लगता है कि सेटअप () का उपयोग करने के कोई फायदे नहीं हैं। हालाँकि JUnit FAQ में कई उदाहरण हैं जो setUp () में एक खाली संग्रह को इनिशियलाइज़ करते हैं, इसलिए मुझे लगता है कि इसका एक कारण होना चाहिए।


2
सावधान रहें कि उत्तर JUnit 4 (घोषणा में इनिशियलाइज़) और JUnit 3 (उपयोग सेटअप) में भिन्न है; यह भ्रम की जड़ है।
निल्स वॉन बर्थ

जवाबों:


99

यदि आप विशेष रूप से JUnit FAQ में उदाहरणों के बारे में सोच रहे हैं, जैसे कि मूल परीक्षण टेम्प्लेट , मुझे लगता है कि वहां दिखाया जा रहा सबसे अच्छा अभ्यास यह है कि परीक्षण के तहत वर्ग को आपके सेटअप विधि में (या परीक्षण विधि में) तत्काल किया जाना चाहिए ।

जब JUnit उदाहरण सेटअप विधि में एक ArrayList बनाते हैं, तो वे सभी उस ArrayList के व्यवहार का परीक्षण करने के लिए चलते हैं, जैसे TestIndexOutOfBoundException, testEmptyCallection और इसी तरह के मामले। वहाँ परिप्रेक्ष्य किसी वर्ग को लिखने और यह सुनिश्चित करने के लिए है कि यह सही काम करता है।

अपनी कक्षाओं का परीक्षण करते समय आपको संभवतः ऐसा ही करना चाहिए: सेटअप या परीक्षण विधि में अपनी वस्तु बनाएं, ताकि बाद में इसे तोड़ने पर आपको उचित आउटपुट मिल सके।

दूसरी ओर, यदि आप अपने परीक्षण कोड में जावा संग्रह वर्ग (या उस मामले के लिए अन्य पुस्तकालय वर्ग) का उपयोग करते हैं, तो यह संभवत: नहीं है क्योंकि आप इसका परीक्षण करना चाहते हैं - यह परीक्षण स्थिरता का सिर्फ एक हिस्सा है। इस स्थिति में, आप सुरक्षित रूप से मान सकते हैं कि यह काम कर रहा है, इसलिए घोषणा में इसे शुरू करने से कोई समस्या नहीं होगी।

जो इसके लायक है, उसके लिए मैं काफी बड़े, कई साल पुराने, टीडीडी-विकसित कोड बेस पर काम करता हूं। हम आदतन चीजों को टेस्ट कोड में उनकी घोषणाओं में इनिशियलाइज़ करते हैं, और डेढ़ साल में मैं इस प्रोजेक्ट पर रहा हूँ, इससे कभी कोई समस्या नहीं हुई। इसलिए कम से कम कुछ महत्वपूर्ण सबूत हैं जो यह करना एक उचित बात है।


45

मैंने खुद को खोदना शुरू कर दिया और मुझे उपयोग करने का एक संभावित लाभ मिला setUp()। यदि निष्पादन के दौरान कोई अपवाद रखा गया है setUp(), तो JUnit एक बहुत ही उपयोगी स्टैक ट्रेस मुद्रित करेगा। दूसरी ओर, यदि ऑब्जेक्ट निर्माण के दौरान एक अपवाद को फेंक दिया जाता है, तो त्रुटि संदेश बस कहता है कि JUnit परीक्षण के मामले को तत्काल रद्द करने में असमर्थ था और आपको वह पंक्ति संख्या दिखाई नहीं देती जहां विफलता हुई, शायद इसलिए कि JUnit परीक्षण को तुरंत करने के लिए प्रतिबिंब का उपयोग करता है कक्षाएं।

यह कोई भी एक खाली संग्रह बनाने के उदाहरण पर लागू नहीं होता है, क्योंकि यह कभी भी फेंक नहीं देगा, लेकिन यह setUp()विधि का एक फायदा है ।


18

एलेक्स बी के जवाब के अलावा।

यहां तक ​​कि एक निश्चित स्थिति में संसाधनों को तत्काल करने के लिए सेटअप विधि का उपयोग करना भी आवश्यक है। कंस्ट्रक्टर में ऐसा करना केवल समय की बात नहीं है, बल्कि जिस तरह से ज्यूनिट परीक्षण चलाती है, उसके चलते प्रत्येक टेस्ट स्टेट को चलाने के बाद मिटा दिया जाएगा।

JUnit पहले प्रत्येक परीक्षण विधि के लिए testClass के उदाहरण बनाता है और प्रत्येक उदाहरण के बनने के बाद परीक्षण चलाना शुरू करता है। परीक्षण विधि चलाने से पहले, इसकी सेटअप विधि चलाई जाती है, जिसमें कुछ राज्य तैयार किए जा सकते हैं।

यदि डेटाबेस राज्य को कंस्ट्रक्टर में बनाया जाएगा, तो सभी उदाहरण प्रत्येक परीक्षण को चलाने से पहले, एक-दूसरे के ठीक बाद db स्थिति को तुरंत चालू कर देंगे। दूसरे परीक्षण के अनुसार, परीक्षण एक गंदी स्थिति के साथ चलेंगे।

JUnits जीवनचक्र:

  1. प्रत्येक परीक्षण विधि के लिए एक अलग टेस्टक्लास उदाहरण बनाएं
  2. प्रत्येक टेस्टक्लास उदाहरण के लिए दोहराएं: कॉल सेटअप + कॉल टेस्टमिथोड

दो परीक्षण विधियों के साथ एक परीक्षण में कुछ लॉगगिंग के साथ: (संख्या हैशकोड है)

  • नया उदाहरण बनाना: 5718203
  • नया उदाहरण बनाना: 5947506
  • सेटअप: 5718203
  • टेस्टऑन: 5718203
  • सेटअप: 5947506
  • TestTwo: 5947506

3
सही है, लेकिन विषय से दूर। डेटाबेस अनिवार्य रूप से वैश्विक स्थिति है। यह एक ऐसी समस्या नहीं है जिसका मैं सामना कर रहा हूं। मैं ठीक से स्वतंत्र परीक्षणों के निष्पादन की गति से चिंतित हूं।
क्रेग पी। मोटलीन

यह प्रारंभिक आदेश केवल JUnit 3 में सही है, जहां यह एक महत्वपूर्ण सावधानी है। JUnit में 4 परीक्षण उदाहरणों को आलसी बनाया जाता है, इसलिए घोषणा में या सेटअप विधि में प्रारंभिक परीक्षण दोनों ही परीक्षण के समय होते हैं। इसके अलावा एक बार के सेटअप के लिए, @BeforeClassJUnit 4 में कोई भी उपयोग कर सकता है
Nils von Barth

11

JUnit 4 में:

  • के लिए क्लास टेस्ट के तहत , एक में प्रारंभ @Beforeपकड़ विफलताओं को, विधि।
  • के लिए अन्य वर्गों , घोषणा में प्रारंभ ...
    • ... संक्षिप्तता के लिए, और फ़ील्ड को चिह्नित करने के लिए final, बिल्कुल सवाल के अनुसार,
    • ... जब तक कि यह जटिल आरंभीकरण है जो विफल हो सकता है, जिस स्थिति में @Beforeविफलताओं को पकड़ने के लिए उपयोग किया जाता है।
  • के लिए वैश्विक राज्य (esp। धीमी गति से आरंभ , एक डेटाबेस की तरह), उपयोग @BeforeClass, लेकिन सावधान रहना परीक्षण के बीच निर्भरता की।
  • किसी एकल परीक्षण में प्रयुक्त वस्तु का प्रारंभिक रूप से परीक्षण विधि में ही किया जाना चाहिए।

एक @Beforeविधि या परीक्षण विधि में प्रारंभ करना आपको विफलताओं पर बेहतर त्रुटि रिपोर्टिंग प्राप्त करने की अनुमति देता है। यह विशेष रूप से क्लास अंडर टेस्ट (जिसे आप तोड़ सकते हैं) को तत्काल करने के लिए उपयोगी है, लेकिन बाह्य प्रणालियों को कॉल करने के लिए भी उपयोगी है, जैसे कि फाइल सिस्टम एक्सेस ("फ़ाइल नहीं मिली") या डेटाबेस से कनेक्ट करना ("कनेक्शन ने इनकार कर दिया")।

यह एक साधारण मानक और हमेशा उपयोग करने के लिए स्वीकार्य है @Before(स्पष्ट त्रुटियों लेकिन क्रिया) या हमेशा घोषणा में संक्षिप्त करें (संक्षिप्त लेकिन भ्रमित करने वाली त्रुटियां देता है), क्योंकि जटिल कोडिंग नियमों का पालन करना कठिन है, और यह कोई बड़ी बात नहीं है।

प्रारंभिक में setUpJUnit 3 का एक अवशेष है, जहां सभी परीक्षण उदाहरणों को उत्सुकता से प्रारंभ किया गया था, जो महंगी इनिशियलाइज़ेशन करने पर समस्या (गति, मेमोरी, संसाधन थकावट) का कारण बनता है। इस प्रकार सबसे अच्छा अभ्यास महंगी इनिशियलाइज़ेशन करना था setUp, जो केवल तभी चलाया जाता था जब परीक्षण निष्पादित किया जाता था। यह अब लागू नहीं होता है, इसलिए इसका उपयोग करना बहुत कम आवश्यक है setUp

यह कई अन्य उत्तरों को संक्षेप में प्रस्तुत करता है, जो क्रेग पी। मोटलिन (प्रश्न और स्वयं के उत्तर), मॉस कोलम (परीक्षण के तहत वर्ग), और डीएसएफ़ द्वारा विशेष रूप से दफनाते हैं।


7

JUnit 3 में, किसी भी परीक्षण को चलाने से पहले आपका फ़ील्ड इनिशियलाइज़र परीक्षण विधि के अनुसार एक बार चलाया जाएगा । जब तक आपके फ़ील्ड मान स्मृति में छोटे हैं, तब तक थोड़ा समय सेट करें, और वैश्विक स्थिति को प्रभावित न करें, फ़ील्ड इनिशियलाइज़र का उपयोग तकनीकी रूप से ठीक है। हालांकि, अगर वे पकड़ में नहीं आते हैं, तो आप पहले परीक्षण चलाने से पहले बहुत सारी मेमोरी या समय का उपभोग कर सकते हैं, और संभवतः स्मृति से बाहर भी दौड़ सकते हैं। इस कारण से, कई डेवलपर्स हमेशा सेटअप () विधि में फ़ील्ड मान सेट करते हैं, जहां यह हमेशा सुरक्षित होता है, तब भी जब यह सख्ती से आवश्यक नहीं है।

ध्यान दें कि JUnit 4 में, टेस्ट ऑब्जेक्ट इनिशियललाइज़ेशन टेस्ट रनिंग से ठीक पहले होता है, और इसलिए फ़ील्ड इनिशियलाइज़र का उपयोग करना अधिक सुरक्षित और अनुशंसित शैली है।


दिलचस्प। तो आपके द्वारा पहले वर्णित व्यवहार केवल JUnit 3 पर लागू होता है?
क्रेग पी। मोटलिन

6

आपके मामले में (एक सूची बनाने) अभ्यास में कोई अंतर नहीं है। लेकिन आम तौर पर सेटअप () का उपयोग करना बेहतर होता है, क्योंकि इससे जुनिट को अपवादों को सही ढंग से रिपोर्ट करने में मदद मिलेगी। यदि किसी टेस्ट के कंस्ट्रक्टर / इनिशियलाइज़र में कोई अपवाद होता है, तो यह एक परीक्षण विफलता है । हालाँकि, यदि सेटअप के दौरान कोई अपवाद होता है, तो परीक्षण की स्थापना में इसे कुछ समस्या के रूप में सोचना स्वाभाविक है, और जूनट इसे उचित रूप से रिपोर्ट करता है।


1
सही कहा। बस सेटअप () में हमेशा इंस्टेंट करने की आदत डालें और आपको चिंता करने के लिए एक प्रश्न कम है - उदाहरण के लिए मुझे अपने fooBar, जहां मेरे संग्रह को तुरंत भेजना चाहिए। यह एक तरह का कोडिंग मानक है जिसका आपको बस पालन करने की आवश्यकता है। लाभ आपको सूचियों के साथ नहीं, बल्कि अन्य तात्कालिकताओं के साथ।
ओलाफ कोक

@ ओलाफ कोडिंग मानक के बारे में जानकारी के लिए धन्यवाद, मैंने उस बारे में नहीं सोचा था। मैं मॉस कोलम के कोडिंग मानक के विचार से अधिक सहमत हूं।
क्रेग पी। मोटलिन

5

मैं पहले पठनीयता को प्राथमिकता देता हूं जो अक्सर सेटअप विधि का उपयोग नहीं करता है। मैं एक अपवाद बनाता हूं जब एक बुनियादी सेटअप ऑपरेशन में लंबा समय लगता है और प्रत्येक परीक्षण के भीतर दोहराया जाता है।
उस बिंदु पर मैं @BeforeClassएनोटेशन (बाद में अनुकूलन) का उपयोग करके सेटअप कार्यक्षमता में उस कार्यक्षमता को स्थानांतरित करता हूं ।

@BeforeClassसेटअप विधि का उपयोग करके अनुकूलन का उदाहरण : मैं कुछ डेटाबेस कार्यात्मक परीक्षणों के लिए dbunit का उपयोग करता हूं। सेटअप विधि डेटाबेस को एक ज्ञात स्थिति में रखने के लिए ज़िम्मेदार है (बहुत धीमी ... 30 सेकंड - डेटा की मात्रा के आधार पर 2 मिनट)। मैंने इस डेटा को सेटअप विधि में एनोटेट किया है @BeforeClassऔर फिर डेटा के उसी सेट के खिलाफ 10-20 परीक्षण चलाएं, जो प्रत्येक परीक्षण के अंदर डेटाबेस को फिर से लोड करने / शुरू करने का विरोध करता है।

Junit 3.8 (आपके उदाहरण में दिखाए गए अनुसार TestCase का विस्तार करना) का उपयोग करने के लिए केवल एक एनोटेशन जोड़ने की तुलना में थोड़ा अधिक कोड लिखने की आवश्यकता होती है, लेकिन "क्लास सेटअप से पहले एक बार चलाएं" अभी भी संभव है।


1
+1 क्योंकि मैं भी पठनीयता पसंद करता हूं। हालांकि, मुझे विश्वास नहीं है कि दूसरा तरीका एक अनुकूलन है।
क्रेग पी। मोटलिन

@ मैटलिन ने स्पष्ट करने के लिए dbunit उदाहरण जोड़ा कि आप सेटअप के साथ कैसे अनुकूलन कर सकते हैं।
एलेक्स बी

डेटाबेस अनिवार्य रूप से वैश्विक स्थिति है। तो db सेटअप को सेटअप () में ले जाना एक अनुकूलन नहीं है, यह परीक्षणों को ठीक से पूरा करने के लिए आवश्यक है।
क्रेग पी। मोटलिन

@ एलेक्स बी: जैसा कि मोटलिन ने कहा, यह अनुकूलन नहीं है। आप बस बदल रहे हैं जहां कोड में इनिशियलाइज़ेशन किया जाता है, लेकिन न तो कितनी बार और न ही कितनी जल्दी।
एडी

मैं "@BeforeClass" एनोटेशन का उपयोग करने का इरादा रखता हूं। स्पष्ट करने के लिए उदाहरण का संपादन।
एलेक्स बी

2

चूंकि प्रत्येक परीक्षण को स्वतंत्र रूप से निष्पादित किया जाता है, वस्तु के एक ताजा उदाहरण के साथ, किसी भी आंतरिक स्थिति वाले टेस्ट ऑब्जेक्ट के लिए ज्यादा बिंदु नहीं होता है, सिवाय इसके कि setUp()एक व्यक्तिगत परीक्षण और के बीच साझा किया जाता है tearDown()। यह एक कारण है (अन्य कारणों के अलावा अन्य) जो setUp()विधि का उपयोग करना अच्छा है ।

नोट: स्थैतिक स्थिति बनाए रखने के लिए यह एक JUnit परीक्षण वस्तु के लिए एक बुरा विचार है! यदि आप ट्रैकिंग या नैदानिक ​​उद्देश्यों के अलावा किसी अन्य चीज़ के लिए अपने परीक्षणों में स्थिर चर का उपयोग करते हैं, तो आप JUnit के उद्देश्य का हिस्सा अमान्य कर रहे हैं, जो यह है कि परीक्षण किसी भी क्रम में चलाए जा सकते हैं (हो सकता है), प्रत्येक परीक्षण एक के साथ चल रहा हो ताजा, स्वच्छ स्थिति।

उपयोग करने के फायदे यह setUp()है कि आपको प्रत्येक परीक्षण पद्धति में आरंभीकरण कोड को काटना और चिपकाना नहीं है और आपके पास निर्माण में परीक्षण सेटअप कोड नहीं है। आपके मामले में, थोड़ा अंतर है। जैसा कि आप इसे एक तुच्छ इनिशियलाइज़ेशन के रूप में दिखाते हैं, वैसे ही खाली सूची बनाने का काम सुरक्षित रूप से किया जा सकता है। हालाँकि, जैसा कि आपने और दूसरों ने बताया है, ऐसा कुछ भी जो संभवतः फेंक Exceptionसकता है, setUp()ऐसा किया जाना चाहिए ताकि आप विफल होने पर नैदानिक ​​स्टैक डंप प्राप्त करें।

आपके मामले में, जहां आप सिर्फ एक खाली सूची बना रहे हैं, मैं उसी तरह से करूंगा जो आप सुझा रहे हैं: घोषणा के बिंदु पर नई सूची असाइन करें। विशेष रूप से इस तरह से आपके पास इसे चिह्नित करने का विकल्प है finalयदि यह आपके परीक्षण वर्ग के लिए समझ में आता है।


1
+1 क्योंकि आप पहले व्यक्ति हैं जो अंतिम रूप से चिह्नित करने के लिए ऑब्जेक्ट निर्माण के दौरान सूची को शुरू करने का समर्थन करते हैं। स्टैटिक वेरिएबल्स के बारे में सामान इस सवाल से हटकर है।
क्रेग पी। मोटलिन

@ मॉटलिन: सच है, स्टैटिक वैरिएबल के बारे में सामान थोड़ा ऑफ-टॉपिक है। मुझे यकीन नहीं है कि मैंने इसे क्यों जोड़ा, लेकिन यह उस समय उचित लग रहा था, जो मैं पहले पैराग्राफ में कह रहा था।
एडी

के लाभ का finalउल्लेख हालांकि प्रश्न में किया गया है।
निल्स वॉन बर्थ

0
  • निरंतर मान (जुड़नार या दावे में उपयोग) को उनकी घोषणाओं में आरंभीकृत किया जाना चाहिए और final(कभी नहीं बदले)

  • परीक्षण के तहत वस्तु को सेटअप विधि में आरंभीकृत किया जाना चाहिए क्योंकि हम चीजों को सेट कर सकते हैं। बेशक हम अभी कुछ सेट नहीं कर सकते हैं लेकिन हम इसे बाद में सेट कर सकते हैं। Init विधि को तुरंत करने से परिवर्तनों में आसानी होगी।

  • परीक्षण के तहत वस्तु की निर्भरता यदि इनका मजाक उड़ाया जाता है, तो इसे अपने आप से त्वरित भी नहीं किया जाना चाहिए: आज नकली रूपरेखा प्रतिबिंब द्वारा इसे त्वरित कर सकती है।

नकली के लिए निर्भरता के बिना एक परीक्षण की तरह लग सकता है:

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

अलग करने के लिए निर्भरता के साथ एक परीक्षण की तरह लग सकता है:

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

    @Test
    public void populateList()
         ...
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.