2008-09-03 13 views
26

मेरे पास सिंगलटन/फैक्ट्री ऑब्जेक्ट है जो मैं एक जुनीट टेस्ट लिखना चाहता हूं। फैक्टरी विधि क्लासपाथ पर गुण फ़ाइल में क्लासनाम के आधार पर तत्काल कार्यान्वित करने का निर्णय लेती है। यदि कोई गुण फ़ाइल नहीं मिली है, या गुण फ़ाइल में क्लासनाम कुंजी नहीं है, तो कक्षा डिफ़ॉल्ट कार्यान्वयन कक्षा को तुरंत चालू करेगी।विभिन्न जुनीट परीक्षणों के लिए विभिन्न क्लासलोडर्स का उपयोग करना?

चूंकि फ़ैक्टरी विधि में "विफलता" तर्क का परीक्षण करने में सक्षम होने के लिए फैक्ट्री को तत्काल समाप्त करने के बाद सिंगलटन का स्थिर उदाहरण रहता है, इसलिए मुझे प्रत्येक टेस्ट विधि को एक अलग क्लासलोडर में चलाने की आवश्यकता होगी।

क्या ऐसा करने के लिए जुनीट (या किसी अन्य इकाई परीक्षण पैकेज के साथ) कोई तरीका है?

संपादित करें: यहाँ फैक्टरी कोड उपयोग में है में से कुछ है:

private static MyClass myClassImpl = instantiateMyClass(); 

private static MyClass instantiateMyClass() { 
    MyClass newMyClass = null; 
    String className = null; 

    try { 
     Properties props = getProperties(); 
     className = props.getProperty(PROPERTY_CLASSNAME_KEY); 

     if (className == null) { 
      log.warn("instantiateMyClass: Property [" + PROPERTY_CLASSNAME_KEY 
        + "] not found in properties, using default MyClass class [" + DEFAULT_CLASSNAME + "]"); 
      className = DEFAULT_CLASSNAME; 
     } 

     Class MyClassClass = Class.forName(className); 
     Object MyClassObj = MyClassClass.newInstance(); 
     if (MyClassObj instanceof MyClass) { 
      newMyClass = (MyClass) MyClassObj; 
     } 
    } 
    catch (...) { 
     ... 
    } 

    return newMyClass; 
} 

private static Properties getProperties() throws IOException { 

    Properties props = new Properties(); 

    InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_FILENAME); 

    if (stream != null) { 
     props.load(stream); 
    } 
    else { 
     log.error("getProperties: could not load properties file [" + PROPERTIES_FILENAME + "] from classpath, file not found"); 
    } 

    return props; 
} 
+0

सिंगलेट्स चोट की पूरी दुनिया का नेतृत्व करते हैं। सिंगलेट्स से बचें और आपका कोड परीक्षण करने के लिए बहुत आसान हो जाता है और बस ऑल-राउंड निकर। –

उत्तर

3

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

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

3

आप instantiateMyClass() फिर से फोन करके myClassImpl स्थापित करने के लिए प्रतिबिंब का उपयोग कर सकते हैं। निजी विधियों और चर के साथ खेलने के लिए उदाहरण पैटर्न देखने के लिए this answer पर एक नज़र डालें।

36

यह सवाल पुराने हो सकता है, लेकिन इस के बाद से सबसे पास का जवाब मैंने पाया जब मैं इस समस्या को मैं हालांकि मैं अपने समाधान का वर्णन होता था।

JUnit 4

का उपयोग करते हुए अपने परीक्षण विभाजित ताकि वर्ग प्रति एक परीक्षा पद्धति है (इस समाधान केवल वर्गों के बीच classloaders बदल जाता है, माता पिता के धावक के रूप में तरीकों के बीच नहीं प्रति वर्ग एक बार सभी तरीकों बटोरता)

अपने परीक्षण वर्गों के लिए @RunWith(SeparateClassloaderTestRunner.class) टिप्पणी जोड़ें।

SeparateClassloaderTestRunner इस तरह देखने के लिए बनाएँ:

public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner { 

    public SeparateClassloaderTestRunner(Class<?> clazz) throws InitializationError { 
     super(getFromTestClassloader(clazz)); 
    } 

    private static Class<?> getFromTestClassloader(Class<?> clazz) throws InitializationError { 
     try { 
      ClassLoader testClassLoader = new TestClassLoader(); 
      return Class.forName(clazz.getName(), true, testClassLoader); 
     } catch (ClassNotFoundException e) { 
      throw new InitializationError(e); 
     } 
    } 

    public static class TestClassLoader extends URLClassLoader { 
     public TestClassLoader() { 
      super(((URLClassLoader)getSystemClassLoader()).getURLs()); 
     } 

     @Override 
     public Class<?> loadClass(String name) throws ClassNotFoundException { 
      if (name.startsWith("org.mypackages.")) { 
       return super.findClass(name); 
      } 
      return super.loadClass(name); 
     } 
    } 
} 

नोट मैं था ऐसा करने के लिए कोड एक विरासत ढांचे मैं नहीं बदल सकता है में चल रहे परीक्षण करने के लिए। पसंद को देखते हुए मैं सिस्टम को रीसेट करने की अनुमति देने के लिए स्टेटिक्स के उपयोग को कम करता हूं और/या परीक्षण हुक डालता हूं। यह सुंदर नहीं हो सकता है लेकिन यह मुझे एक बहुत सारे कोड का परीक्षण करने की अनुमति देता है जो अन्यथा मुश्किल होगा।

यह समाधान कुछ भी तोड़ता है जो मॉकिटो जैसे क्लासलोडिंग चाल पर निर्भर करता है।

+0

"org.mypackages" की तलाश करने के बजाय। loadClass() में आप कुछ ऐसा भी कर सकते हैं: वापसी नाम .starts के साथ ("जावा") || name.starts के साथ ("org.junit")? super.loadClass (नाम): super.findClass (नाम); – Gilead

+1

हम इसे स्वीकृत उत्तर कैसे बना सकते हैं? यह सवाल का जवाब देता है जबकि वर्तमान 'स्वीकृत उत्तर' नहीं है। – irbull

+0

उत्तर के लिए धन्यवाद। मैं इसे फिर से बनाने की कोशिश कर रहा हूं, लेकिन मेरे सभी वर्ग अभिभावक क्लासलोडर द्वारा किसी भी तरह से लोड हो जाते हैं, भले ही वे बहिष्कृत पैकेज से हों? –

2

हैं Ant task के माध्यम से JUnit क्रियान्वित आप fork=true अपने आप JVM में परीक्षण के हर वर्ग पर अमल करने के लिए सेट कर सकते हैं। प्रत्येक टेस्ट विधि को अपनी कक्षा में भी रखें और वे प्रत्येक लोड करेंगे और MyClass का अपना संस्करण प्रारंभ करेंगे। यह चरम लेकिन बहुत प्रभावी है।

0

नीचे आप एक नमूना पा सकते हैं जिसके लिए एक अलग जुनीट परीक्षण धावक की आवश्यकता नहीं है और मॉकिटो जैसे क्लासलोडिंग चाल के साथ भी काम करता है।

package com.mycompany.app; 

import static org.junit.Assert.assertEquals; 
import static org.mockito.Mockito.mock; 
import static org.mockito.Mockito.verify; 

import java.net.URLClassLoader; 

import org.junit.Test; 

public class ApplicationInSeparateClassLoaderTest { 

    @Test 
    public void testApplicationInSeparateClassLoader1() throws Exception { 
    testApplicationInSeparateClassLoader(); 
    } 

    @Test 
    public void testApplicationInSeparateClassLoader2() throws Exception { 
    testApplicationInSeparateClassLoader(); 
    } 

    private void testApplicationInSeparateClassLoader() throws Exception { 
    //run application code in separate class loader in order to isolate static state between test runs 
    Runnable runnable = mock(Runnable.class); 
    //set up your mock object expectations here, if needed 
    InterfaceToApplicationDependentCode tester = makeCodeToRunInSeparateClassLoader(
     "com.mycompany.app", InterfaceToApplicationDependentCode.class, CodeToRunInApplicationClassLoader.class); 
    //if you want to try the code without class loader isolation, comment out above line and comment in the line below 
    //CodeToRunInApplicationClassLoader tester = new CodeToRunInApplicationClassLoaderImpl(); 
    tester.testTheCode(runnable); 
    verify(runnable).run(); 
    assertEquals("should be one invocation!", 1, tester.getNumOfInvocations()); 
    } 

    /** 
    * Create a new class loader for loading application-dependent code and return an instance of that. 
    */ 
    @SuppressWarnings("unchecked") 
    private <I, T> I makeCodeToRunInSeparateClassLoader(
     String packageName, Class<I> testCodeInterfaceClass, Class<T> testCodeImplClass) throws Exception { 
    TestApplicationClassLoader cl = new TestApplicationClassLoader(
     packageName, getClass(), testCodeInterfaceClass); 
    Class<?> testerClass = cl.loadClass(testCodeImplClass.getName()); 
    return (I) testerClass.newInstance(); 
    } 

    /** 
    * Bridge interface, implemented by code that should be run in application class loader. 
    * This interface is loaded by the same class loader as the unit test class, so 
    * we can call the application-dependent code without need for reflection. 
    */ 
    public static interface InterfaceToApplicationDependentCode { 
    void testTheCode(Runnable run); 
    int getNumOfInvocations(); 
    } 

    /** 
    * Test-specific code to call application-dependent code. This class is loaded by 
    * the same class loader as the application code. 
    */ 
    public static class CodeToRunInApplicationClassLoader implements InterfaceToApplicationDependentCode { 
    private static int numOfInvocations = 0; 

    @Override 
    public void testTheCode(Runnable runnable) { 
     numOfInvocations++; 
     runnable.run(); 
    } 

    @Override 
    public int getNumOfInvocations() { 
     return numOfInvocations; 
    } 
    } 

    /** 
    * Loads application classes in separate class loader from test classes. 
    */ 
    private static class TestApplicationClassLoader extends URLClassLoader { 

    private final String appPackage; 
    private final String mainTestClassName; 
    private final String[] testSupportClassNames; 

    public TestApplicationClassLoader(String appPackage, Class<?> mainTestClass, Class<?>... testSupportClasses) { 
     super(((URLClassLoader) getSystemClassLoader()).getURLs()); 
     this.appPackage = appPackage; 
     this.mainTestClassName = mainTestClass.getName(); 
     this.testSupportClassNames = convertClassesToStrings(testSupportClasses); 
    } 

    private String[] convertClassesToStrings(Class<?>[] classes) { 
     String[] results = new String[classes.length]; 
     for (int i = 0; i < classes.length; i++) { 
     results[i] = classes[i].getName(); 
     } 
     return results; 
    } 

    @Override 
    public Class<?> loadClass(String className) throws ClassNotFoundException { 
     if (isApplicationClass(className)) { 
     //look for class only in local class loader 
     return super.findClass(className); 
     } 
     //look for class in parent class loader first and only then in local class loader 
     return super.loadClass(className); 
    } 

    private boolean isApplicationClass(String className) { 
     if (mainTestClassName.equals(className)) { 
     return false; 
     } 
     for (int i = 0; i < testSupportClassNames.length; i++) { 
     if (testSupportClassNames[i].equals(className)) { 
      return false; 
     } 
     } 
     return className.startsWith(appPackage); 
    } 

    } 

}