2008-09-14 30 views
35

Phương châm của tôi cho Java là "chỉ vì Java có các khối tĩnh, điều đó không có nghĩa là bạn nên sử dụng chúng." Truyện cười sang một bên, có rất nhiều thủ thuật trong Java mà làm cho thử nghiệm một cơn ác mộng. Hai trong số những thứ tôi ghét nhất là Lớp ẩn danh và Khối tĩnh. Chúng tôi có rất nhiều mã di sản sử dụng các khối tĩnh và đây là một trong những điểm gây phiền nhiễu trong việc đẩy các bài kiểm tra viết đơn vị của chúng tôi. Mục tiêu của chúng tôi là có thể viết các bài kiểm tra đơn vị cho các lớp phụ thuộc vào việc khởi tạo tĩnh này với các thay đổi mã tối thiểu.Chế nhạo các khối tĩnh trong Java

Cho đến nay đề xuất của tôi cho đồng nghiệp của tôi là di chuyển phần thân của khối tĩnh vào một phương pháp tĩnh riêng và gọi nó là staticInit. Phương thức này có thể được gọi từ bên trong khối tĩnh. Đối với bài kiểm tra đơn vị, một lớp khác phụ thuộc vào lớp này có thể dễ dàng giả lập staticInit với JMockit để không làm bất cứ điều gì. Hãy xem ví dụ này.

public class ClassWithStaticInit { 
    static { 
    System.out.println("static initializer."); 
    } 
} 

sẽ được thay đổi để

public class ClassWithStaticInit { 
    static { 
    staticInit(); 
    } 

    private static void staticInit() { 
    System.out.println("static initialized."); 
    } 
} 

Vì vậy mà chúng ta có thể làm như sau trong một JUnit.

public class DependentClassTest { 
    public static class MockClassWithStaticInit { 
    public static void staticInit() { 
    } 
    } 

    @BeforeClass 
    public static void setUpBeforeClass() { 
    Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class); 
    } 
} 

Tuy nhiên giải pháp này cũng đi kèm với các vấn đề của riêng nó. Bạn không thể chạy DependentClassTestClassWithStaticInitTest trên cùng một JVM vì bạn thực sự muốn khối tĩnh chạy cho ClassWithStaticInitTest.

Bạn sẽ làm gì để hoàn thành nhiệm vụ này? Hoặc bất kỳ giải pháp nào tốt hơn, không dựa trên JMockit mà bạn cho rằng sẽ hoạt động hiệu quả hơn?

Trả lời

5

Khi tôi chạy vào vấn đề này, tôi thường làm điều tương tự mà bạn mô tả, ngoại trừ tôi làm cho phương pháp tĩnh được bảo vệ vì vậy tôi có thể gọi nó bằng tay. Ngày đầu này, tôi chắc chắn rằng phương pháp có thể được gọi nhiều lần mà không có vấn đề (nếu không nó không tốt hơn so với initializer tĩnh như xa như các xét nghiệm đi).

Điều này hoạt động khá tốt, và tôi thực sự có thể kiểm tra rằng phương thức khởi tạo tĩnh làm những gì tôi mong đợi/muốn nó làm. Đôi khi nó chỉ đơn giản là có một số mã khởi tạo tĩnh, và nó chỉ là không có giá trị nó để xây dựng một hệ thống quá phức tạp để thay thế nó.

Khi tôi sử dụng cơ chế này, tôi đảm bảo rằng tài liệu rằng phương pháp được bảo vệ chỉ được hiển thị cho mục đích thử nghiệm, với hy vọng rằng nó sẽ không được sử dụng bởi các nhà phát triển khác. Điều này tất nhiên có thể không phải là một giải pháp khả thi, ví dụ nếu giao diện của lớp được hiển thị bên ngoài (hoặc là một thành phần phụ của một loại nào đó cho các nhóm khác hoặc dưới dạng khung công khai). Đó là một giải pháp đơn giản cho vấn đề này và không yêu cầu thư viện của bên thứ ba để thiết lập (mà tôi thích).

4

Âm thanh với tôi như bạn đang điều trị triệu chứng: thiết kế kém với phụ thuộc vào khởi tạo tĩnh. Có lẽ một số refactoring là giải pháp thực sự. Có vẻ như bạn đã thực hiện một phép tái cấu trúc nhỏ với hàm staticInit() của mình, nhưng có lẽ hàm đó cần phải được gọi từ hàm tạo, không phải từ trình khởi tạo tĩnh. Nếu bạn có thể làm được với giai đoạn khởi tạo tĩnh, bạn sẽ tốt hơn. Chỉ bạn mới có thể đưa ra quyết định này (Tôi không thể thấy mã số của bạn) nhưng một số việc tái cấu trúc chắc chắn sẽ hữu ích.

Đối với chế nhạo, tôi sử dụng EasyMock, nhưng tôi đã gặp phải vấn đề tương tự. Tác dụng phụ của bộ khởi tạo tĩnh trong mã kế thừa làm cho việc thử nghiệm trở nên khó khăn. Câu trả lời của chúng tôi là cấu trúc lại bộ khởi tạo tĩnh.

+0

Bạn không thể giả lập phương thức 'static' hoặc' private' bằng EasyMock. Di chuyển cơ thể của bộ khởi tạo tĩnh là như xa của một refactoring chúng tôi có thể làm cho bây giờ. –

+0

Nếu lớp được kiểm tra có phương thức init/private tĩnh, bạn muốn nó được gọi. Không vấn đề gì. Nhưng nếu đó là lớp được chế nhạo, không có vấn đề gì cho việc giả lập dễ dàng: nó sẽ không được gọi vì không có THỰC HIỆN. Bạn có thể giả lập một cách an toàn các giao diện công khai như thể các công cụ riêng tư không tồn tại. –

1

Tôi cho rằng bạn thực sự muốn một số loại nhà máy thay vì bộ khởi tạo tĩnh. Một số kết hợp của một nhà máy đơn giản và trừu tượng có thể sẽ giúp bạn có được chức năng tương tự như ngày hôm nay, và với khả năng kiểm tra tốt, nhưng điều đó sẽ bổ sung khá nhiều mã đĩa nồi hơi, do đó, có thể tốt hơn để chỉ cần cố gắng để tái cấu trúc các công cụ tĩnh đi hoàn toàn hoặc nếu bạn có thể ít nhất có được đi với một số giải pháp ít phức tạp.

Rất khó để biết liệu có thể không nhìn thấy mã của bạn hay không.

3

Bạn có thể viết mã thử nghiệm của mình trong Groovy và dễ dàng thử phương pháp tĩnh bằng cách sử dụng lập trình meta.

Math.metaClass.'static'.max = { int a, int b -> 
    a + b 
} 

Math.max 1, 2 

Nếu bạn không thể sử dụng Groovy, bạn sẽ thực sự cần phải tái cấu trúc mã (có thể để tiêm thứ gì đó như bộ khởi tạo).

Kind Regards

+0

Tôi yêu Groovy nhưng tiếc là tất cả mã kiểm tra của chúng tôi phải nằm trong JUnit. –

+0

Cem, Nhưng bạn có thể viết các bài kiểm tra junit (thậm chí junit4) bằng tiếng rêu. ;-) Kind Regards – marcospereira

1

Tôi không phải là siêu kiến ​​thức trong khuôn khổ Mock vì vậy xin vui lòng sửa tôi nếu tôi sai nhưng không thể bạn có thể có hai đối tượng Mock khác nhau để trang trải các tình huống mà bạn đề cập đến? Chẳng hạn như

public static class MockClassWithEmptyStaticInit { 
    public static void staticInit() { 
    } 
} 

public static class MockClassWithStaticInit { 
    public static void staticInit() { 
    System.out.println("static initialized."); 
    } 
} 

Sau đó, bạn có thể sử dụng chúng trong trường hợp thử nghiệm khác nhau của bạn

@BeforeClass 
public static void setUpBeforeClass() { 
    Mockit.redefineMethods(ClassWithStaticInit.class, 
         MockClassWithEmptyStaticInit.class); 
} 

@BeforeClass 
public static void setUpBeforeClass() { 
    Mockit.redefineMethods(ClassWithStaticInit.class, 
         MockClassWithStaticInit.class); 
} 

tương ứng.

+0

'System.out.println (" static initialized. ");' Là những gì bộ khởi tạo tĩnh đang làm ngay từ đầu. Tại sao chúng ta nên thử những gì nó đã làm với cùng một điều? –

+0

Cem, tôi nghĩ bạn đã bỏ lỡ quan điểm của tôi. Tôi đang cố gắng trả lời mối quan tâm "Tuy nhiên giải pháp này cũng đi kèm với các vấn đề riêng của nó. Bạn không thể chạy DependentClassTest và ClassWithStaticInitTest trên cùng một JVM vì bạn thực sự muốn khối tĩnh để chạy cho ClassWithStaticInitTest." – martinatime

+0

Về cơ bản, bạn có hai đối tượng Mock khác nhau để Mock ClassWithStaticInit một để sử dụng với DependentClassTest của bạn và một để sử dụng với ClassWithStaticInitTest. – martinatime

10

Điều này sẽ tham gia vào JMockit "Nâng cao" hơn. Hóa ra, bạn có thể xác định lại các khối khởi tạo tĩnh trong JMockit bằng cách tạo phương thức public void $clinit(). Vì vậy, thay vì làm cho sự thay đổi này

public class ClassWithStaticInit { 
    static { 
    staticInit(); 
    } 

    private static void staticInit() { 
    System.out.println("static initialized."); 
    } 
} 

chúng tôi cũng có thể rời khỏi ClassWithStaticInit như là và làm như sau trong MockClassWithStaticInit:

public static class MockClassWithStaticInit { 
    public void $clinit() { 
    } 
} 

Điều này sẽ thực tế cho phép chúng tôi không thực hiện bất kỳ thay đổi trong các lớp hiện có.

+0

Đây phải là câu trả lời được chấp nhận. Là một bổ sung; truy cập công khai không bắt buộc đối với $ clinit với chú thích @Mock. – user2219808

32

PowerMock là một khuôn mẫu giả khác mở rộng EasyMock và Mockito. Với PowerMock bạn có thể dễ dàng remove unwanted behavior từ một lớp, ví dụ như một bộ khởi tạo tĩnh. Trong ví dụ của bạn, bạn chỉ cần thêm các chú thích sau đây để kiểm tra trường hợp JUnit của bạn:

@RunWith(PowerMockRunner.class) 
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit") 

PowerMock không sử dụng dịch vụ Java và do đó không yêu cầu sửa đổi các thông số khởi động JVM. Bạn đơn giản thêm tệp jar và chú thích ở trên.

9

Thỉnh thoảng, tôi tìm các trình khởi tạo tĩnh trong các lớp mà mã của tôi phụ thuộc vào.Nếu tôi không thể cấu trúc lại mã, tôi sử dụng @SuppressStaticInitializationFor chú thích PowerMock 's để ngăn chặn initializer tĩnh:

@RunWith(PowerMockRunner.class) 
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit") 
public class ClassWithStaticInitTest { 

    ClassWithStaticInit tested; 

    @Before 
    public void setUp() { 
     tested = new ClassWithStaticInit(); 
    } 

    @Test 
    public void testSuppressStaticInitializer() { 
     asserNotNull(tested); 
    } 

    // more tests... 
} 

Đọc thêm về suppressing unwanted behaviour.

Tuyên bố từ chối trách nhiệm: PowerMock là một dự án mã nguồn mở được phát triển bởi hai đồng nghiệp của tôi.

0

Không thực sự là câu trả lời, nhưng chỉ tự hỏi - không có cách nào để "đảo ngược" cuộc gọi đến Mockit.redefineMethods?
Nếu không có phương pháp rõ ràng như vậy tồn tại, không nên thực hiện nó một lần nữa trong thời trang sau đây làm các trick?

Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class); 

Nếu một phương pháp như vậy tồn tại, bạn có thể thực hiện nó trong lớp @AfterClass phương pháp, và thử nghiệm ClassWithStaticInitTest với khối khởi tạo 'bản gốc' tĩnh, như không có gì đã thay đổi, từ cùng một JVM.

Đây chỉ là linh cảm, vì vậy tôi có thể thiếu một thứ gì đó.

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