2010-05-24 19 views
16

Tôi đã lớp sau đó là một trang trí cho một đối tượng IDisposable (Tôi đã bỏ qua những thứ nó bổ sung thêm) mà bản thân thực hiện IDisposable sử dụng một mô hình phổ biến:Làm cách nào để kiểm tra đơn vị trình hoàn thành?

public class DisposableDecorator : IDisposable 
{ 
    private readonly IDisposable _innerDisposable; 

    public DisposableDecorator(IDisposable innerDisposable) 
    { 
     _innerDisposable = innerDisposable; 
    } 

    #region IDisposable Members 

    public void Dispose() 
    { 
     Dispose(true); 
     GC.SuppressFinalize(this); 
    } 

    #endregion 

    ~DisposableDecorator() 
    { 
     Dispose(false); 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if (disposing) 
      _innerDisposable.Dispose(); 
    } 
} 

tôi có thể dễ dàng kiểm tra rằng innerDisposable được xử lý khi Dispose() được gọi là:

[Test] 
public void Dispose__DisposesInnerDisposable() 
{ 
    var mockInnerDisposable = new Mock<IDisposable>(); 

    new DisposableDecorator(mockInnerDisposable.Object).Dispose(); 

    mockInnerDisposable.Verify(x => x.Dispose()); 
} 

Nhưng làm thế nào để viết một bài kiểm tra để đảm bảo innerDisposable không không được xử lý theo finalizer? Tôi muốn viết một cái gì đó như thế này nhưng nó không thành công, có lẽ vì finalizer chưa được gọi bằng sợi GC:

[Test] 
public void Finalizer__DoesNotDisposeInnerDisposable() 
{ 
    var mockInnerDisposable = new Mock<IDisposable>(); 

    new DisposableDecorator(mockInnerDisposable.Object); 
    GC.Collect(); 

    mockInnerDisposable.Verify(x => x.Dispose(), Times.Never()); 
} 
+0

[Here] (http://stackoverflow.com/questions/3259456/should-dispose-methods-be-unit-tested) bạn có thể thấy việc sử dụng IDisposable. Điều đó làm việc tốt cho tôi. –

Trả lời

8

Khi viết kiểm tra đơn vị, bạn nên luôn cố gắng thử nghiệm bên ngoài hành vi hiển thị, không phải chi tiết triển khai. Người ta có thể lập luận rằng việc hoàn thành việc triệt tiêu thực sự là bên ngoài hành vi có thể nhìn thấy, nhưng mặt khác, có lẽ không có cách nào bạn có thể (cũng không nên bạn) giả lập bộ sưu tập garabage.

Những gì bạn cố gắng đảm bảo trong trường hợp của mình, là thực hành "thực hành tốt nhất" hoặc thực hành mã hóa được theo sau. Nó nên được thực thi thông qua một công cụ được thực hiện cho mục đích này, chẳng hạn như FxCop.

+1

Yup. Ngoài ra, bạn sẽ không bao giờ nhận được bảo hiểm 100% cho các bài kiểm tra đơn vị của bạn. Cuối cùng bạn chỉ cần có niềm tin rằng mã của bạn sẽ hoạt động, và nếu bạn có thẩm quyền, nó nên. –

+9

Tôi thực sự đã có một lỗi vì tôi đã quên để kiểm tra cờ 'disposing' trong' Dispose() 'vì vậy muốn thêm một thử nghiệm trước khi tôi sửa chữa nó. – GraemeF

2

Tôi sử dụng Appdomain (xem ví dụ bên dưới). Lớp TemporaryFile tạo tệp tạm thời trong hàm tạo và xóa tệp đó trong mục Dispose hoặc trong finalizer ~ TemporaryFile().

Thật không may, GC.WaitForPendingFinalizers(); không giúp tôi kiểm tra finalizer.

[Test] 
    public void TestTemporaryFile_without_Dispose() 
    { 
     const string DOMAIN_NAME = "testDomain"; 
     const string FILENAME_KEY = "fileName"; 

     string testRoot = Directory.GetCurrentDirectory(); 

     AppDomainSetup info = new AppDomainSetup 
            { 
             ApplicationBase = testRoot 
     }; 
     AppDomain testDomain = AppDomain.CreateDomain(DOMAIN_NAME, null, info); 
     testDomain.DoCallBack(delegate 
     { 
      TemporaryFile temporaryFile = new TemporaryFile(); 
      Assert.IsTrue(File.Exists(temporaryFile.FileName)); 
      AppDomain.CurrentDomain.SetData(FILENAME_KEY, temporaryFile.FileName); 
     }); 
     string createdTemporaryFileName = (string)testDomain.GetData(FILENAME_KEY); 
     Assert.IsTrue(File.Exists(createdTemporaryFileName)); 
     AppDomain.Unload(testDomain); 

     Assert.IsFalse(File.Exists(createdTemporaryFileName)); 
    } 
+0

Tôi không nghĩ rằng có bất kỳ cách nào để kiểm tra một finalizer đúng cách, cho rằng có một số vô biên của các kịch bản luồng mà theo đó một finalizer có thể chạy. Finalizers có thể kết thúc chạy trên các đối tượng được xây dựng một phần, và do đó thường chỉ được sử dụng trên các lớp đơn giản, đủ để cho phép tất cả các kịch bản được xác nhận thông qua kiểm tra.Nếu một lớp sử dụng tài nguyên không được quản lý quá phức tạp để cho phép kiểm tra dễ dàng, tài nguyên sẽ được đóng gói trong lớp nhỏ hơn của chúng, để lớp có tham chiếu đến đối tượng đang nắm giữ tài nguyên sẽ không cần finalizer. – supercat

+0

Rất gần! Điều này thực sự khá thông minh. Nó thực sự buộc các finalizer để chạy, và được tôi 90% đến nơi tôi muốn được. Tuy nhiên, trong trường hợp của tôi, tôi cũng cần có khả năng sử dụng Fakes Shim và mã chạy trong AppDomain không thấy Shim. Tôi không thể tạo shim bên trong DoCallback vì nó sẽ nằm ngoài phạm vi trước khi finalizer chạy. Có ai nghĩ rằng một trong những? –

+0

@SteveInCO bạn có thể xuất bản câu hỏi với các nguồn trong trường hợp của bạn không? Thú vị khi xem giải pháp mẫu và tìm kiếm. – constructor

0

Không dễ để kiểm tra việc hoàn thành, nhưng có thể dễ dàng kiểm tra xem đối tượng có phải là đối tượng thu gom rác hay không.

Điều này có thể được thực hiện với một tham chiếu yếu.

Trong thử nghiệm, điều quan trọng là để các biến cục bộ chạy hết phạm vi trước khi gọi GC.Collect(). Cách dễ nhất để đảm bảo là phạm vi chức năng.

class Stuff 
    { 
     ~Stuff() 
     { 
     } 
    } 

    WeakReference CreateWithWeakReference<T>(Func<T> factory) 
    { 
     return new WeakReference(factory()); 
    } 

    [Test] 
    public void TestEverythingOutOfScopeIsReleased() 
    { 
     var tracked = new List<WeakReference>(); 

     var referer = new List<Stuff>(); 

     tracked.Add(CreateWithWeakReference(() => { var stuff = new Stuff(); referer.Add(stuff); return stuff; })); 

     // Run some code that is expected to release the references 
     referer.Clear(); 

     GC.Collect(); 

     Assert.IsFalse(tracked.Any(o => o.IsAlive), "All objects should have been released"); 
    } 

    [Test] 
    public void TestLocalVariableIsStillInScope() 
    { 
     var tracked = new List<WeakReference>(); 

     var referer = new List<Stuff>(); 

     for (var i = 0; i < 10; i++) 
     { 
      var stuff = new Stuff(); 
      tracked.Add(CreateWithWeakReference(() => { referer.Add(stuff); return stuff; })); 
     } 

     // Run some code that is expected to release the references 
     referer.Clear(); 

     GC.Collect(); 

     // Following holds because of the stuff variable is still on stack! 
     Assert.IsTrue(tracked.Count(o => o.IsAlive) == 1, "Should still have a reference to the last one from the for loop"); 
    } 
Các vấn đề liên quan