Các phương pháp hay nhất về phát triển theo hướng thử nghiệm bằng C # và RhinoMocks [đã đóng]


86

Để giúp nhóm của tôi viết mã có thể kiểm tra, tôi đã đưa ra danh sách đơn giản về các phương pháp hay nhất để làm cho cơ sở mã C # của chúng tôi dễ kiểm tra hơn. (Một số điểm đề cập đến những hạn chế của Rhino Mocks, một khuôn khổ chế tạo cho C #, nhưng các quy tắc cũng có thể áp dụng chung hơn.) Có ai có bất kỳ phương pháp hay nhất nào mà họ tuân theo không?

Để tối đa hóa khả năng kiểm tra của mã, hãy làm theo các quy tắc sau:

  1. Viết bài kiểm tra trước, sau đó là mã. Lý do: Điều này đảm bảo rằng bạn viết mã có thể kiểm tra và mỗi dòng mã đều được viết thử nghiệm cho nó.

  2. Thiết kế các lớp bằng cách sử dụng tiêm phụ thuộc. Lý do: Bạn không thể mô phỏng hoặc kiểm tra những gì không thể nhìn thấy.

  3. Tách mã giao diện người dùng khỏi hành vi của nó bằng cách sử dụng Model-View-Controller hoặc Model-View-Presenter. Lý do: Cho phép kiểm tra logic nghiệp vụ trong khi các phần không thể kiểm tra (giao diện người dùng) được thu nhỏ.

  4. Không viết các phương thức hoặc lớp tĩnh. Lý do: Các phương thức tĩnh rất khó hoặc không thể cô lập và Rhino Mocks không thể bắt chước chúng.

  5. Chương trình tắt giao diện, không phải lớp học. Lý do: Sử dụng giao diện làm rõ mối quan hệ giữa các đối tượng. Một giao diện nên xác định một dịch vụ mà một đối tượng cần từ môi trường của nó. Ngoài ra, các giao diện có thể dễ dàng bị chế nhạo bằng cách sử dụng Rhino Mocks và các khung chế tạo khác.

  6. Cô lập các phụ thuộc bên ngoài. Lý do: Không thể kiểm tra các phụ thuộc bên ngoài chưa được giải quyết.

  7. Đánh dấu là ảo các phương pháp bạn định chế nhạo. Lý do: Rhino Mocks không thể giả lập các phương thức không phải ảo.


Đây là một danh sách hữu ích. Chúng tôi hiện đang sử dụng NUnit và Rhino.Mocks, và tốt hơn là nên đưa ra các tiêu chí này cho các thành viên trong nhóm, những người ít quen thuộc với khía cạnh này của thử nghiệm đơn vị.
Chris Ballard,

Câu trả lời:


58

Chắc chắn là một danh sách tốt. Dưới đây là một vài suy nghĩ về nó:

Viết bài kiểm tra trước, sau đó là mã.

Tôi đồng ý, ở mức độ cao. Nhưng, tôi muốn cụ thể hơn: "Trước tiên hãy viết một bài kiểm tra, sau đó viết một đoạn mã vừa đủ để vượt qua bài kiểm tra và lặp lại." Nếu không, tôi e rằng các bài kiểm tra đơn vị của tôi sẽ giống các bài kiểm tra tích hợp hoặc chấp nhận hơn.

Thiết kế các lớp bằng cách sử dụng tiêm phụ thuộc.

Đã đồng ý. Khi một đối tượng tạo ra các phụ thuộc của chính nó, bạn không có quyền kiểm soát chúng. Inversion of Control / Dependency Injection cung cấp cho bạn quyền kiểm soát đó, cho phép bạn cô lập đối tượng đang được kiểm tra bằng mocks / sơ khai / vv. Đây là cách bạn kiểm tra các đối tượng một cách riêng biệt.

Tách mã giao diện người dùng khỏi hành vi của nó bằng cách sử dụng Model-View-Controller hoặc Model-View-Presenter.

Đã đồng ý. Lưu ý rằng ngay cả người trình bày / người điều khiển cũng có thể được kiểm tra bằng cách sử dụng DI / IoC, bằng cách cung cấp cho nó một chế độ xem và mô hình bị chế nhạo / chế nhạo. Hãy xem Presenter First TDD để biết thêm về điều đó.

Không viết các phương thức hoặc lớp tĩnh.

Không chắc tôi đồng ý với điều này. Có thể kiểm tra đơn vị một phương thức / lớp tĩnh mà không cần sử dụng mocks. Vì vậy, có lẽ đây là một trong những quy tắc cụ thể của Rhino Mock mà bạn đã đề cập.

Chương trình tắt giao diện, không phải lớp học.

Tôi đồng ý, nhưng vì một lý do hơi khác. Các giao diện cung cấp rất nhiều tính linh hoạt cho nhà phát triển phần mềm - ngoài việc chỉ hỗ trợ cho các khung đối tượng giả khác nhau. Ví dụ, không thể hỗ trợ DI đúng cách nếu không có giao diện.

Cô lập các phụ thuộc bên ngoài.

Đã đồng ý. Ẩn phụ thuộc bên ngoài đằng sau mặt tiền hoặc bộ điều hợp của riêng bạn (nếu thích hợp) bằng một giao diện. Điều này sẽ cho phép bạn tách phần mềm của mình khỏi sự phụ thuộc bên ngoài, có thể là dịch vụ web, hàng đợi, cơ sở dữ liệu hoặc thứ gì khác. Điều này đặc biệt quan trọng khi nhóm của bạn không kiểm soát được sự phụ thuộc (hay còn gọi là bên ngoài).

Đánh dấu là ảo các phương pháp bạn định chế nhạo.

Đó là một hạn chế của Rhino Mocks. Trong một môi trường thích viết mã bằng tay hơn một khung đối tượng giả, điều đó sẽ không cần thiết.

Và, một số điểm mới cần xem xét:

Sử dụng các mẫu thiết kế sáng tạo. Điều này sẽ hỗ trợ với DI, nhưng nó cũng cho phép bạn tách mã đó và kiểm tra nó độc lập với các logic khác.

Viết bài kiểm tra bằng kỹ thuật Sắp xếp / Hành động / Khẳng định của Bill Wake . Kỹ thuật này làm cho nó rất rõ ràng cấu hình nào là cần thiết, những gì thực sự đang được thử nghiệm và những gì được mong đợi.

Đừng ngại tự chế giễu / sơ khai của chính mình. Thông thường, bạn sẽ thấy rằng việc sử dụng các khuôn khổ đối tượng giả làm cho các bài kiểm tra của bạn cực kỳ khó đọc. Bằng cách tự giới thiệu, bạn sẽ có toàn quyền kiểm soát đối với các mô phỏng / sơ khai của mình và bạn sẽ có thể đọc được các thử nghiệm của mình. (Tham khảo lại điểm trước.)

Tránh cám dỗ để cấu trúc lại sự trùng lặp từ các bài kiểm tra đơn vị của bạn thành các lớp cơ sở trừu tượng hoặc các phương thức thiết lập / xé nhỏ. Làm như vậy sẽ ẩn cấu hình / mã xóa khỏi nhà phát triển đang cố gắng kiểm tra đơn vị. Trong trường hợp này, sự rõ ràng của từng bài kiểm tra riêng lẻ quan trọng hơn việc cấu trúc lại sự trùng lặp.

Thực hiện Tích hợp Liên tục. Đăng ký mã của bạn trên mỗi "thanh màu xanh lá cây". Xây dựng phần mềm của bạn và chạy bộ kiểm tra đơn vị đầy đủ của bạn trong mỗi lần đăng ký. (Chắc chắn, đây không phải là một phương pháp viết mã; nhưng nó là một công cụ đáng kinh ngạc để giữ cho phần mềm của bạn sạch sẽ và được tích hợp đầy đủ.)


3
Tôi thường thấy rằng nếu một bài kiểm tra khó đọc, đó không phải là lỗi của khuôn khổ mà là do mã mà nó đang kiểm tra. Nếu SUT phức tạp để thiết lập, thì có lẽ nó nên được chia thành nhiều khái niệm hơn.
Steve Freeman

10

Nếu bạn đang làm việc với .Net 3.5, bạn có thể muốn xem xét thư viện chế nhạo Moq - nó sử dụng cây biểu thức và lambdas để loại bỏ thành ngữ trả lời bản ghi không trực quan của hầu hết các thư viện chế nhạo khác.

Hãy xem phần bắt đầu nhanh này để xem các trường hợp thử nghiệm của bạn trở nên trực quan hơn như thế nào, đây là một ví dụ đơn giản:

// ShouldExpectMethodCallWithVariable
int value = 5;
var mock = new Mock<IFoo>();

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2);

Assert.AreEqual(value * 2, mock.Object.Duplicate(value));

5
Tôi nghĩ rằng phiên bản mới của Rhino Mocks làm việc như thế này quá
George Mauer


3

Đây là một bài viết rất hữu ích!

Tôi muốn nói thêm rằng điều quan trọng là phải hiểu Bối cảnh và Hệ thống Đang Thử nghiệm (SUT). Việc tuân theo các nguyên tắc TDD cho thư sẽ dễ dàng hơn nhiều khi bạn đang viết mã mới trong môi trường mà mã hiện có tuân theo các nguyên tắc tương tự. Nhưng khi bạn đang viết mã mới trong môi trường kế thừa không phải TDD, bạn thấy rằng nỗ lực TDD của bạn có thể nhanh chóng vượt xa những ước tính và mong đợi của bạn.

Đối với một số bạn, những người sống trong một thế giới học thuật hoàn toàn, lịch trình và phân phối có thể không quan trọng, nhưng trong một môi trường mà phần mềm là tiền, việc sử dụng hiệu quả nỗ lực TDD của bạn là rất quan trọng.

TDD rất tuân theo Quy luật Lợi nhuận cận biên giảm dần . Tóm lại, những nỗ lực của bạn đối với TDD ngày càng có giá trị cho đến khi bạn đạt đến mức sinh lời tối đa, sau đó, thời gian đầu tư vào TDD ngày càng ít giá trị hơn.

Tôi có xu hướng tin rằng giá trị chính của TDD nằm trong ranh giới (hộp đen) cũng như trong thử nghiệm hộp trắng không thường xuyên đối với các khu vực quan trọng của hệ thống.


2

Lý do thực sự của việc lập trình chống lại các giao diện không phải để làm cho cuộc sống của Rhino dễ dàng hơn, mà là để làm rõ mối quan hệ giữa các đối tượng trong mã. Một giao diện nên xác định một dịch vụ mà một đối tượng cần từ môi trường của nó. Một lớp cung cấp một triển khai cụ thể của dịch vụ đó. Đọc cuốn sách "Thiết kế đối tượng" của Rebecca Wirfs-Brock về Vai trò, Trách nhiệm và Cộng tác viên.


Đồng ý ... Tôi sẽ cập nhật câu hỏi của mình để phản ánh điều đó.
Kevin Albrecht

1

Danh sách tốt. Một trong những điều mà bạn có thể muốn thiết lập - và tôi không thể cho bạn nhiều lời khuyên vì tôi chỉ mới bắt đầu tự nghĩ về nó - là khi một lớp nên ở trong một thư viện khác, không gian tên, không gian tên lồng nhau. Bạn thậm chí có thể muốn tìm ra danh sách các thư viện và không gian tên trước và yêu cầu nhóm phải họp và quyết định hợp nhất hai / thêm một mới.

Ồ, tôi vừa nghĩ ra điều gì đó mà tôi có thể làm mà bạn cũng muốn. Tôi thường có một thư viện bài kiểm tra đơn vị với một lịch thi thử cho mỗi chính sách lớp trong đó mỗi bài kiểm tra đi vào một không gian tên tương ứng. Tôi cũng có xu hướng có một thư viện kiểm tra khác (kiểm tra tích hợp?) Theo phong cách BDD hơn . Điều này cho phép tôi viết các bài kiểm tra để xác định những gì phương pháp nên làm cũng như những gì ứng dụng sẽ làm tổng thể.


Tôi cũng làm một phần thử nghiệm kiểu BDD tương tự (ngoài mã thử nghiệm đơn vị) trong một dự án cá nhân.
Kevin Albrecht 24-08

0

Đây là một cái khác mà tôi đã nghĩ ra mà tôi muốn làm.

Nếu bạn định chạy các bài kiểm tra từ kiểm thử đơn vị Gui thay vì từ TestDriven.Net hoặc NAnt thì tôi thấy việc đặt loại dự án kiểm thử đơn vị thành ứng dụng bảng điều khiển dễ dàng hơn là thư viện. Điều này cho phép bạn chạy các bài kiểm tra theo cách thủ công và vượt qua chúng trong chế độ gỡ lỗi (mà TestDriven.Net đã nói ở trên thực sự có thể làm cho bạn).

Ngoài ra, tôi luôn muốn có một dự án Playground để thử nghiệm các đoạn mã và ý tưởng mà tôi không quen thuộc. Điều này không nên được kiểm tra trong kiểm soát nguồn. Tốt hơn nữa, nó phải nằm trong một kho lưu trữ kiểm soát nguồn riêng biệt chỉ trên máy của nhà phát triển.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.