2009-10-06 38 views
50

Đọc qua các chuỗi liên quan đến kiểm tra đơn vị hiện có tại đây trên Stack Overflow, tôi không thể tìm thấy câu trả lời rõ ràng về cách thực hiện các thao tác I/O tệp thử nghiệm. Gần đây tôi chỉ mới bắt đầu tìm kiếm thử nghiệm đơn vị, trước đây đã nhận thức được những lợi thế nhưng gặp khó khăn khi làm quen với việc viết các bài kiểm tra trước. Tôi đã thiết lập dự án của mình để sử dụng NUnit và Rhino Mocks và mặc dù tôi hiểu khái niệm đằng sau chúng, tôi đang gặp một chút rắc rối khi hiểu cách sử dụng các đối tượng Mock.Kiểm tra đơn vị Tệp I/O

Cụ thể tôi có hai câu hỏi mà tôi muốn được trả lời. Đầu tiên, cách thích hợp để kiểm tra các hoạt động I/O file đơn vị là gì? Thứ hai, trong nỗ lực của tôi để tìm hiểu về thử nghiệm đơn vị, tôi đã đi qua tiêm phụ thuộc. Sau khi thiết lập Ninject và làm việc, tôi đã tự hỏi liệu tôi có nên sử dụng DI trong các bài kiểm tra đơn vị của mình hay chỉ cần khởi tạo trực tiếp các đối tượng.

+5

hay không sử dụng DI cho kiểm tra đơn vị phải là một câu hỏi riêng biệt ihmo. –

+0

Tôi khuyên bạn nên sử dụng DI ** khoảng thời gian ** (xem [bài viết này] (http://blog.ploeh.dk/2011/05/24/PokayokeDesignFromSmellToFragrance.aspx) và các liên kết mà nó liên kết đến) –

Trả lời

28

Khám phá Tutorial to TDD sử dụng Rhino MocksSystemWrapper.

SystemWrapper bao bọc nhiều lớp System.IO bao gồm Tệp, FileInfo, Thư mục, DirectoryInfo, .... Bạn có thể xem the complete list.

Trong hướng dẫn này, tôi sẽ chỉ cách thực hiện kiểm tra với MbUnit nhưng nó giống hệt với NUnit.

thử nghiệm của bạn sẽ trông giống như thế này:

[Test] 
public void When_try_to_create_directory_that_already_exists_return_false() 
{ 
    var directoryInfoStub = MockRepository.GenerateStub<IDirectoryInfoWrap>(); 
    directoryInfoStub.Stub(x => x.Exists).Return(true); 
    Assert.AreEqual(false, new DirectoryInfoSample().TryToCreateDirectory(directoryInfoStub)); 

    directoryInfoStub.AssertWasNotCalled(x => x.Create()); 
} 
+3

+1 - SystemWrapper có vẻ hữu ích! Có một cuộc thảo luận tốt đẹp về kỹ thuật này trên podcast tại http://www.slickthought.net/post/2009/04/03/New-Spaghetti-Code-Podcast-ndash3b-Donn-Felker-Talks-Mocking.aspx – TrueWill

+0

có sẵn trên NuGet chưa? Không nhìn thấy nó. Sẵn sàng chấp nhận rằng tôi chỉ bỏ lỡ nó :) –

+0

Nó có sẵn trên NuGet. Nó không phải là phiên bản mới nhất nhưng nó có sẵn. Tìm kiếm SystemWrapper. https://nuget.org/packages/SystemWrapper/0.4 – Vadim

9

Q1:

Bạn có ba tùy chọn ở đây.

Tùy chọn 1: Sống với nó.

(không Ví dụ: P)

Phương án 2: Tạo một trừu tượng nhẹ nơi cần thiết.

Thay vì làm tệp I/O (File.ReadAllBytes hoặc bất kỳ) trong phương pháp đang thử nghiệm, bạn có thể thay đổi nó để IO được thực hiện bên ngoài và thay vào đó luồng được truyền.

public class MyClassThatOpensFiles 
{ 
    public bool IsDataValid(string filename) 
    { 
     var filebytes = File.ReadAllBytes(filename); 
     DoSomethingWithFile(fileBytes); 
    } 
} 

sẽ trở thành

// File IO is done outside prior to this call, so in the level 
// above the caller would open a file and pass in the stream 
public class MyClassThatNoLongerOpensFiles 
{ 
    public bool IsDataValid(Stream stream) // or byte[] 
    { 
     DoSomethingWithStreamInstead(stream); // can be a memorystream in tests 
    } 
} 

Cách tiếp cận này là một sự cân bằng. Thứ nhất, có, nó là testable hơn. Tuy nhiên, nó giao dịch testability cho một sự bổ sung nhỏ để phức tạp. Điều này có thể đạt đến khả năng bảo trì và số lượng mã bạn phải viết, cộng với bạn chỉ có thể di chuyển vấn đề kiểm tra của mình lên một cấp. Tuy nhiên, theo kinh nghiệm của tôi, đây là một cách tiếp cận cân bằng, tốt đẹp khi bạn có thể khái quát hóa và làm cho testable logic quan trọng mà không tự cam kết với một hệ thống tập tin được gói đầy đủ. I E. bạn có thể khái quát hóa các bit bạn thực sự quan tâm, trong khi vẫn để phần còn lại.

Lựa chọn 3: Quấn toàn bộ hệ thống tập tin

Lấy nó một bước xa hơn, chế giễu hệ thống tập tin có thể là một cách tiếp cận hợp lệ; nó phụ thuộc vào bao nhiêu bloat bạn sẵn sàng để sống với.

Tôi đã đi tuyến đường này trước đây; Tôi đã thực hiện hệ thống tệp được bao bọc, nhưng cuối cùng tôi đã xóa nó.Có những khác biệt nhỏ trong API, tôi đã phải tiêm nó ở khắp mọi nơi và cuối cùng nó là thêm đau đớn vì lợi ích ít khi nhiều lớp sử dụng nó không vô cùng quan trọng đối với tôi. Nếu tôi đã sử dụng một container IoC hoặc viết một cái gì đó mà là rất quan trọng và các bài kiểm tra cần thiết để được nhanh chóng tôi có thể đã bị mắc kẹt với nó, mặc dù. Giống như tất cả các tùy chọn này, số dặm của bạn có thể thay đổi.

Đối với câu hỏi container IoC của bạn:

Tiêm thử nghiệm của bạn đôi tay. Nếu bạn phải làm rất nhiều công việc lặp đi lặp lại, chỉ cần sử dụng phương pháp thiết lập/nhà máy trong các bài kiểm tra của bạn. Sử dụng một container IoC để thử nghiệm sẽ là quá mức cần thiết trong cùng cực! Có lẽ tôi không hiểu câu hỏi thứ hai của bạn, mặc dù.

1

Hiện tại, tôi tiêu thụ đối tượng IFileSystem qua tiêm phụ thuộc. Đối với mã sản xuất, một lớp bao bọc thực hiện giao diện, gói các hàm IO cụ thể mà tôi cần. Khi thử nghiệm, tôi có thể tạo ra một thực thi null hoặc stub và cung cấp cho lớp đó dưới sự kiểm tra. Lớp học được thử nghiệm không phải là người khôn ngoan hơn.

32

Không nhất thiết phải một điều cần làm khi thử nghiệm hệ thống tệp. Trong sự thật, có một số điều bạn có thể làm, tùy thuộc vào hoàn cảnh.

Câu hỏi bạn cần đặt ra là: Tôi đang thử nghiệm những gì?

  • Hệ thống tệp có hoạt động không? Có thể bạn không cần phải thử nghiệm rằng trừ khi bạn đang sử dụng hệ điều hành mà bạn vô cùng không quen thuộc. Vì vậy, nếu bạn chỉ đơn giản là đưa ra một lệnh để lưu các tập tin, ví dụ, đó là một sự lãng phí thời gian để viết một bài kiểm tra để đảm bảo rằng họ thực sự tiết kiệm.

  • Các tệp đó được lưu vào đúng vị trí? Làm thế nào để bạn biết đúng địa điểm là gì? Có lẽ bạn có mã kết hợp một đường dẫn với tên tệp. Đây là mã bạn có thể kiểm tra dễ dàng: Đầu vào của bạn là hai chuỗi và đầu ra của bạn phải là một chuỗi là vị trí tệp hợp lệ được tạo bằng hai chuỗi đó.

  • Bạn nhận được tập hợp các tệp phù hợp từ một thư mục? Bạn có thể sẽ phải viết một bài kiểm tra cho lớp học của bạn-getter mà thực sự kiểm tra hệ thống tập tin. Nhưng bạn nên sử dụng một thư mục thử nghiệm với các tập tin trong đó sẽ không thay đổi. Bạn cũng nên đặt thử nghiệm này trong một dự án thử nghiệm tích hợp, bởi vì đây không phải là một thử nghiệm đơn vị thực sự, bởi vì nó phụ thuộc vào hệ thống tệp.

  • Nhưng, tôi cần phải làm điều gì đó với các tệp tôi nhận được. Đối với rằng thử nghiệm, bạn nên sử dụng giả cho lớp trình thu thập tệp của mình. Giả mạo của bạn sẽ trả lại danh sách tệp được mã hóa cứng. Nếu bạn sử dụng bộ tải tệp thực sự và bộ xử lý tệp thực, bạn sẽ không biết trình xử lý nào gây ra lỗi kiểm tra. Vì vậy, lớp xử lý tệp của bạn, trong quá trình thử nghiệm, nên sử dụng lớp trình thu thập tệp giả. Lớp trình xử lý tệp của bạn nên lấy giao diện của tệp-getter. Trong mã thực, bạn sẽ vượt qua trong file-getter thực sự. Trong mã thử nghiệm, bạn sẽ vượt qua một trình thu thập tệp giả trả về một danh sách tĩnh đã biết.

Các nguyên tắc cơ bản là:

  • Sử dụng một hệ thống tập tin giả, ẩn đằng sau một giao diện, khi bạn không kiểm tra hệ thống tập tin riêng của mình.
  • Nếu bạn cần kiểm tra các hoạt động tệp thực, sau đó
    • đánh dấu thử nghiệm là kiểm tra tích hợp chứ không phải kiểm tra đơn vị.
    • có thư mục kiểm tra được chỉ định, tập hợp tệp, v.v. sẽ luôn ở đó trong trạng thái không thay đổi, vì vậy các bài kiểm tra tích hợp theo định dạng tệp của bạn có thể vượt qua một cách nhất quán.
1

Kể từ năm 2012, bạn có thể làm điều đó bằng Microsoft Fakes mà không cần phải thay đổi codebase của bạn ví dụ bởi vì nó đã bị đóng băng rồi.

Đầu tiên generate a fake assembly cho System.dll - hoặc bất kỳ gói khác và sau đó trở lại chế nhạo dự kiến ​​như trong:

using Microsoft.QualityTools.Testing.Fakes; 
... 
using (ShimsContext.Create()) 
{ 
    System.IO.Fakes.ShimFile.ExistsString = (p) => true; 
    System.IO.Fakes.ShimFile.ReadAllTextString = (p) => "your file content"; 

     //Your methods to test 
}