2010-03-18 30 views
21

Cụ thể, tôi đang cố tạo một bài kiểm tra đơn vị cho một phương thức yêu cầu sử dụng File.separatorChar để tạo đường dẫn trên cửa sổ và unix. Mã phải chạy trên cả hai nền tảng, nhưng tôi gặp lỗi với JUnit khi tôi cố gắng thay đổi trường cuối cùng tĩnh này.Sử dụng phản chiếu để thay đổi tệp cuối cùng File.separatorChar cho thử nghiệm đơn vị?

Bất kỳ ai có ý tưởng gì đang xảy ra?

Field field = java.io.File.class.getDeclaredField("separatorChar"); 
field.setAccessible(true); 
field.setChar(java.io.File.class,'/'); 

Khi tôi làm điều này, tôi nhận được

IllegalAccessException: Can not set static final char field java.io.File.separatorChar to java.lang.Character 

Suy nghĩ?

+1

Có thể tốt hơn là chạy thử nghiệm đơn vị trong môi trường VirtualBox trong hệ điều hành đích khác. Ai biết được những gì sẽ phá vỡ khi bạn gây rối với JVM như thế. Ngoài ra, có thể viết lại mã của bạn để không sử dụng trực tiếp File.separatorChar. Bạn có thể xây dựng đường dẫn bằng cách sử dụng hàm tạo tệp (parentFile, name), ví dụ. – Thilo

+0

@ Thilo: Đó là một ý tưởng hay, và bây giờ tôi nghĩ về nó, có lẽ có một cách để chạy logic của tôi theo cách đa nền tảng mà không cần xử lý các URL của tập tin. Tôi nghĩ rằng việc biết cách thay đổi java.io.File.separatorChar, tuy nhiên, là một điều hữu ích để biết về một số trường hợp sử dụng hợp pháp khác. –

Trả lời

59

Từ tài liệu cho Field.set:

Nếu trường cơ bản là cuối cùng, phương pháp ném một IllegalAccessException trừ setAccessible(true) đã thành công cho lĩnh vực này và lĩnh vực này là không tĩnh.

Vì vậy, thoạt đầu có vẻ như bạn không may mắn, vì File.separatorCharstatic. Đáng ngạc nhiên, có một cách để giải quyết vấn đề này: chỉ cần tạo trường static không còn là final thông qua phản ánh.

Tôi thích giải pháp này from javaspecialist.eu:

static void setFinalStatic(Field field, Object newValue) throws Exception { 
    field.setAccessible(true); 

    // remove final modifier from field 
    Field modifiersField = Field.class.getDeclaredField("modifiers"); 
    modifiersField.setAccessible(true); 
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 

    field.set(null, newValue); 
} 

Tôi đã thử nghiệm nó và nó hoạt động:

setFinalStatic(File.class.getField("separatorChar"), '#'); 
System.out.println(File.separatorChar); // prints "#" 

Làm bài tập cực kỳ thận trọng với kỹ thuật này.hậu quả nghiêm trọng sang một bên, sau đây thực sự hoạt động:

setFinalStatic(Boolean.class.getField("FALSE"), true); 
System.out.format("Everything is %s", false); // "Everything is true" 

quan trọng cập nhật: giải pháp trên không làm việc trong mọi trường hợp. Nếu trường này có thể truy cập được và đọc qua Reflection trước khi nó được đặt lại, thì IllegalAccessException sẽ bị ném. Nó không thành công vì API phản chiếu tạo các đối tượng nội bộ FieldAccessor được lưu trữ và sử dụng lại (xem triển khai thực hiện java.lang.reflect.Field # obtainFieldAccessor (boolean)). Mã thử nghiệm mẫu không thành công:

Field f = File.class.getField("separatorChar"); f.setAccessible(true); f.get(null); 
// call setFinalStatic as before: throws IllegalAccessException 
+13

#define FALSE 1 đã trở lại và sẵn sàng khởi động lại một số lần nữa. –

+0

Tôi cuối cùng có thể kiểm tra đơn vị chống lại biến động lượng tử. –

+3

Holy crap! Sự ra đời của setAccessible là một trục vít lớn. –

0

Thay vì sử dụng File.separatorChar khai báo lớp dịch vụ của bạn, hãy gọi nó là PathBuilder hoặc một cái gì đó. Lớp này sẽ có một phương thức concatPaths() để ghép nối hai tham số (sử dụng char tách riêng của hệ điều hành). Vẻ đẹp là bạn đang viết lớp này để bạn có thể tinh chỉnh nó bất cứ lúc nào bạn muốn khi bạn đơn vị kiểm tra nó.

2

Hãy thử gọi trên một phiên bản tệp không phải trên một phiên bản của lớp Tệp

Ví dụ:

File file = ...;  
field.setChar(file,'/'); 

Bạn cũng có thể thử http://code.google.com/p/jmockit/ và thử phương pháp tĩnh FileSystem.getFileSystem(). (không biết liệu bạn có thể giả lập các biến tĩnh hay không, thông thường các lệnh này không cần thiết -> viết mã oo và chỉ sử dụng 'mockito')

1

Tôi nhận thấy điều này không trả lời câu hỏi của bạn trực tiếp, nhưng Apache Commons FileNameUtils sẽ làm xây dựng tên tập tin đa nền tảng và có thể giúp bạn tiết kiệm bằng văn bản cho lớp học của riêng bạn để làm điều này.

+0

Tôi đang deconstructing một con đường, chứ không phải là xây dựng, nhưng những gì tôi có thể làm là chuyển đổi tất cả mọi thứ để unix tách, chạy thuật toán của tôi, và sau đó chuyển đổi để tách hệ thống. Theo cách này, tôi có thể kiểm tra mà không cần phải giả lập java.io.File. –

+0

Tôi vết thương bằng cách sử dụng giải pháp này, nhưng câu trả lời dưới đây trả lời câu hỏi ban đầu của tôi, vì vậy theo tinh thần của stackoverflow, tôi chấp nhận câu trả lời khác. –

0

Bạn có thể lấy nguồn cho java.io.File và sửa đổi nó để dấu phân táchChar và dấu tách không phải là cuối cùng và thêm phương thức setSeparatorChar cập nhật hai trong số chúng, sau đó bao gồm lớp được biên dịch trong lớp khởi động của bạn.

+0

Bạn có thể phải mở rộng khả năng đó cho tất cả các triển khai java.io.FileSystem. –

+0

Lưu ý: Các ứng dụng sử dụng tùy chọn này với mục đích ghi đè một lớp trong rt.jar không nên được triển khai khi làm như vậy sẽ làm trái với giấy phép mã nhị phân của Môi trường chạy thử Java 2. –

2

Chỉ cần sử dụng/ở mọi nơi khi tạo tệp. Tôi đã làm điều đó trong 13 năm và chưa bao giờ có vấn đề gì cả. Không có gì để kiểm tra.

0

tại đây tôi sẽ đặt giá trị cho "android.os.Build.VERSION.RELEASE", trong đó VERSION là tên lớp và RELEASE là giá trị chuỗi tĩnh cuối cùng.

Nếu trường cơ bản là cuối cùng, phương pháp ném một IllegalAccessException để chúng ta cần phải sử dụng setAccessible (true), NoSuchFieldException cần được thêm vào khi bạn sử dụng field.set () phương pháp

@RunWith(PowerMockRunner.class) 
@PrepareForTest({Build.VERSION.class}) 
public class RuntimePermissionUtilsTest { 
@Test 
public void hasStoragePermissions() throws IllegalAccessException, NoSuchFieldException { 
    Field field = Build.VERSION.class.getField("RELEASE"); 
    field.setAccessible(true); 
    field.set(null,"Marshmallow"); 
} 
} 

bây giờ giá trị của string RELEASE sẽ trả về "Marshmallow".

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