2012-04-25 30 views
26

Tôi có một lớp Transfer, đơn giản hóa nó trông như thế này:Mocking một phương pháp để ném một ngoại lệ (moq), nhưng nếu không hành động như đối tượng giả?

public class Transfer 
{ 
    public virtual IFileConnection source { get; set; } 
    public virtual IFileConnection destination { get; set; } 

    public virtual void GetFile(IFileConnection connection, 
     string remoteFilename, string localFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void PutFile(IFileConnection connection, 
     string localFilename, string remoteFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void TransferFiles(string sourceName, string destName) 
    { 
     source = internalConfig.GetFileConnection("source"); 
     destination = internalConfig.GetFileConnection("destination"); 
     var tempName = Path.GetTempFileName(); 
     GetFile(source, sourceName, tempName); 
     PutFile(destination, tempName, destName); 
    } 
} 

Các phiên bản đơn giản của giao diện IFileConnection trông như thế này:

public interface IFileConnection 
{ 
    void Get(string remoteFileName, string localFileName); 
    void Put(string localFileName, string remoteFileName); 
} 

Lớp thực là nghĩa vụ phải xử lý một System.IO.IOException đó là bị ném khi lớp bê tông IFileConnection mất kết nối với điều khiển từ xa, gửi email và những gì không.

Tôi muốn sử dụng Moq để tạo ra một lớp Transfer, và sử dụng nó như lớp bê tông của tôi Transfer trong tất cả các thuộc tính và phương pháp, trừ khi các phương pháp GetFile được gọi - sau đó tôi muốn nó ném một System.IO.IOException và chắc chắn rằng Transfer lớp xử lý nó đúng cách.

Tôi có sử dụng đúng công cụ cho công việc không? Tôi đang đi về điều này đúng cách? Và làm cách nào để tôi viết thiết lập cho thử nghiệm đơn vị đó cho NUnit?

Trả lời

7

Đây là cách tôi cố gắng làm những gì tôi đã cố gắng để làm:

[Test] 
public void TransferHandlesDisconnect() 
{ 
    // ... set up config here 
    var methodTester = new Mock<Transfer>(configInfo); 
    methodTester.CallBase = true; 
    methodTester 
     .Setup(m => 
      m.GetFile(
       It.IsAny<IFileConnection>(), 
       It.IsAny<string>(), 
       It.IsAny<string>() 
      )) 
     .Throws<System.IO.IOException>(); 

    methodTester.Object.TransferFiles("foo1", "foo2"); 
    Assert.IsTrue(methodTester.Object.Status == TransferStatus.TransferInterrupted); 
} 

Nếu có vấn đề với phương pháp này, tôi muốn biết; các câu trả lời khác cho thấy tôi đang làm điều này sai, nhưng đây chính xác là những gì tôi đang cố gắng làm.

49

Đây là cách bạn có thể chế nhạo của bạn FileConnection

Mock<IFileConnection> fileConnection = new Mock<IFileConnection>(
                  MockBehavior.Strict); 
fileConnection.Setup(item => item.Get(It.IsAny<string>,It.IsAny<string>)) 
       .Throws(new IOException()); 

Sau đó nhanh chóng lớp chuyển của bạn và sử dụng các mô hình trong phương thức gọi bạn

Transfer transfer = new Transfer(); 
transfer.GetFile(fileConnection.Object, someRemoteFilename, someLocalFileName); 

Cập nhật:

Trước hết bạn chỉ cần giả lập các phụ thuộc của bạn, không phải lớp bạn đang thử nghiệm (lớp chuyển giao trong trường hợp này). Nói rõ những phụ thuộc trong constructor của bạn làm cho nó dễ dàng để xem những gì các lớp dịch vụ của bạn cần làm việc. Nó cũng làm cho nó có thể thay thế chúng với hàng giả khi bạn đang viết các bài kiểm tra đơn vị của bạn. Hiện tại, không thể thay thế những tài sản đó bằng hàng giả.

Vì bạn đang thiết lập các thuộc tính sử dụng phụ thuộc khác, tôi sẽ viết nó như thế này:

public class Transfer 
{ 
    public Transfer(IInternalConfig internalConfig) 
    { 
     source = internalConfig.GetFileConnection("source"); 
     destination = internalConfig.GetFileConnection("destination"); 
    } 

    //you should consider making these private or protected fields 
    public virtual IFileConnection source { get; set; } 
    public virtual IFileConnection destination { get; set; } 

    public virtual void GetFile(IFileConnection connection, 
     string remoteFilename, string localFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void PutFile(IFileConnection connection, 
     string localFilename, string remoteFilename) 
    { 
     connection.Get(remoteFilename, localFilename); 
    } 

    public virtual void TransferFiles(string sourceName, string destName) 
    { 
     var tempName = Path.GetTempFileName(); 
     GetFile(source, sourceName, tempName); 
     PutFile(destination, tempName, destName); 
    } 
} 

Bằng cách này bạn có thể thử internalConfig và làm cho nó trở mocks IFileConnection mà những gì bạn muốn.

+0

Hmm. Không phải những gì tôi mong đợi, và bây giờ tôi thấy tôi đã đơn giản hóa lớp 'Transfer' của mình quá nhiều. Bạn có thể nhìn vào những thay đổi mà tôi đã thực hiện cho lớp 'Transfer' không? Điều này giống mã của tôi một chút chặt chẽ hơn. Các thuộc tính được trưng ra trong lớp, nhưng tôi đã làm điều đó chủ yếu để tôi có thể kiểm tra điều kiện ngoại lệ. Là những gì tôi đã cố gắng để làm một cách sai lầm? –

+0

@JeremyHolovacs Tôi đã cập nhật câu trả lời của mình. –

+0

Hmm. Những gì tôi nghĩ sẽ đơn giản dường như nhận được ít hơn như vậy. Hãy để tôi hỏi bạn điều này: nó sẽ có ý nghĩa hơn để tạo ra một lớp thử nghiệm kế thừa từ 'Transfer' và ghi đè lên phương thức cho mục đích thử nghiệm? Có lẽ chế nhạo không phải là công nghệ thích hợp để sử dụng trong kịch bản này, và tôi đang cố gắng để đẩy một chốt vuông vào một lỗ tròn? –

2

Tôi nghĩ rằng đây là những gì bạn muốn, tôi đã kiểm tra mã này và các công trình

Các công cụ được sử dụng là: (tất cả những công cụ này có thể được tải về như các gói NuGet)

http://fluentassertions.codeplex.com/

http://autofixture.codeplex.com/

http://code.google.com/p/moq/

https://nuget.org/packages/AutoFixture.AutoMoq

var fixture = new Fixture().Customize(new AutoMoqCustomization()); 
var myInterface = fixture.Freeze<Mock<IFileConnection>>(); 

var sut = fixture.CreateAnonymous<Transfer>(); 

myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())) 
     .Throws<System.IO.IOException>(); 

sut.Invoking(x => 
     x.TransferFiles(
      myInterface.Object, 
      It.IsAny<string>(), 
      It.IsAny<string>() 
     )) 
     .ShouldThrow<System.IO.IOException>(); 

được sửa đổi:

Hãy để tôi giải thích:

Khi bạn viết một bài kiểm tra, bạn phải biết chính xác những gì bạn muốn kiểm tra, điều này được gọi là: "đối tượng được kiểm tra (SUT) ", nếu hiểu biết của tôi là chính xác, trong trường hợp này SUT của bạn là: Transfer

Vì vậy, với ý nghĩ này, bạn không nên giả lập SUT của bạn, nếu bạn thay thế SUT, thì bạn sẽ không thực sự thử nghiệm mã thực

Khi SUT của bạn có phụ thuộc bên ngoài (rất phổ biến) thì bạn cần phải thay thế chúng để kiểm tra cách ly cách ly SUT của bạn. Khi tôi nói thay thế, tôi đề cập đến việc sử dụng giả, giả, giả, v.v. tùy theo nhu cầu của bạn

Trong trường hợp này, phụ thuộc bên ngoài của bạn là IFileConnection vì vậy bạn cần phải tạo mô hình cho sự phụ thuộc này và định cấu hình để loại trừ ngoại lệ , sau đó chỉ cần gọi SUT phương pháp thực sự của bạn và khẳng định phương pháp của bạn xử lý các ngoại lệ như mong đợi

  • var fixture = new Fixture().Customize(new AutoMoqCustomization());: Linie này khởi tạo một đối tượng thi đấu mới (Autofixture thư viện), đối tượng này được sử dụng để tạo ra SUT của mà không cần phải rõ ràng có phải lo lắng về các tham số của hàm tạo, vì chúng được tạo tự động hoặc được mô phỏng, trong trường hợp này sử dụng Moq

  • var myInterface = fixture.Freeze<Mock<IFileConnection>>();: Điều này bị đóng băng phụ thuộc IFileConnection. Freeze có nghĩa là Autofixture sẽ luôn sử dụng sự phụ thuộc này khi được hỏi, như một singleton cho sự đơn giản.Nhưng phần thú vị là chúng ta đang tạo một Mock của sự phụ thuộc này, bạn có thể sử dụng tất cả các phương pháp Moq, vì đây là một đối tượng Moq đơn giản

  • var sut = fixture.CreateAnonymous<Transfer>();: Đây AutoFixture đang tạo SUT cho chúng ta

  • myInterface.Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>())).Throws<System.IO.IOException>(); ở đây bạn đang cấu hình phụ thuộc để ném một ngoại lệ bất cứ khi nào phương pháp Get được gọi, phần còn lại của các phương pháp từ giao diện này không được cấu hình, do đó nếu bạn cố gắng truy cập chúng bạn sẽ nhận được một ngoại lệ bất ngờ

  • sut.Invoking(x => x.TransferFiles(myInterface.Object, It.IsAny<string>(), It.IsAny<string>())).ShouldThrow<System.IO.IOException>(); : Và cuối cùng, thời gian để t est SUT của bạn, dòng này sử dụng thư viện FluenAssertions, và nó chỉ gọi TransferFilesphương pháp thực tế từ các SUT và như thông số mà nó nhận được sự chế giễu IFileConnection nên bất cứ khi nào bạn gọi IFileConnection.Get trong dòng chảy bình thường của phương pháp SUT TransferFiles của bạn, chế giễu đối tượng sẽ gọi lệnh ném ngoại lệ đã định cấu hình và đây là lúc để xác nhận rằng SUT của bạn đang xử lý đúng ngoại lệ, trong trường hợp này, tôi chỉ đảm bảo rằng ngoại lệ được ném bằng cách sử dụng ShouldThrow<System.IO.IOException>() (từ thư viện FluentAssertions)

Tham chiếu được đề xuất:

http://martinfowler.com/articles/mocksArentStubs.html

http://misko.hevery.com/code-reviewers-guide/

http://misko.hevery.com/presentations/

http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded

http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded

+0

Tôi không nghĩ rằng điều này làm những gì tôi cần nó để làm. Tôi cần phải ném ngoại lệ ở giữa phương thức 'Transfer.TransferFiles()' bằng cách cho phép phương thức 'Transfer.TransferFiles()' gọi ra ngoại lệ trong phương thức 'GetFile()'. Tôi nghĩ rằng (nếu tôi đọc điều này đúng), nó sẽ tiêm 'IFileConnection' vào thời gian chạy, nó sẽ được ghi đè lên. Liệu tôi có sai? –

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