2009-09-23 34 views
6

Tôi đang làm việc với một số mã hiện có, cố gắng thêm vào nó và tăng các bài kiểm tra đơn vị cho nó. Nhưng chạy vào một số vấn đề với việc kiểm tra mã.Thiết kế Constructors cho Testability

Original vị thi công:

public Info() throws Exception 
{ 
    _ServiceProperties = new ServiceProperties(); 
    _SshProperties = new SshProperties(); 
} 

Tôi biết rằng điều này là xấu, và rõ ràng là không thể kiểm chứng. Trong môi trường junit, lớp này sẽ không tạo ra mọi thời gian vì nó sẽ không thể tìm thấy các thuộc tính cần thiết để xây dựng chính nó. Bây giờ, tôi biết rằng lớp này sẽ có thể kiểm chứng được nhiều hơn với sự thay đổi đơn giản của việc di chuyển bất cứ thứ gì được bắt đầu bằng 'mới' làm tham số.

Vì vậy, tôi kết thúc với:

New vị thi công:

public Info(ServiceProperties srvProps, SshProperties sshProps) throws Exception 
{ 
    _ServiceProperties = srvProps; 
    _SshProperties = sshProps; 
} 

nào cho phép tôi đúng đơn vị kiểm tra lớp Thông tin này. Vấn đề tuy nhiên, bây giờ là tất cả những gì công việc được đẩy lên một số lớp khác:

Một số khác Class' Phương pháp:

public void useInfo() throws Exception 
{ 
    ServiceProperties srvProps = new ServiceProperties(); 
    SshProperties sshProps = new SshProperties(); 
    Info info = new Info(srvProprs, sshProprs); 
    doStuffWithInfo(info); 
} 

Bây giờ phương pháp này là không thể kiểm chứng. Tất cả những gì tôi đã làm là đẩy các công trình xây dựng của các đối tượng bất động sản này đang xảy ra, và một nơi nào đó một số đoạn mã sẽ bị mắc kẹt thực sự phải gọi là "mới".

Đây là sự chà xát đối với tôi: Tôi không thể tìm ra cách phá vỡ chuỗi sự kiện này chỉ đơn giản là đẩy những cuộc gọi "mới" này đến một nơi khác. Tôi đang thiếu gì?

Trả lời

7

Xem xét sử dụng khuôn khổ Dependency Injection chẳng hạn như Spring. Ứng dụng này của Inversion of Control có nghĩa là mỗi loại của bạn có thể tránh được cạm bẫy bạn đã thấy, để lại cấu hình cho các thành phần "dây" với nhau.

introduction to Spring (pdf) này cung cấp tổng quan toàn diện về Spring. Chương đầu tiên hoặc hai là đủ để giải thích các khái niệm.

Cũng xem Inversion of Control Containers and the Dependency Injection pattern bởi Martin Fowler

+0

Bạn cũng có thể có một cái nhìn tại của chúng tôi Tài liệu khóa học mùa xuân: http://www.trainologic.org/courses/info/2 –

0

Câu trả lời dễ nhất là Spring. Tuy nhiên một câu trả lời khác là đặt công cụ cấu hình của bạn vào JNDI. Mùa xuân theo một số cách là một câu trả lời tốt hơn, đặc biệt là nếu bạn không có bất cứ điều gì thay đổi tùy thuộc vào môi trường.

2

Bạn có ý tưởng đúng. Có lẽ điều này sẽ giúp bạn. Tôi khuyên bạn nên làm theo hai quy tắc cho tất cả các lớp học có ý nghĩa của bạn, nơi "ý nghĩa" có nghĩa là nếu bạn không làm theo các bước, sẽ khó kiểm tra, sử dụng lại hoặc duy trì lớp học hơn. Dưới đây là các quy tắc:

  1. bao giờ nhanh chóng hoặc tự thu được một sự phụ thuộc
  2. luôn chương trình để giao diện

Bạn có một sự khởi đầu tại quy tắC# 1. Bạn đã thay đổi lớp học Info của mình để không còn tạo phụ thuộc nữa. Bởi "phụ thuộc" tôi có nghĩa là các lớp khác, dữ liệu cấu hình được tải từ các tệp thuộc tính hoặc bất kỳ thứ gì, v.v.Khi bạn phụ thuộc vào cách một cái gì đó được khởi tạo bạn đang buộc lớp của bạn vào nó và làm cho nó khó khăn hơn để kiểm tra, tái sử dụng và duy trì. Vì vậy, ngay cả khi một phụ thuộc được tạo ra thông qua một nhà máy hoặc một singleton, không có lớp học của bạn tạo ra nó. Có một cái gì đó khác gọi là create() hoặc getInstance() hoặc bất kỳ thứ gì và chuyển nó vào.

Vì vậy, bạn đã chọn "cái gì khác" làm lớp học sử dụng lớp học của bạn và nhận ra có mùi hôi với nó. Các biện pháp khắc phục là thay vào đó có điểm vào để ứng dụng của bạn nhanh chóng tất cả các phụ thuộc. Trong một ứng dụng java truyền thống, đây là phương thức main() của bạn. nếu bạn nghĩ về nó, khởi tạo các lớp và nối chúng với nhau, hoặc "nối" chúng lại với nhau, là một loại logic đặc biệt: "lắp ráp ứng dụng" logic. Có tốt hơn để truyền bá logic này xuyên suốt mã của bạn hay thu thập nó ở một nơi để dễ dàng duy trì nó hơn không? Câu trả lời là thu thập nó ở một nơi là tốt hơn - không chỉ để bảo trì, nhưng hành động làm như vậy biến tất cả các lớp ý nghĩa của bạn thành các thành phần hữu ích và linh hoạt hơn.

Trong main() hoặc tương đương main(), bạn nên tạo tất cả các đối tượng bạn cần, chuyển chúng vào các nhà cung cấp và người xây dựng khác để "nối" chúng với nhau. Các bài kiểm tra đơn vị của bạn sau đó sẽ nối chúng một cách khác nhau, đi qua trong các đối tượng giả hoặc những thứ tương tự. Hành động làm tất cả điều này được gọi là "tiêm phụ thuộc". Sau khi làm như tôi nói, bạn sẽ có thể có một phương pháp xấu xí lớn main(). Đây là nơi mà công cụ tiêm phụ thuộc có thể giúp bạn và thực tế làm cho mã của bạn vô cùng linh hoạt hơn. Công cụ tôi sẽ đề nghị khi bạn nhận được đến thời điểm này, như những người khác cũng đã đề nghị, là mùa xuân. Quy tắc ít quan trọng # 2 - luôn luôn là chương trình để giao diện, vẫn rất quan trọng vì nó giúp loại bỏ tất cả các phụ thuộc vào việc triển khai, sử dụng lại dễ dàng hơn nhiều, chưa kể đến tận dụng các công cụ khác như khung đối tượng giả, khung ORM, v.v. cũng.

1

Ngay cả khuôn khổ phụ thuộc tiêm như Spring, Guice, PicoContainer, vv cần một số loại tăng cường để bạn luôn phải xây dựng một cái gì đó lên.

Tôi khuyên bạn nên sử dụng nhà cung cấp/nhà máy trả về phiên bản được cấu hình của lớp bạn. Điều này sẽ cho phép bạn thoát khỏi chế độ "-hierarchy".

0

Bạn để cho một số lớp khác có quá nhiều kiến ​​thức về lớp Thông tin và các phụ thuộc của lớp đó. A khuôn khổ tiêm phụ thuộc sẽ sử dụng một lớp nhà cung cấp. Sử dụng các loại generic ai có thể làm cho một nhà cung cấp của các đối tượng Thông tin:

interface Provider<T> { 
    T get(); 
} 

Nếu some-other-lớp học của bạn có một nhà cung cấp < Thông tin > trong constructor của nó phương pháp của bạn sẽ trông như thế:

public void useInfo() throws Exception 
{ 
    Info info = infoProvider.get(); 
    doStuffWithInfo(info); 
} 

này có loại bỏ việc xây dựng các lớp bê tông khỏi mã của bạn. Bước tiếp theo là làm cho Thông tin thành một giao diện để giúp tạo mô hình cho trường hợp thử nghiệm đơn vị cụ thể dễ dàng hơn.

Và có, điều này sẽ đẩy và đẩy tất cả mã xây dựng đối tượng thêm và tiếp tục lên. Nó sẽ dẫn đến một số mô-đun chỉ mô tả cách kết nối mọi thứ với nhau. Các "quy tắc" cho mã như vậy là nó không có các câu lệnh và vòng lặp có điều kiện.

Tôi khuyên bạn nên đọc Misko Heverys blog. Tất cả các bài thuyết trình đều hữu ích và in ra hướng dẫn để viết mã có thể kiểm tra như một rulebook nhỏ một khi bạn hiểu các quy tắc là một điều tốt.

1

Nhà xây dựng của bạn không chính xác và vấn đề không phải là khi nào/nơi mã được thực hiện, đó là về những gì mọi người khác đã đề cập: Dependency Injection. Bạn cần tạo các đối tượng SshProperties giả lập để khởi tạo đối tượng của bạn. Cách đơn giản nhất (giả sử lớp không được đánh dấu như là cuối cùng) là để mở rộng các lớp:

public class MockSshProperties extends SshProperties { 
    // implemented methods 
} 

Bạn có thể sử dụng khuôn khổ giả như Mockito:

public class Info { 

    private final sshProps; 
    private final serviceProps; 

    public Info() { 
     this(new SshProperties(), new ServiceProperties()); 
    } 

    public Info(SshProperties arg1, ServiceProperties arg2) { 
     this.sshProps = arg1; 
     this.serviceProps = arg2 
    } 
} 

public class InfoTester 
{ 
    private final static SshProperties sshProps = mock(SshProperties.class); 
    private final static ServiceProperties serviceProps = mock(ServiceProperties.class); 
    static { 
     when(sshProps.someGetMethod("something")).thenReturn("value"); 
    } 

    public static void main(String[] args) { 
     Info info = new Info(sshProps, serviceProps); 
     //do stuff 
    } 
}