Làm thế nào để đối phó với các lớp tiện ích tĩnh khi thiết kế để kiểm tra


62

Chúng tôi đang cố gắng thiết kế hệ thống của mình để có thể kiểm tra được và trong hầu hết các phần được phát triển bằng TDD. Hiện tại chúng tôi đang cố gắng giải quyết vấn đề sau:

Ở nhiều nơi khác nhau, chúng tôi cần sử dụng các phương thức trợ giúp tĩnh như ImageIO và URLEncoder (cả API Java tiêu chuẩn) và nhiều thư viện khác bao gồm chủ yếu là các phương thức tĩnh (như thư viện Apache Commons). Nhưng cực kỳ khó để kiểm tra các phương thức sử dụng các lớp trình trợ giúp tĩnh như vậy.

Tôi có một vài ý tưởng để giải quyết vấn đề này:

  1. Sử dụng một khung giả có thể giả định các lớp tĩnh (như PowerMock). Đây có thể là giải pháp đơn giản nhất nhưng bằng cách nào đó cảm thấy muốn bỏ cuộc.
  2. Tạo các lớp trình bao bọc khả thi xung quanh tất cả các tiện ích tĩnh đó để chúng có thể được đưa vào các lớp sử dụng chúng. Điều này nghe có vẻ là một giải pháp tương đối sạch nhưng tôi sợ rằng cuối cùng chúng ta sẽ tạo ra rất nhiều lớp bao bọc.
  3. Trích xuất mọi cuộc gọi đến các lớp trình trợ giúp tĩnh này thành một hàm có thể được ghi đè và kiểm tra một lớp con của lớp mà tôi thực sự muốn kiểm tra.

Nhưng tôi cứ nghĩ rằng đây chỉ là một vấn đề mà nhiều người phải đối mặt khi làm TDD - vì vậy phải có giải pháp cho vấn đề này.

Chiến lược tốt nhất để giữ cho các lớp sử dụng các trình trợ giúp tĩnh này có thể kiểm tra được là gì?


Tôi không chắc ý của bạn về "nguồn đáng tin cậy và / hoặc nguồn chính thức" nhưng tôi đồng ý với những gì @berecursive đã viết trong câu trả lời của anh ấy. PowerMock tồn tại vì một lý do và nó không nên cảm thấy như "từ bỏ" đặc biệt là nếu bạn không muốn tự mình viết các lớp bao bọc. Phương pháp cuối cùng và tĩnh là một nỗi đau khi thử nghiệm đơn vị (và TDD). Cá nhân? Tôi sử dụng phương pháp 2 mà bạn mô tả.
Deco

"Nguồn đáng tin cậy và / hoặc chính thức" chỉ là một trong những lựa chọn bạn có thể chọn khi bắt đầu tiền thưởng cho một câu hỏi. Điều tôi thực sự muốn nói: Kinh nghiệm từ hoặc tham khảo các bài báo được viết bởi các chuyên gia TDD. Hoặc bất kỳ loại kinh nghiệm nào của một người đã phải đối mặt với cùng một vấn đề ...

Câu trả lời:


34

(Không có nguồn "chính thức" nào ở đây, tôi sợ - không giống như có một đặc điểm kỹ thuật để kiểm tra tốt. Chỉ là ý kiến ​​của tôi, hy vọng sẽ hữu ích.)

Khi các phương thức tĩnh này đại diện cho các phụ thuộc chính hãng , hãy tạo các hàm bao. Vì vậy, đối với những thứ như:

  • Hình ảnh
  • Máy khách HTTP (hoặc bất cứ thứ gì khác liên quan đến mạng)
  • Hệ thống tập tin
  • Lấy thời gian hiện tại (ví dụ yêu thích của tôi về nơi giúp tiêm phụ thuộc)

... nó có ý nghĩa để tạo ra một giao diện.

Nhưng nhiều phương thức trong Apache Commons có lẽ không nên bị chế giễu / làm giả. Ví dụ: lấy một phương thức để nối với nhau một danh sách các chuỗi, thêm dấu phẩy giữa chúng. Không có điểm nào để chế nhạo những điều này - hãy để cuộc gọi tĩnh thực hiện công việc bình thường của nó. Bạn không muốn hoặc cần thay thế hành vi bình thường; bạn không xử lý tài nguyên bên ngoài hoặc thứ gì đó khó xử lý, đó chỉ là dữ liệu. Kết quả là có thể dự đoán được và bạn sẽ không bao giờ muốn nó là bất cứ điều gì khác ngoài những gì nó sẽ cung cấp cho bạn.

Tôi nghi ngờ rằng đã loại bỏ tất cả các cuộc gọi tĩnh thực sự là phương thức tiện lợi với các kết quả "thuần túy" có thể dự đoán được (như mã hóa base64 hoặc URL) thay vì nhập điểm vào một mớ hỗn độn lớn của các phụ thuộc logic (như HTTP), bạn sẽ thấy hoàn toàn thực tế để làm điều đúng với các phụ thuộc chính hãng.


20

Đây chắc chắn là một câu hỏi / câu trả lời có ý kiến ​​nhưng đối với những gì đáng giá tôi nghĩ tôi đã ném hai xu của mình. Về phương pháp TDD, phương pháp 2 chắc chắn là cách tiếp cận với bức thư. Đối số cho phương thức 2 là nếu bạn từng muốn thay thế việc thực hiện một trong các lớp đó - giả sử một ImageIOthư viện tương đương - thì bạn có thể làm điều đó trong khi duy trì sự tin tưởng vào các lớp tận dụng mã đó.

Tuy nhiên, như bạn đã đề cập, nếu bạn sử dụng nhiều phương thức tĩnh thì cuối cùng bạn sẽ viết rất nhiều mã trình bao bọc. Điều này có thể không phải là một điều xấu trong thời gian dài. Về khả năng bảo trì chắc chắn có những lập luận cho điều này. Cá nhân tôi thích cách tiếp cận này.

Phải nói rằng, PowerMock tồn tại vì một lý do. Một vấn đề khá nổi tiếng là việc kiểm tra khi các phương thức tĩnh có liên quan là rất nghiêm trọng, do đó sự ra đời của PowerMock. Tôi nghĩ rằng bạn cần cân nhắc các lựa chọn của mình về mức độ công việc sẽ bao trùm tất cả các lớp trợ giúp của bạn so với sử dụng PowerMock. Tôi không nghĩ rằng việc 'từ bỏ' để sử dụng PowerMock - Tôi chỉ cảm thấy rằng việc bao bọc các lớp học cho phép bạn linh hoạt hơn trong một dự án lớn. Các hợp đồng công khai (giao diện) càng nhiều, bạn có thể cung cấp sự phân tách rõ ràng hơn giữa ý định và việc thực hiện.


1
Một vấn đề bổ sung mà tôi không chắc chắn về: Khi triển khai các trình bao bọc, bạn sẽ thực hiện tất cả các phương thức của lớp được gói hay chỉ là các phương thức hiện đang cần?

3
Theo những ý tưởng nhanh, bạn nên làm những việc đơn giản nhất có hiệu quả và tránh làm những công việc bạn không cần. Do đó, bạn chỉ nên phơi bày những phương pháp bạn thực sự cần.
Assaf Stone

@AssafStone đã đồng ý

Hãy cẩn thận với PowerMock, tất cả các thao tác lớp phải thực hiện để chế nhạo các phương thức đi kèm với rất nhiều chi phí. Các bài kiểm tra của bạn sẽ chậm hơn nhiều nếu bạn sử dụng nó rộng rãi.
bcarlso

Bạn có thực sự phải viết nhiều trình bao bọc nếu kết hợp việc kiểm tra / di chuyển của bạn với việc áp dụng thư viện DI / IoC không?

4

Để tham khảo cho tất cả những ai cũng đang giải quyết vấn đề này và tình cờ gặp câu hỏi này, tôi sẽ mô tả cách chúng tôi quyết định giải quyết vấn đề:

Về cơ bản, chúng tôi đang đi theo đường dẫn được nêu là # 2 (các lớp bao bọc cho các tiện ích tĩnh). Nhưng chúng tôi chỉ sử dụng chúng khi quá phức tạp để cung cấp cho tiện ích dữ liệu cần thiết để tạo ra đầu ra mong muốn (nghĩa là khi chúng tôi hoàn toàn phải chế giễu phương thức).

Điều này có nghĩa là chúng ta không phải viết một trình bao bọc cho một tiện ích đơn giản như Apache Commons StringEscapeUtils(vì các chuỗi họ cần có thể dễ dàng cung cấp) và chúng ta không sử dụng giả cho các phương thức tĩnh (nếu chúng ta nghĩ rằng chúng ta cần phải viết một lớp bao bọc và sau đó mô phỏng một thể hiện của trình bao bọc).



1

Tôi làm việc cho một công ty bảo hiểm lớn và mã nguồn của chúng tôi có tới 400 MB tệp java thuần túy. Chúng tôi đã phát triển toàn bộ ứng dụng mà không nghĩ về TDD. Từ tháng một năm nay, chúng tôi đã bắt đầu với thử nghiệm Junit cho từng thành phần riêng lẻ.

Giải pháp tốt nhất trong bộ phận của chúng tôi là sử dụng các đối tượng Mock trên một số phương thức JNI có thể tin cậy hệ thống (được viết bằng C) và do đó bạn không thể ước tính chính xác kết quả mỗi lần trên mọi HĐH. Chúng tôi không có lựa chọn nào khác ngoài việc sử dụng các lớp bị nhạo báng và triển khai cụ thể các phương thức JNI cho mục đích kiểm tra từng mô-đun riêng lẻ của ứng dụng cho mọi HĐH mà chúng tôi hỗ trợ.

Nhưng nó thực sự rất nhanh và cho đến nay nó vẫn hoạt động khá tốt. Tôi khuyên bạn nên dùng nó - http://www.easymock.org/


1

Các đối tượng tương tác với nhau để hoàn thành mục tiêu, khi bạn có đối tượng khó kiểm tra vì môi trường (điểm cuối dịch vụ web, lớp dao truy cập DB, bộ điều khiển xử lý tham số yêu cầu http) hoặc sau đó bạn muốn kiểm tra đối tượng của mình bạn chế nhạo những đồ vật đó.

Sự cần thiết của các phương pháp thống kê chế nhạo là một mùi khó chịu, bạn phải thiết kế ứng dụng của mình theo hướng đối tượng hơn và các phương thức tĩnh tiện ích thử nghiệm đơn vị không thêm nhiều giá trị cho dự án, lớp bao bọc là một cách tiếp cận tốt tùy thuộc vào tình huống, nhưng hãy thử để kiểm tra các đối tượng sử dụng các phương thức tĩnh.


1

Đôi khi tôi sử dụng tùy chọn 4

  1. Sử dụng mô hình chiến lược. Tạo một lớp tiện ích với các phương thức tĩnh ủy quyền thực hiện cho một thể hiện của giao diện có thể cắm được. Mã một trình khởi tạo tĩnh cắm vào một triển khai cụ thể. Cắm một thực hiện giả để thử nghiệm.

Một cái gì đó như thế này.

public class DateUtil {
    public interface ITimestampGenerator {
        long getUtcNow();
    }

    class ConcreteTimestampGenerator implements ITimestampGenerator {
        public long getUtcNow() { return System.currentTimeMillis(); }
    }

    private static ITimestampGenerator timestampGenerator;

    static {
        timestampGenerator = new ConcreteTimeStampGenerator;
    }

    public static DateTime utcNow() {
        return new DateTime(timestampGenerator.getUtcNow(), DateTimeZone.UTC);
    }

    public static void setTimestampGenerator(ITimestampGenerator t) {...}

    // plus other util routines, which may or may not use the timestamp generator 
}

Điều tôi thích về cách tiếp cận này là nó giữ cho các phương thức tiện ích tĩnh, điều này chỉ cảm thấy đúng với tôi khi tôi đang cố gắng sử dụng lớp trong suốt mã.

Math.sum(17, 29, 42);
// vs
new Math().sum(17, 29, 42);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.