2009-06-10 33 views
5

Tôi đang cố gắng phát triển hành vi theo hướng hành vi và tôi đang tìm kiếm bản thân mình khi đoán thiết kế của mình khi tôi đang viết nó. Đây là dự án greenfield đầu tiên của tôi và nó có thể chỉ là sự thiếu kinh nghiệm của tôi. Dù sao, đây là một thông số kỹ thuật đơn giản cho lớp tôi đang viết. Nó được viết bằng NUnit theo kiểu BDD thay vì sử dụng một khung điều khiển hành vi chuyên dụng. Điều này là do dự án nhắm mục tiêu .NET 2.0 và tất cả các khuôn khổ BDD dường như đã chấp nhận .NET 3.5.Đây có phải là thiết kế kém không?

[TestFixture] 
public class WhenUserAddsAccount 
{ 
    private DynamicMock _mockMainView; 
    private IMainView _mainView; 

    private DynamicMock _mockAccountService; 
    private IAccountService _accountService; 

    private DynamicMock _mockAccount; 
    private IAccount _account; 

    [SetUp] 
    public void Setup() 
    { 
     _mockMainView = new DynamicMock(typeof(IMainView)); 
     _mainView = (IMainView) _mockMainView.MockInstance; 

     _mockAccountService = new DynamicMock(typeof(IAccountService)); 
     _accountService = (IAccountService) _mockAccountService.MockInstance; 

     _mockAccount = new DynamicMock(typeof(IAccount)); 
     _account = (IAccount)_mockAccount.MockInstance; 
    } 

    [Test] 
    public void ShouldCreateNewAccount() 
    { 
     _mockAccountService.ExpectAndReturn("Create", _account); 
     MainPresenter mainPresenter = new MainPresenter(_mainView, _accountService); 
     mainPresenter.AddAccount(); 
     _mockAccountService.Verify(); 
    } 
} 

Không có giao diện nào được sử dụng bởi MainPresenter có bất kỳ triển khai thực nào được nêu ra. AccountService sẽ chịu trách nhiệm tạo tài khoản mới. Có thể có nhiều triển khai IAccount được định nghĩa là các plugin riêng biệt. Khi chạy, nếu có nhiều hơn một người dùng thì người dùng sẽ được nhắc chọn loại tài khoản nào để tạo. Nếu không thì AccountService sẽ chỉ tạo một tài khoản.

Một trong những điều khiến tôi không thoải mái là cần bao nhiêu mocks để viết một thông số/thử nghiệm đơn lẻ. Đây có phải chỉ là một tác dụng phụ của việc sử dụng BDD hoặc tôi đang đi về điều này một cách sai lầm?

[Cập nhật]

Đây là việc thực hiện hiện tại của MainPresenter.AddAccount

public void AddAccount() 
    { 
     IAccount account; 
     if (AccountService.AccountTypes.Count == 1) 
     { 
      account = AccountService.Create(); 
     } 
     _view.Accounts.Add(account); 
    } 

Bất cứ lời khuyên, gợi ý hoặc thay thế chào đón.

Trả lời

3

Khi thực hiện phát triển từ trên xuống, việc sử dụng rất nhiều mock là khá phổ biến. Các mảnh bạn cần không có ở đó vì vậy tự nhiên bạn cần phải thử chúng. Với điều đó nói rằng điều này không giống như một bài kiểm tra mức độ chấp nhận. Theo kinh nghiệm của tôi BDD hoặc bối cảnh/đặc điểm kỹ thuật bắt đầu để có được một chút lạ ở cấp độ thử nghiệm đơn vị. Ở cấp độ bài kiểm tra đơn vị, tôi có thể sẽ làm một số thứ khác dọc theo các dòng ...

 
when_adding_an_account 
    should_use_account_service_to_create_new_account 
    should_update_screen_with_new_account_details 

Bạn có thể cân nhắc việc sử dụng giao diện cho IAccount. Cá nhân tôi dính bằng cách giữ giao diện cho các dịch vụ trên thực thể tên miền. Nhưng đó là một sở thích cá nhân hơn.

Một vài gợi ý nhỏ khác ...

  • Bạn có thể muốn xem xét sử dụng một khuôn khổ Mocking như Rhino Mocks (hoặc Moq) cho phép bạn tránh sử dụng chuỗi để khẳng định mình.
 

    _mockAccountService.Expect(mock => mock.Create()) 
    .Return(_account); 

  • Nếu bạn đang làm BDD phong cách một mẫu chung tôi đã nhìn thấy đang sử dụng lớp xích cho thiết lập thử nghiệm. Trong ví dụ của bạn ...
 
public class MainPresenterSpec 
{ 
    // Protected variables for Mocks 

    [SetUp] 
    public void Setup() 
    { 
     // Setup Mocks 
    } 

} 

[TestFixture] 
public class WhenUserAddsAccount : MainPresenterSpec 
{ 
    [Test] 
    public void ShouldCreateNewAccount() 
    { 
    } 
} 
  • Ngoài ra tôi khuyên bạn nên thay đổi mã của bạn để sử dụng một điều khoản bảo vệ ..
 
    public void AddAccount() 
    { 
     if (AccountService.AccountTypes.Count != 1) 
     { 
      // Do whatever you want here. throw a message? 
     return; 
     } 

    IAccount account = AccountService.Create(); 

     _view.Accounts.Add(account); 
    } 
+0

Một số lời khuyên tốt ở đây +1 –

1

Điều đó có vẻ giống như số lượng mocks chính xác cho người trình bày với dịch vụ được cho là sẽ trả lại tài khoản.

Điều này có vẻ giống như một bài kiểm tra chấp nhận hơn là một bài kiểm tra đơn vị, mặc dù - có lẽ nếu bạn giảm sự phức tạp khẳng định của bạn, bạn sẽ tìm thấy một tập hợp nhỏ hơn các mối quan tâm được chế giễu.

0

Bạn có thể muốn sử dụng MockContainers để thoát khỏi tất cả công tác quản lý giả, trong khi tạo ra người dẫn chương trình. Nó đơn giản hóa các bài kiểm tra đơn vị rất nhiều.

0

Điều này là ổn, nhưng tôi sẽ mong đợi một thùng chứa tự động IoC ở đó đâu đó. Mã gợi ý tại các nhà văn thử nghiệm bằng tay (rõ ràng) chuyển đổi giữa các đối tượng giả và thực trong các thử nghiệm mà không phải là trường hợp vì nếu chúng ta đang nói về một đơn vị (với đơn vị chỉ là một lớp), nó đơn giản hơn để tự động -mock tất cả các lớp khác và sử dụng các mocks.

Điều tôi đang cố gắng nói là nếu bạn có một lớp kiểm tra sử dụng cả hai số mainViewmockMainView, bạn không có bài kiểm tra đơn vị theo nghĩa nghiêm ngặt của từ - giống như bài kiểm tra tích hợp.

+0

Đây là một tạo phẩm của khung mocking được sử dụng trong ví dụ. NUnit.Mocks không linh hoạt như các khuôn mẫu giả khác. Mỗi mô hình được tạo, cấu hình và xác minh độc lập. Tôi đã chuyển sang sử dụng một thùng chứa để tạo và xác minh, chỉ để lại cấu hình của mỗi mô hình được xử lý riêng biệt. –

2

Hỗ trợ cuộc sống thử nghiệm đơn giản hơn rất nhiều nếu bạn sử dụng thùng chứa tự động chế nhạo như RhinoAutoMocker (một phần của StructureMap). Bạn sử dụng thùng chứa tự động chế nhạo để tạo lớp đang được kiểm tra và yêu cầu nó cho các phụ thuộc bạn cần cho (các) thử nghiệm. Các container có thể cần phải tiêm 20 thứ trong constructor nhưng nếu bạn chỉ cần kiểm tra một trong những bạn chỉ cần phải yêu cầu một trong đó.

using StructureMap.AutoMocking; 

namespace Foo.Business.UnitTests 
{ 
    public class MainPresenterTests 
    { 
     public class When_asked_to_add_an_account 
     { 
      private IAccountService _accountService; 
      private IAccount _account; 
      private MainPresenter _mainPresenter; 

      [SetUp] 
      public void BeforeEachTest() 
      { 
       var mocker = new RhinoAutoMocker<MainPresenter>(); 
       _mainPresenter = mocker.ClassUnderTest; 
       _accountService = mocker.Get<IAccountService>(); 
       _account = MockRepository.GenerateStub<IAccount>(); 
      } 

      [TearDown] 
      public void AfterEachTest() 
      { 
       _accountService.VerifyAllExpectations(); 
      } 

      [Test] 
      public void Should_use_the_AccountService_to_create_an_account() 
      { 
       _accountService.Expect(x => x.Create()).Return(_account); 
       _mainPresenter.AddAccount(); 
      } 
     } 
    } 
} 

Về cơ bản, tôi thích sử dụng dấu gạch dưới giữa các từ thay vì RunningThemAllTất cả khi tôi thấy dễ quét hơn. Tôi cũng tạo ra một lớp ngoài được đặt tên cho lớp đang được kiểm tra và nhiều lớp bên trong được đặt tên cho phương thức được thử nghiệm. Các phương pháp thử nghiệm sau đó cho phép bạn chỉ định các hành vi của phương pháp được thử nghiệm. Khi chạy trong NUnit, tính năng này cung cấp cho bạn ngữ cảnh như:

Foo.Business.UnitTests.MainPresenterTest 
    When_asked_to_add_an_account 
    Should_use_the_AccountService_to_create_an_account 
    Should_add_the_Account_to_the_View 
+0

+1 để giới thiệu các công cụ để giảm bớt việc áp dụng tính năng nâng cao được khuyến nghị cũng như tăng cường. –

0

Tôi nghĩ rằng nếu bạn thấy mình cần mocks, thiết kế của bạn không chính xác.

Các thành phần phải được xếp lớp. Bạn xây dựng và thử nghiệm các thành phần A trong sự cô lập. Sau đó, bạn xây dựng và kiểm tra B + A. Sau khi hạnh phúc, bạn xây dựng lớp C và kiểm tra C + B + A.

Trong trường hợp của bạn, bạn không cần "_mockAccountService". Nếu AccountService thực của bạn đã được kiểm tra, thì hãy sử dụng nó. Bằng cách đó bạn biết bất kỳ lỗi nào trong MainPresentor và không phải trong bản thân mô phỏng.

Nếu AccountService thực của bạn chưa được kiểm tra, hãy dừng lại. Quay trở lại và làm những gì bạn cần để đảm bảo nó hoạt động chính xác. Làm cho nó đến mức mà bạn có thể thực sự phụ thuộc vào nó, sau đó bạn sẽ không cần giả.

+0

Mocks sẽ hữu ích nếu bạn muốn A + B phát triển đồng thời bởi các đội khác nhau (nhà phát triển B sẽ cần một mô hình A). Và nếu thành phần A thực sự chậm, bạn sẽ rất vui mừng về một giả lập A trong khi devloping B. (Có nói rằng, trong thực tế tôi thấy mình làm A, sau đó thử nghiệm A + B bạn mô tả trong phần lớn các trường hợp). – timday

+0

Mocks là một cách tốt đẹp và dễ dàng để kiểm tra các đầu ra gián tiếp quan trọng và không an toàn để bỏ qua. –

+1

Khả năng sử dụng mocks để cô lập một lớp được kiểm tra có vẻ giống như một dấu hiệu của thiết kế chính xác, không thiết kế không chính xác. Tôi cảm thấy thoải mái hơn nhiều khi thiết kế các hệ thống của mình khi tôi có thể tiếp cận với jMock hơn là tôi đã từng sử dụng lớp này, được ràng buộc chặt chẽ với cách tiếp cận lớp lồng nhau của cơ sở dữ liệu. –

1

Vâng, thiết kế của bạn bị thiếu sót. Bạn đang sử dụng mocks :)

Nghiêm túc hơn, tôi đồng ý với người đăng trước đề xuất thiết kế của bạn nên được xếp lớp để mỗi lớp có thể được kiểm tra riêng biệt. Tôi nghĩ rằng đó là sai về nguyên tắc mã thử nghiệm nên thay đổi mã sản xuất thực tế - trừ khi điều này có thể được thực hiện tự động và minh bạch cách mã có thể được biên dịch để gỡ lỗi hoặc phát hành. Nó giống như nguyên lý bất định Heisenberg - một khi bạn có mocks trong đó, mã của bạn bị thay đổi nó trở thành một nhức đầu bảo trì và bản thân các mocks có tiềm năng để giới thiệu hoặc che dấu lỗi.

Nếu bạn có giao diện rõ ràng, tôi không có tranh cãi với việc thực hiện một giao diện đơn giản mô phỏng (hoặc mocks) giao diện chưa được thực hiện cho mô-đun khác. Mô phỏng này có thể được sử dụng trong cùng một cách chế giễu là, cho đơn vị thử nghiệm vv.

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