2009-06-10 15 views
5

Próbuję swoich sił w rozwoju opartym na zachowaniu i po drugie, zgaduję, że mój projekt został napisany. To mój pierwszy projekt typu greenfield i może to być po prostu mój brak doświadczenia. W każdym razie, tutaj jest prosta specyfikacja dla klasy, którą piszę. Jest napisany w NUnit w stylu BDD, zamiast używać dedykowanej struktury opartej na zachowaniu. Dzieje się tak dlatego, że celem projektu jest .NET 2.0 i wydaje się, że wszystkie środowiska BDD objęły środowisko .NET 3.5.Czy to jest kiepski projekt?

[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(); 
    } 
} 

Żaden z interfejsów używanych przez MainPresenter nie ma jeszcze żadnych prawdziwych implementacji. AccountService będzie odpowiedzialny za tworzenie nowych kont. Istnieje wiele implementacji IAccount zdefiniowanych jako osobne wtyczki. W środowisku uruchomieniowym, jeśli jest ich więcej niż jeden, użytkownik zostanie poproszony o wybranie typu konta do utworzenia. W przeciwnym razie AccountService po prostu utworzy konto.

Jedną z rzeczy, która mnie niepokoi, jest to, jak wiele mocków jest wymaganych do napisania pojedynczej specyfikacji/testu. Czy to tylko efekt uboczny używania BDD, czy też podchodzę do tego w niewłaściwy sposób?

[Aktualizacja]

Oto aktualna realizacja MainPresenter.AddAccount

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

wszelkie wskazówki, sugestie lub alternatywy mile widziane.

Odpowiedz

3

Podczas tworzenia od góry do dołu często zdarza się, że używasz wielu meczy. Potrzebne elementy nie są dostępne, więc musisz ich kpić. Powiedziawszy to, czujesz się jak test poziomu akceptacji. Z mojego doświadczenia wynika, że ​​BDD lub kontekst/specyfikacja zaczynają być nieco dziwne na poziomie testu jednostkowego. Na poziomie testów jednostkowych pewnie bym się robić coś więcej wzdłuż linii ...

 
when_adding_an_account 
    should_use_account_service_to_create_new_account 
    should_update_screen_with_new_account_details 

Możesz rozważyć swoje wykorzystanie interfejsu dla IAccount. Osobiście trzymam z zachowaniem interfejsów usług dla podmiotów domeny. Ale to bardziej osobiste preferencje.

Kilka innych drobnych sugestii ...

  • Można rozważyć użycie drwiącym ramy takie jak Rhino Mocks (lub Min), które pozwalają unikać ciągów dla swoich twierdzeń.
 

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

  • Jeśli robisz stylem BDD jeden wspólny wzór widziałem używa łańcuchowych zajęcia dla konfiguracji testowej. W przykładzie ...
 
public class MainPresenterSpec 
{ 
    // Protected variables for Mocks 

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

} 

[TestFixture] 
public class WhenUserAddsAccount : MainPresenterSpec 
{ 
    [Test] 
    public void ShouldCreateNewAccount() 
    { 
    } 
} 
  • Również polecam zmianę kodu do korzystania z klauzuli straży ..
 
    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

Kilka dobrych porad tutaj +1 –

1

Wydaje się, że jest to odpowiednia liczba prób dla prezentera z usługą, która ma przekazać konto.

To wydaje się raczej testem akceptacyjnym, a nie jednostkowym - być może, jeśli zmniejszysz swoją złożoność twierdzeń, znajdziesz mniejszy zestaw pytań, które będą drwić.

0

Możesz chcieć użyć MockContainers aby pozbyć się wszystkich udawanym zarządzania, podczas tworzenia prezentera. Bardzo upraszcza testy jednostkowe.

0

To jest w porządku, ale spodziewałbym się, że gdzieś tam znajdzie się pojemnik do automatyzacji IoC. Kod podpowiada maszynie testowej ręcznie (jawnie) przełączanie pomiędzy wyśmiewanymi i rzeczywistymi obiektami w testach, które nie powinny mieć miejsca, ponieważ jeśli mówimy o teście jednostka (z jednostką będącą tylko jedną klasą), łatwiej jest po prostu automatycznie -okolić wszystkie inne klasy i używać mocks.

Co próbuję powiedzieć to to, że jeśli masz klasę testową, która wykorzystuje zarówno mainView i mockMainView, nie ma testów jednostkowych w ścisłym znaczeniu tego słowa - bardziej jak test integracji.

+0

Jest to artefakt szyderczy ramy używane w przykładzie. NUnit.Mocks nie jest tak wszechstronny jak inne symulowane frameworki. Każda sztuczka jest niezależnie tworzona, konfigurowana i weryfikowana. Odtąd przełączałem się na taki, który używa pojemnika do tworzenia i weryfikacji, pozostawiając tylko konfigurację każdej sztuczki, która ma być obsługiwana osobno. –

2

Obsługa okresu próbnego jest o wiele prostsza, jeśli używasz automatycznego kpinającego pojemnika, takiego jak RhinoAutoMocker (część StructureMap). Korzystasz z automatycznego kpinającego kontenera, aby utworzyć testowaną klasę i poprosić o zależności potrzebne do testu. Kontener może potrzebować wstrzyknięcia 20 elementów w konstruktorze, ale jeśli potrzebujesz tylko jednego testu, musisz go tylko poprosić.

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(); 
      } 
     } 
    } 
} 

Strukturalnie wolę używać podkreśleń między słowami zamiast RunningThemAllTogether, ponieważ łatwiej jest zeskanować. Tworzę także zewnętrzną klasę nazwaną dla badanej klasy i wiele klas wewnętrznych nazwanych dla testowanej metody. Metody testowe pozwalają następnie określić zachowania testowanej metody. Po uruchomieniu w NUnit daje to kontekst taki jak:

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 za polecanie narzędzi, które ułatwiają przyjęcie zalecanego wzmocnienia, a także ulepszenie. –

0

Uważam, że jeśli potrzebujesz makiet, Twój projekt jest nieprawidłowy.

Komponenty powinny być warstwowe. Kompilujesz i testujesz komponenty A w izolacji. Następnie buduj i testuj B + A. Gdy jesteś szczęśliwy, budujesz warstwę C i testujesz C + B + A.

W twoim przypadku nie powinieneś potrzebować "_mockAccountService". Jeśli Twoja prawdziwa usługa AccountService została przetestowana, po prostu z niej skorzystaj. W ten sposób wiesz, że wszelkie błędy są w MainPresentor, a nie w fałszywym.

Jeśli twoja usługa AccountService nie została przetestowana, zatrzymaj się. Wróć i rób to, czego potrzebujesz, aby upewnić się, że działa poprawnie. Dosuń go do punktu, w którym naprawdę możesz na nim polegać, wtedy nie będziesz potrzebować sztuczki.

+0

Mocks byłyby przydatne, gdybyś chciał, aby A + B było rozwijane równocześnie przez różne zespoły (deweloperzy B będą potrzebować udawanego A). A jeśli komponent A jest naprawdę powolny, będziesz bardzo zadowolony z udawanego A podczas devlopowania B. (Powiedziawszy to, w praktyce robię to, co robię, a następnie test A + B opisujesz w ogromnej większości przypadków). – timday

+0

Mocks to miły i łatwy sposób na przetestowanie pośrednich wyjść, które są ważne i niebezpieczne do zignorowania. –

+1

Możliwość użycia mocków do izolowania badanej klasy wydaje się oznaką prawidłowego projektu, a nie nieprawidłowego projektu. Dużo bardziej swobodnie podchodzę do projektowania moich systemów, kiedy mogę sięgnąć po jMock, niż kiedyś z warstwami, ściśle związanymi z podejściem opartym na zagnieżdżeniu bazy danych. –

1

Tak, Twój projekt jest wadliwy. Używasz makietek :)

Poważniej, zgadzam się z poprzednim plakatem, który sugeruje, że twój projekt powinien być warstwowany, aby każda warstwa mogła być testowana osobno. Myślę, że zasadniczo nie ma racji, że kod testowy powinien zmienić rzeczywisty kod produkcyjny - chyba że można to zrobić automatycznie i przejrzyście, w jaki sposób można skompilować kod do debugowania lub wydania.

To jest tak jak zasada nieoznaczoności Heisenberga - kiedy już masz drwiny, twój kod jest tak zmieniony, że staje się bóle głowy konserwacji i same makiety mają potencjał do wprowadzenia lub maskowania błędów.

Jeśli masz czyste interfejsy, nie kłócę się z implementacją prostego interfejsu, który symuluje (lub kpi) niezaimplementowany interfejs do innego modułu. Ta symulacja może być użyta w taki sam sposób, jak szyderstwo, do testowania jednostek itp.