2008-09-03 42 views
26

Tôi có một đối tượng Singleton/Factory mà tôi muốn viết một bài kiểm tra JUnit cho. Phương thức Factory quyết định việc triển khai lớp nào để khởi tạo dựa trên một tên lớp trong một tệp thuộc tính trên đường dẫn lớp. Nếu không tìm thấy tệp thuộc tính nào hoặc tệp thuộc tính không chứa khóa classname, thì lớp sẽ khởi tạo lớp triển khai mặc định.Sử dụng các trình nạp lớp khác nhau cho các bài kiểm tra JUnit khác nhau?

Vì nhà máy giữ một thể hiện tĩnh của Singleton để sử dụng khi nó được khởi tạo, để có thể kiểm tra logic "chuyển đổi dự phòng" trong phương thức Factory, tôi cần chạy từng phương thức thử trong một trình nạp lớp khác.

Có cách nào với JUnit (hoặc với gói kiểm tra đơn vị khác) để thực hiện việc này không?

chỉnh sửa: đây là một số mã máy đó là trong sử dụng:

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

Độc thân dẫn đến cả thế giới bị thương. Tránh đơn và mã của bạn trở nên dễ dàng hơn nhiều để kiểm tra và chỉ toàn diện đẹp hơn. –

Trả lời

3

Khi tôi chạy vào các loại tình huống tôi thích sử dụng một chút của một hack là gì. Thay vào đó, tôi có thể trưng ra một phương thức được bảo vệ như reinitialize(), sau đó gọi nó từ bài kiểm tra để thiết lập hiệu quả nhà máy về trạng thái ban đầu của nó. Phương pháp này chỉ tồn tại cho các trường hợp thử nghiệm và tôi ghi lại nó như vậy.

Đó là một chút hack, nhưng nó dễ dàng hơn nhiều so với các tùy chọn khác và bạn sẽ không cần lib của bên thứ 3 để làm điều đó (mặc dù nếu bạn thích giải pháp sạch hơn, có thể có một số loại bên thứ 3 các công cụ hiện có mà bạn có thể sử dụng).

3

Bạn có thể sử dụng Reflection để đặt myClassImpl bằng cách gọi lại instantiateMyClass(). Hãy xem this answer để xem các mẫu ví dụ để phát xung quanh bằng các biến và phương thức riêng tư.

36

Câu hỏi này có thể cũ nhưng vì đây là câu trả lời gần nhất mà tôi tìm thấy khi gặp sự cố này, mặc dù tôi đã mô tả giải pháp của mình.

Sử dụng JUnit 4

Tách các bài kiểm tra của bạn lên để có một phương pháp thử nghiệm cho mỗi lớp học (giải pháp này chỉ thay đổi classloaders giữa các lớp, không giữa các phương pháp như Á hậu mẹ tập hợp tất cả các phương pháp một lần cho mỗi lớp)

Thêm chú thích @RunWith(SeparateClassloaderTestRunner.class) vào các lớp thử nghiệm của bạn.

Tạo SeparateClassloaderTestRunner trông như thế này:

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); 
     } 
    } 
} 

Lưu ý tôi phải làm điều này để kiểm tra mã chạy trong một khuôn khổ di sản mà tôi không thể thay đổi. Với sự lựa chọn tôi muốn giảm việc sử dụng các statics và/hoặc đặt các móc kiểm tra vào để cho phép hệ thống được thiết lập lại. Nó có thể không được đẹp nhưng nó cho phép tôi kiểm tra rất nhiều mã khủng khiếp mà sẽ là khó khăn nếu không.

Giải pháp này cũng phá vỡ mọi thứ khác dựa trên các thủ thuật tải lớp như Mockito.

+0

Thay vì tìm kiếm "org.mypackages". trong loadClass() bạn cũng có thể làm một cái gì đó như thế này: return name.startsWith ("java") || name.startsWith ("org.junit")? super.loadClass (name): super.findClass (tên); – Gilead

+1

Làm cách nào để chúng tôi đưa ra câu trả lời được chấp nhận này? Điều này trả lời câu hỏi trong khi câu trả lời 'chấp nhận' hiện tại thì không. – irbull

+0

Cảm ơn câu trả lời. Tôi đang cố gắng để tái tạo này, nhưng tất cả các lớp học của tôi được nạp bởi bộ nạp lớp cha mẹ anyway, ngay cả khi họ đang từ gói bị loại trừ? –

2

Nếu thực hiện Junit qua Ant task, bạn có thể đặt fork=true để thực thi mọi lớp kiểm tra trong JVM của chính nó. Cũng đặt mỗi phương pháp thử nghiệm trong lớp riêng của mình và chúng sẽ tải và khởi tạo phiên bản MyClass của riêng chúng. Nó cực kỳ nhưng rất hiệu quả.

0

Dưới đây bạn có thể tìm thấy mẫu không cần một trình kiểm tra JUnit riêng biệt và cũng hoạt động với các thủ thuật tải lớp như Mockito.

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); 
    } 

    } 

} 
Các vấn đề liên quan