2008-11-23 11 views
13

Có cách nào tốt để hủy đơn vị kiểm tra đơn vị không? Giống như nói rằng tôi có một lớp học như thế này (giả tạo) ví dụ:Đơn vị kiểm tra destructors?

class X 
{ 
private: 
    int *x; 

public: 
    X() 
    { 
     x = new int; 
    } 

    ~X() 
    { 
     delete x; 
    } 

    int *getX() {return x;} 
    const int *getX() const {return x;} 
}; 

Có cách nào tốt để kiểm tra đơn vị này để đảm bảo x bị xóa mà không làm lộn xộn lên tập tin hpp tôi với #ifdef kiểm tra hoặc phá vỡ đóng gói? Vấn đề chính mà tôi thấy là rất khó để nói nếu x thực sự bị xóa, đặc biệt là vì đối tượng nằm ngoài phạm vi tại thời điểm destructor được gọi.

Trả lời

7

Có thể có điều gì đó để nói về tiêm phụ thuộc. Thay vì tạo một đối tượng (trong trường hợp này là int, nhưng trong một trường hợp không có nhiều khả năng là một kiểu do người dùng định nghĩa) trong hàm tạo của nó, đối tượng được truyền vào như một tham số cho hàm tạo. Nếu đối tượng được tạo sau, thì một nhà máy được chuyển đến nhà xây dựng của X.

Sau đó, khi bạn thử nghiệm đơn vị, bạn chuyển vào một đối tượng giả lập (hoặc một nhà máy sản xuất giả tạo đối tượng giả) và trình hủy ghi lại thực tế là nó đã được gọi. Kiểm tra không thành công nếu không. Tất nhiên bạn không thể giả lập (hoặc thay thế) một loại nội trang, vì vậy trong trường hợp cụ thể này không tốt, nhưng nếu bạn xác định đối tượng/nhà máy với giao diện thì bạn có thể.

Kiểm tra rò rỉ bộ nhớ trong các thử nghiệm đơn vị thường có thể được thực hiện ở mức cao hơn, như những người khác đã nói. Nhưng điều đó chỉ kiểm tra rằng một destructor được gọi, nó không chứng minh rằng phải destructor được gọi.Vì vậy, nó sẽ không ví dụ bắt một khai báo "ảo" bị mất trên destructor của kiểu thành viên x (một lần nữa, không liên quan nếu nó chỉ là một int).

+0

Chính xác những gì tôi đã nói –

+1

Đúng, và ngay chính xác cùng một lúc.Tôi đoán tôi đã thắng vì bạn dừng lại để viết một mẫu mã :-) –

0

Một số trình biên dịch sẽ ghi đè bộ nhớ đã xóa với mẫu đã biết trong chế độ gỡ lỗi để giúp phát hiện quyền truy cập vào các con trỏ lơ lửng. Tôi biết Visual C++ được sử dụng để sử dụng 0xDD, nhưng tôi đã không sử dụng nó trong một thời gian.

Trong trường hợp thử nghiệm của bạn, bạn có thể lưu một bản sao của x, để cho nó đi ra khỏi phạm vi và chắc chắn rằng * x == 0xDDDDDDDD:

void testDestructor() 
{ 
    int *my_x; 
    { 
     X object; 
     my_x = object.getX(); 
    } 
    CPPUNIT_ASSERT(*my_x == 0xDDDDDDDD); 
} 
+0

Tôi coi thường ý tưởng này. Bạn không thể kiểm tra đơn vị mã với tối ưu hóa được kích hoạt hoặc nếu trình biên dịch của bạn thay đổi –

+0

Tôi đồng ý rằng nó không tuyệt vời cho những lý do bạn đề cập, và bạn không thể truy cập con trỏ riêng. Nói chung, tôi nghĩ rằng ý tưởng của bạn (và onebyone's) là tốt hơn, nhưng nó không phải luôn luôn tầm thường để sử dụng một đối tượng giả. –

1

tôi có xu hướng đi với một "Bằng bất cứ phương tiện cần thiết "tiếp cận để thử nghiệm. Nếu nó cần một thử nghiệm, tôi sẵn sàng để rò rỉ trừu tượng, phá vỡ đóng gói, và hack ... bởi vì mã thử nghiệm là tốt hơn so với mã đẹp. Tôi thường sẽ đặt tên cho các phương thức phá vỡ điều này như VaildateForTesting hoặc OverrideForTesting để làm rõ rằng vi phạm đóng gói chỉ có nghĩa là để thử nghiệm.

Tôi không biết cách nào khác để làm điều này trong C++ hơn là có cuộc gọi destructor thành một singleton để đăng ký rằng nó đã bị hủy. Tôi đã đưa ra một phương pháp để làm một cái gì đó tương tự như thế này trong C# bằng cách sử dụng một tham chiếu yếu (tôi không vi phạm đóng gói hoặc trừu tượng với phương pháp này). Tôi không đủ sáng tạo để đưa ra một sự tương tự với C++, nhưng bạn có thể. Nếu nó giúp, tuyệt vời, nếu không, xin lỗi.

http://houseofbilz.com/archive/2008/11/11/writing-tests-to-catch-memory-leaks-in-.net.aspx

0

Không phải là một nền tảng bất khả tri đề nghị, nhưng trong quá khứ tôi đã thực hiện các cuộc gọi vào chức năng kiểm tra đống của CRT trong kiểm tra đơn vị, để xác minh rằng không có thêm bộ nhớ được phân bổ vào cuối của một bài kiểm tra (hoặc có lẽ toàn bộ các bài kiểm tra) hơn là bắt đầu. Bạn cũng có thể làm điều gì đó tương tự với thiết bị của nền tảng của mình, để kiểm tra số lượng xử lý, v.v.

1

Trong ví dụ này, hãy xác định và đặt công cụ mới và xóa toàn cầu của riêng bạn.

Để tránh #ifdef, tôi đã thực hiện các lớp thử nghiệm cho bạn bè. Bạn có thể đặt/lưu/nhận trạng thái theo yêu cầu để xác minh kết quả cuộc gọi.

+0

Ý tưởng này không mở rộng. Điều gì sẽ xảy ra nếu bạn cần kiểm tra nhiều lớp khác nhau, bạn sẽ triển khai các phiên bản khác nhau của toàn cầu mới và xóa của bạn? Điều gì sẽ xảy ra nếu bạn là mã thử nghiệm đơn vị đã thay thế :: mới và :: xóa? –

+0

Tôi không biết những gì người đàn ông rơm tưởng tượng của bạn. Thiết bị được thiết kế để trả lời các câu hỏi như "là x vẫn được phân bổ"? Các câu trả lời khác ở đây tận dụng một phiên bản cụ thể của nền tảng của thiết bị bộ nhớ thay vì tự kiểm soát. –

+0

Thiết kế của bạn là thiết kế kém. Tại sao bạn phải sửa đổi các lớp học của bạn được kiểm tra để khai thác thử nghiệm của bạn có thể làm việc với nó? Nếu bạn làm cho các lớp học của bạn bắt đầu từ đầu (xem ví dụ của tôi), bạn không cần phải sử dụng những thứ như vậy –

5

Tôi nghĩ rằng vấn đề của bạn là ví dụ hiện tại của bạn không thể kiểm tra được. Vì bạn muốn biết nếu x đã bị xóa, bạn thực sự cần phải có thể thay thế x bằng một mô hình. Đây có lẽ là một chút OTT cho một int nhưng tôi đoán trong ví dụ thực sự của bạn, bạn có một số lớp khác. Để làm cho nó kiểm chứng, các nhà xây dựng X cần phải yêu cầu các đối tượng thực hiện các giao diện int:

template<class T> 
class X 
{ 
    T *x; 
    public: 
    X(T* inx) 
    : x(inx) 
    { 
    } 

    // etc 
}; 

Bây giờ nó trở nên đơn giản để chế nhạo trong giá trị cho x, và các mô hình có thể xử lý kiểm tra để tiêu huỷ đúng.

Xin vui lòng không chú ý đến những người nói rằng bạn nên phá vỡ đóng gói hoặc nghỉ mát để hack khủng khiếp để kết quả trong mã testable. Mặc dù đúng là mã được thử nghiệm tốt hơn mã chưa được kiểm tra, mã có thể kiểm tra là tốt nhất và nó luôn dẫn đến mã rõ ràng hơn với ít hacks và khớp nối thấp hơn.

+0

Đó là cách duy nhất để làm điều đó cho một int, nhưng một mẫu lớp không phải là một sự thay thế nhỏ cho một lớp. Sử dụng các mẫu ở khắp mọi nơi thường giới hạn các tùy chọn của bạn cho DLL, giảm phụ thuộc tiêu đề, vv Tất cả phụ thuộc vào trình biên dịch của khóa học: Tôi nói với một số hỗ trợ mẫu tốt hơn liên kết hơn những người khác. –

+0

Hãy suy nghĩ bạn, có lẽ tôi chỉ là một người gắt gỏng vì tôi nói bạn không thể chế nhạo một loại nội trang, như bạn đã chỉ ra là sai, bởi vì bạn có thể làm theo cách này thông qua các mẫu và một lớp thực hiện toàn bộ int giao diện (không tầm thường, nhưng bạn chỉ phải làm điều đó một lần. Đừng quên numeric_limits). –

+0

Vâng, bạn sẽ không phải làm cho nó templatised. Nếu bạn loại bỏ các mẫu và thay đổi T để int ở khắp mọi nơi trong ví dụ của tôi nó vẫn sẽ được testable –

1

Nó sẽ không liên quan đến người đã đặt câu hỏi nhưng có thể hữu ích cho những người đọc bài này. Tôi đã được hỏi một câu hỏi tương tự trong một cuộc phỏng vấn xin việc.

Giả sử bộ nhớ là có hạn, bạn có thể thử phương pháp này:

  1. Cấp phát bộ nhớ cho đến khi việc phân bổ không thành công với ra thông điệp bộ nhớ (trước khi chạy bất kỳ thử nghiệm liên quan đến các destructor) và lưu kích thước của bộ nhớ có sẵn trước khi chạy thử nghiệm.
  2. Chạy thử nghiệm (gọi hàm khởi tạo và thực hiện một số hành động như bạn muốn trên phiên bản mới).
  3. Chạy trình phá hủy.
  4. Chạy lại phần phân bổ (như ở bước 1) nếu bạn có thể phân bổ chính xác cùng một bộ nhớ khi bạn quản lý phân bổ trước khi chạy thử nghiệm, trình phá hủy hoạt động tốt.

Phương pháp này hoạt động (hợp lý) khi bạn có bộ nhớ giới hạn nhỏ, nếu không có vẻ như không hợp lý, ít nhất là theo ý kiến ​​của tôi.