2013-07-18 31 views
12

Tôi đang tạo một JUnit TestCase cho một dự án cần tải một tệp cấu hình trong khi khởi tạo.getResourceAsStream từ JUnit

Tệp cấu hình này nằm trong dự án trong thư mục src/main/resources/config và trong quá trình xây dựng, hãy đặt nó vào thư mục/config, bên trong JAR.

Lớp khởi, đọc các tập tin từ đó sử dụng câu lệnh:

ClassLoader classloader = this.getClass().getClassLoader(); 
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream("/config/config.xml"))); 

Tôi có vấn đề là khi tôi triển khai và thực hiện jar này vào máy chủ ứng dụng nó hoạt động như mong đợi, tuy nhiên, bất cứ khi nào tôi chạy nó trong một JUnit TestCase trong Eclipse, phương thức getResrouceAsStream trả về null.

Xem xét lớp đó là my.package.MyClassTest.java và nó nằm trong src/test/java/my/package/MyClassTest.java, tôi đã thử đặt một bản sao của tệp config.xml vào thư mục nhưng không thành công như sau:

- src/test/resources/config 
- src/test/resources/my/package/config 
- src/test/java/my/package/config 

tôi biết rằng câu hỏi tương tự đã được hỏi nhiều lần ở đây trong StackOverflow, nhưng tất cả các câu trả lời tôi thấy đề cập đến việc thay đổi cách các tập tin được nạp và, mặc dù thay đổi mã có thể là một tùy chọn, tôi muốn chỉ tìm đúng nơi cho tệp để tôi không cần sửa đổi những thứ đã hoạt động trong môi trường sản xuất.

Vì vậy, tôi nên đặt tệp này ở đâu để có thể sử dụng nó trong bài kiểm tra JUnit?

CẬP NHẬT

Tôi chỉ đưa ra các giải pháp với một thay đổi nhỏ trong các mã: Thay vì sử dụng ClassLoader để có được những tài nguyên, tôi trực tiếp sử dụng các lớp:

Class clazz = this.getClass(); 
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream("/config/config.xml"))); 

Và nó đọc tập tin thành công từ src/test/resources/config/config.xml.

Tuy nhiên, có một cái gì đó rất lạ ở đây: Phương pháp Class.getResourceAsStream là:

public InputStream getResourceAsStream(String name) { 
    name = resolveName(name); 
    ClassLoader cl = getClassLoader0(); 
    if (cl==null) { 
     // A system class. 
     return ClassLoader.getSystemResourceAsStream(name); 
    } 
    return cl.getResourceAsStream(name); 
} 

Và nếu tôi gỡ lỗi nó, tôi có thể thấy rõ rằng đây getClassLoader0() lợi nhuận chính xác cùng một đối tượng (cùng một id) so với cuộc gọi trước đó, this.getClass(). getResourceAsStream() (mà tôi đã duy trì, chỉ để so sánh các giá trị) !!!

Điều gì đang xảy ra ở đây ?!

Tại sao gọi phương thức trực tiếp không hoạt động, trong khi chèn cuộc gọi phương thức mới vào giữa các tác phẩm?

Thành thật mà nói, tôi thực sự kinh ngạc trước điều này.

BTW, tôi đang sử dụng phiên bản JUnit 4.10. Có thể nó giả mạo cuộc gọi getClassLoader theo một cách nào đó không?

Rất cám ơn,

Carles

+1

Có phải 'src/test/resources /' là thư mục nguồn trong Eclipse không? –

+0

@SotiriosDelimanolis nếu bạn đăng câu trả lời này, tôi sẽ bỏ phiếu. –

+0

@SotiriosDelimanolis: Vâng, đúng vậy. Thật vậy, khi tôi nhìn vào nó, tôi đã nhận ra rằng Eclipse nhận nó theo mặc định cho các dự án maven. –

Trả lời

14

Trả lời với tên cho câu hỏi của bạn

Và nếu tôi gỡ lỗi nó, tôi có thể thấy rõ rằng đây getClassLoader0() lợi nhuận chính xác cùng một đối tượng (giống id) so với cuộc gọi trước đó, this.getClass(). getResourceAsStream() (mà tôi đã duy trì, chỉ để so sánh các giá trị) !!!

Điều gì đang xảy ra ở đây ?!

Tại sao gọi phương thức trực tiếp không hoạt động, trong khi chèn cuộc gọi phương thức mới vào giữa các tác phẩm?

Sự khác biệt giữa gọi

this.getClass().getClassLoader().getResourceAsStream("/config/config.xml"); 

và gọi

this.getClass().getResourceAsStream("/config/config.xml"); 

Lies trong nguồn chính xác mà bạn đã được hiển thị từ Class:

public InputStream getResourceAsStream(String name) { 
    name = resolveName(name); 
    ClassLoader cl = getClassLoader0(); 
    if (cl==null) { 
     // A system class. 
     return ClassLoader.getSystemResourceAsStream(name); 
    } 
    return cl.getResourceAsStream(name); 
} 

Nhưng vấn đề không phải là với những gì getClassLoader0() trả về. Nó trả về cùng một điều trong cả hai trường hợp. Sự khác biệt thực sự là trong resolveName(name). Đây là phương pháp riêng tư trong lớp học Class.

private String resolveName(String name) { 
    if (name == null) { 
     return name; 
    } 
    if (!name.startsWith("/")) { 
     Class<?> c = this; 
     while (c.isArray()) { 
      c = c.getComponentType(); 
     } 
     String baseName = c.getName(); 
     int index = baseName.lastIndexOf('.'); 
     if (index != -1) { 
      name = baseName.substring(0, index).replace('.', '/') 
       +"/"+name; 
     } 
    } else { 
     name = name.substring(1); 
    } 
    return name; 
} 

Vì vậy, bạn thấy đấy, trước khi thực sự gọi getResourceAsStream() của classloader, nó thực sự loại bỏ các dấu gạch chéo bắt đầu từ con đường.

Nói chung, nó sẽ cố gắng lấy tài nguyên tương ứng với this khi nó không có dấu gạch chéo và chuyển nó lên lớp classLoader nếu nó có dấu gạch chéo ở đầu.

Phương thức getResourceAsStream() của classLoader thực sự có ý định được sử dụng cho các đường dẫn tương đối (nếu không bạn chỉ sử dụng FileInputStream).

Vì vậy, khi bạn sử dụng this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");, bạn đã thực sự vượt qua đường dẫn bằng dấu gạch chéo ở đầu, điều này không thành công. Khi bạn sử dụng this.getClass().getResourceAsStream("/config/config.xml");, việc này đã đủ loại bỏ nó cho bạn.

+0

Tôi sợ rằng hiện tại tôi không thể kiểm tra nó (Tôi muốn thử xóa dấu gạch chéo đầu tiên theo cách thủ công khỏi tuyến đường và xem nó có hoạt động giống nhau không), nhưng âm thanh thuyết phục. Cảm ơn và được chấp nhận. –

1

Tôi không chắc chắn về điều này, nhưng bạn có thể thử đặt thư mục tài nguyên của mình trong src/cây chính thay vì cây src/kiểm tra. Trong một số cấu hình, ít nhất, các tập tin eclipse copy 'resource' từ src đến các lớp, nhưng không phải từ kiểm tra tới các lớp. Đó là giá trị một shot ...

+0

Cả hai thư mục tài nguyên tồn tại trong src/main và src/test, chứa cùng một tệp ... nhưng nó không hoạt động. –

2

getResourceAsStream chức năng từ đối tượng ClassLoader sẽ không xóa dấu gạch chéo được thêm vào trước chuỗi tìm kiếm của bạn vì tìm kiếm sẽ được thực hiện tương ứng với đường dẫn lớp. tức là tìm kiếm tài nguyên nơi đường dẫn tìm kiếm được sử dụng để tải các lớp.

Ví dụ: nếu lớp của bạn yourpackage/Test.class, nằm dưới /a/b/c/d/yourpackage/Test.class, được tải bởi trình nạp lớp hệ thống (mặc định trình nạp lớp) và đường dẫn lớp phải trỏ đến /a/b/c/d để tải lớp.Tìm kiếm sẽ được thực hiện trên đường dẫn này.

getResourceAsStream chức năng từ đối tượng lớp loại bỏ dấu gạch chéo được thêm vào trước chuỗi tìm kiếm của bạn vì tìm kiếm sẽ được thực hiện liên quan đến lớp mà nó cư trú. nghĩa là tìm kiếm tài nguyên nơi lớp của bạn được tải từ đó.

Nói ví dụ nếu yourpackage/Test.class nạp từ /a/b/c/d/yourpackage/Test.class sau đó con đường tài nguyên sẽ được /a/b/c/d/yourpackage/config/config.xml

Bạn có thể kiểm tra điều này bằng snipet mã sau vì cả hai getResourcegetResourceAsStream sử dụng để làm theo các thuật toán tương tự cho tìm kiếm.

System.out.println(Test.class.getClassLoader().getResource("config/config.xml")); 
System.out.println(Test.class.getResource("config/config.xml")); 
Các vấn đề liên quan