2010-06-21 60 views
6

Gần đây, tôi đã hỏi (và trả lời) một câu hỏi về StackOverflow về lý do tại sao một bài kiểm tra đơn vị sẽ hoạt động khi tự chạy và sau đó không thường xuyên khi chạy với toàn bộ lô kiểm thử đơn vị. Xem tại đây: SQL Server and TransactionScope (with MSDTC): Sporadically can't get connectionBộ thu gom rác hoạt động như thế nào với các bài kiểm tra đơn vị?

Kiểm tra đơn vị đi qua khi chạy một lần và sau đó không thành công khi chạy cùng nhau là dấu hiệu cổ điển cho biết có điều gì đó nghiêm trọng sai với mã.

Tôi phát hiện ra rằng có một chút rò rỉ tài nguyên. Bởi vì một lỗi tinh vi gây ra các kết nối đến một máy chủ SQL không được phát hành, tôi đã chạy ra khỏi các kết nối và thử nghiệm đã thất bại. AFAIK, công trình này gần giống như rò rỉ bộ nhớ; các kết nối được cấp phát từ một hồ bơi kết nối và không bao giờ giải phóng cũng giống như bộ nhớ có thể được cấp phát và sau đó không được giải phóng.

Tuy nhiên, điều này khiến tôi có một câu hỏi khó hiểu? Sự khác nhau giữa việc chạy thử nghiệm của tôi từng cái một và chạy chúng như một bộ phần mềm là gì? Nếu các bài kiểm tra vượt qua khi chạy một lần và sau đó thất bại khi chạy cùng nhau, thì phải có một số loại dọn dẹp xảy ra giữa các lần chạy thử chỉ xảy ra khi các phép thử chạy một lần.

Tôi phỏng đoán rằng điều này có thể liên quan đến những gì trình thu thập rác .net thực hiện hoặc không thực hiện giữa các thử nghiệm. Trong một trường hợp, các kết nối được giải phóng giữa các thử nghiệm; trong trường hợp khác, họ không phải vậy.

Tôi làm cách nào để giải thích điều này?

Cập nhật: Để những người bạn hỏi về chi tiết cụ thể của mã này, nó khá đơn giản. Tôi khai báo một đối tượng TransactionScope mới trong phương pháp Thiết lập của tôi và xử lý nó theo phương pháp Teardown của tôi. Tuy nhiên, bài kiểm tra vấn đề là một thử nghiệm dựa trên dữ liệu với 100 trường hợp thử nghiệm; mã được thử nghiệm đã điền một đối tượng SqlDataReader từ một câu lệnh chọn sử dụng lớp SqlHelper và sau đó không gọi phương thức đóng trên SqlDataReader. Bởi vì tôi đã sử dụng lớp SqlHelper để có được SqlDataReader, tôi mong rằng các kết nối đã được xử lý cho tôi. Không phải vậy!

Nhưng để làm rõ, tôi không hỏi về trường hợp cụ thể của tôi. Điều tôi muốn biết là: thông thường, các nguồn lực được giải phóng như thế nào giữa các bài kiểm tra? Tôi sẽ tưởng tượng điều này sẽ là một số ứng dụng của bộ thu gom rác. Tôi tự hỏi liệu bộ thu gom rác vẫn có thể làm sạch một thử nghiệm trước đó khi chạy thử nghiệm tiếp theo (điều kiện chủng tộc?)

Cập nhật: Những gì tôi biết về thu gom rác với Bài kiểm tra đơn vị. Theo sự tò mò của riêng tôi, tôi đã rút ra các bài kiểm tra đơn vị đã thất bại vì một kết nối đã được mở bởi đối tượng SqlDataReader. Tôi đã thử thêm System.GC.Collect() vào cuối mỗi bài kiểm tra. Điều này đã giải phóng thành công các kết nối, nhưng áp đặt một hình phạt hiệu suất ~ 50%.

+0

Nếu bạn đang hết kết nối vì bạn không phát hành đủ giữa các thử nghiệm, tôi nghi ngờ rằng có vấn đề với phương pháp rách. Khi chạy chúng cùng một lúc, hệ điều hành sẽ chăm sóc một số giọt nước mắt, nhưng chạy như một bộ sử dụng tài nguyên của bạn vẫn tồn tại lâu hơn. – MikeD

+1

Có thể bạn chỉ cần thêm một số lệnh gọi 'Dispose' hoặc' Close' ở đâu đó? – Gabe

Trả lời

1

Bộ sưu tập rác là tác vụ nền định kỳ. Cụ thể, có một chuỗi không làm gì ngoài việc hoàn thành các đối tượng đã được đánh dấu là đã chết. Bằng cách chạy một bài kiểm tra tại một thời điểm, bạn đang cho chủ đề đó một cơ hội hoàn thành các đối tượng để đóng các kết nối.

+0

Thật sao?Bạn có thể chỉ cho tôi một số tài liệu cho thấy GC thường hoạt động trên chỉ riêng của nó không? –

+1

http://stackoverflow.com/questions/318462/how-to-identify-the-gc-finalizer-thread –

+0

GC thực sự có thể dừng tất cả các luồng một thời gian ngắn, nhưng nó cũng chạy finalizers trong một chủ đề, như được giải thích trong liên kết. –

3

Điều đó nghe có vẻ khả thi, có. Nó sẽ không gây ngạc nhiên cho khung kiểm thử đơn vị để yêu cầu bộ thu gom rác chạy giữa các phép thử.

Cách khác, mô hình thực thi khác có thể tự nhiên kích hoạt bộ sưu tập rác khi chúng chạy cái khác. Vấn đề với việc phân tích loại điều này là tất cả đều rất năng động - và sẽ thay đổi từ chạy thử nghiệm đến chạy thử nghiệm.

Đừng quên nó có lẽ không cần phải giải phóng tất cả các kết nối giữa các xét nghiệm - vừa đủ để giữ cho chúng chạy ...

Các nhà sưu tập rác chính nó là khó có khả năng cư xử bất kỳ khác nhau trong các thử nghiệm đơn vị , trừ khi quá trình thử nghiệm được cấu hình theo một cách cụ thể. Mặt khác, cho dù bạn chạy thử nghiệm trong trình gỡ rối hay không sẽ ảnh hưởng đến mức độ mong muốn của bộ thu gom rác, v.v.

+0

Jon, trong trường hợp bạn bỏ lỡ nó, Lucero đã đề cập rằng nó dường như dỡ bỏ toàn bộ AppDomain, điều này sẽ loại bỏ bất kỳ rò rỉ nào. –

2

Thường thì mỗi lần chạy thử được thực hiện trong một miền ứng dụng riêng biệt vì nhiều lý do. Bây giờ khi appdomain được unloaded nó sẽ phát hành các tài nguyên liên kết với nó, để các kết nối mở được đóng lại và do đó ngăn chặn "rò rỉ" để biểu hiện chính nó.

Xem thêm Cbrumme's blog on this topic.

+0

Ah, ok. Nếu nó dỡ bỏ AppDomain thì không cần phải ép buộc thu gom rác ở giữa. –

+0

Chính xác. Vì cách duy nhất để dỡ các assembly là bằng cách đặt chúng trong một appdomain riêng biệt và dỡ bỏ appdomain, "clean leak cleanup" là một tác dụng phụ của nó. – Lucero

+0

Vâng, cho dù đó là một tác dụng phụ phụ thuộc vào ý định của bạn. Tôi đã phải sử dụng AppDomains để làm sạch tài nguyên bị rò rỉ, với việc dỡ bỏ các hội đồng là một tác dụng phụ. :-) –

1

Unit tests đi khi chạy một lúc một thời gian và sau đó thất bại khi chạy với nhau là một dấu hiệu cổ điển mà một cái gì đó là sai lầm nghiêm trọng với mã .

Tôi nghĩ có điều gì đó nghiêm trọng sai với cách bạn đã viết các bài kiểm tra đơn vị của mình. Mỗi thử nghiệm nên chạy độc lập với các thử nghiệm khác. Một cách để làm điều đó là đảm bảo bạn có một phương pháp thiết lập và teardown ([SetUp][TearDown]) để tạo và dọn dẹp môi trường cần thiết để chạy thử nghiệm.

Trong phương pháp thiết lập của bạn, bạn tạo kết nối, trong phương pháp teardown bạn bỏ nó. Bây giờ trước khi chạy từng bài kiểm tra, phương thức Thiết lập của bạn sẽ được gọi và sau mỗi lần kiểm tra, phương thức teardown của bạn sẽ được gọi và điều này sẽ đảm bảo rằng bạn không bị rò rỉ bất kỳ tài nguyên nào.

0

Wow, nhiều sự cố ở đây!

Trước tiên, bạn muốn loạt bài kiểm tra đơn vị của mình trở thành FAST. Đừng nhấn vào cơ sở dữ liệu để kiểm tra logic nghiệp vụ, v.v.

Thứ hai, nếu mã sản xuất của bạn là tài nguyên bị rò rỉ (?) Là vấn đề chính của bạn. Không khắc phục sự cố đó bằng cách thay đổi cách bạn thiết lập/tách mã kiểm tra của mình. Bây giờ, nếu mã thử nghiệm của bạn phân bổ hệ thống resorces nhưng không xử lý một cách chính xác, bạn cần phải sửa chữa đúng cách và không phải bằng cách cố gắng để kiểm soát khi thu gom rác chạy. Bạn không cần phải lo lắng về điều đó.

Thứ ba, bạn thực sự không cần phải tạo một TransactionScope trong các bài kiểm tra đơn vị của mình. Điều này không có nghĩa gì với tôi. Có điều gì đó sai với kiểu mã hóa bạn đang sử dụng trong mã thử nghiệm của mình. Kiểm tra đơn vị không chỉ là bất kỳ kiểm tra tự động nào, chẳng hạn như kiểm tra tích hợp hoặc kiểm tra hệ thống. Bài kiểm tra đơn vị là các bài kiểm tra nhỏ và tập trung kiểm tra hành vi của một đoạn mã sản xuất SMALL trong ISOLATION, độc lập với tất cả các mã sản xuất khác.

Bây giờ, mẹo về tài nguyên bị rò rỉ. Một thực hành lập trình tốt là sử dụng báo cáo sử dụng khi tạo một đối tượng dùng một lần để đảm bảo các tài nguyên đó được xử lý đúng cách.

using (SqlDataReader reader = ...) 
{ 
    ... 
} 
+0

Làm thế nào bạn có thể sử dụng 'SqlDataReader' với' using'. Nó không có phương thức 'dispose()'. –

+0

Dude nó chỉ mở Trình duyệt đối tượng của bạn và kiểm tra nó ra, nó thừa hưởng DbDataReader dùng một lần - xem khai báo kiểu dưới đây. Btw, hầu hết các loại kết nối cơ sở dữ liệu liên quan này phải được dùng một lần khi chúng quấn các tài nguyên hệ điều hành khác nhau được cấp phát khi sử dụng. Việc đóng một trình đọc có hiệu lực khi xử lý nó. Lợi ích của việc sử dụng câu lệnh là chúng bỏ đi ngay cả khi một ngoại lệ được ném, trên đường có hiệu lực để thử {...} cuối cùng {foo.Dispose();} lớp công khai SqlDataReader: DbDataReader, IDataReader, IDisposable, IDataRecord – Mahol25

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