Domanda

Sto cercando la mia mano a un comportamento guidato lo sviluppo e mi sto trovando me stesso secondo indovinare il mio progetto come sto scrivendo. Questo è il mio primo progetto greenfield e può essere solo la mia mancanza di esperienza. Comunque, ecco una semplice specifica per la classe (s) sto scrivendo. E 'scritto in NUnit in uno stile BDD invece di utilizzare un quadro guidato comportamento dedicata. Questo perché gli obiettivi del progetto NET 2.0 e tutti i quadri BDD sembrano aver abbracciato .NET 3.5.

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

nessuna delle interfacce utilizzate da MainPresenter ha ancora implementazioni reali. AccountService sarà responsabile della creazione di nuovi account. Ci possono essere molteplici implementazioni di IAccount definiti come plugin separati. In fase di esecuzione, se non v'è più di un allora l'utente verrà chiesto di scegliere quale tipo di conto da creare. In caso contrario AccountService sarà sufficiente creare un account.

Una delle cose che mi ha disagio è come molti mock sono necessari solo per scrivere una sola spec / test. Questo è solo un effetto collaterale di utilizzare BDD o sto andando su questa cosa nel modo sbagliato?

[Aggiornamento]

Ecco l'attuale implementazione di MainPresenter.AddAccount

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

Tutte le punte, suggerimenti o alternative benvenuti.

È stato utile?

Soluzione

Quando si fa parte superiore per lo sviluppo verso il basso è abbastanza comune trovare da soli, usando un sacco di schernisce. I pezzi necessari non sono lì così naturalmente è necessario per derisione. Detto questo si sente come un test di livello di accettazione. Nella mia esperienza BDD o Contesto / specifica inizia a essere un po 'strano a livello di unit test. A livello di unit test avrei probabilmente fare qualcosa di più lungo le linee di ...

when_adding_an_account
   should_use_account_service_to_create_new_account
   should_update_screen_with_new_account_details

Si potrebbe voler riconsiderare l'uso di un'interfaccia per IAccount. Io personalmente bastone con il mantenimento di interfacce per i servizi di più di entità del dominio. Ma questo è più di una preferenza personale.

Un paio di altri piccoli suggerimenti ...

  • Si consiglia di considerare l'utilizzo di un quadro di scherno, come Rhino Mocks (o MOQ) che consentono di evitare l'uso di stringhe per le vostre affermazioni.
  _mockAccountService.Expect(mock => mock.Create())
     .Return(_account);

  • Se si sta facendo lo stile BDD uno schema comune che ho visto sta utilizzando le classi concatenati per la configurazione di prova. Nel tuo esempio ...
public class MainPresenterSpec 
{
    // Protected variables for Mocks 

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

}

[TestFixture]
public class WhenUserAddsAccount : MainPresenterSpec
{
    [Test]
    public void ShouldCreateNewAccount()
    {
    }
}
  • Anche io consiglierei di cambiare il codice per utilizzare una clausola di guardia ..
     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);
     }

Altri suggerimenti

Il supporto vitale di prova è molto più semplice se si utilizza un contenitore beffardo auto come RhinoAutoMocker (parte di StructureMap ). È possibile utilizzare il contenitore automatico beffardo per creare la classe in prova e chiedere per le dipendenze necessarie per la prova (s). Il contenitore potrebbe aver bisogno di iniettare 20 cose nel costruttore, ma se avete solo bisogno di testare quello che si avrà solo a chiedere per quello.

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

Strutturalmente Io preferisco usare sottolineature tra le parole, invece di RunningThemAllTogether come trovo più facile per la scansione. Ho anche creare una classe esterna chiamata per la classe in prova e più classi interne nome per il metodo in prova. I metodi di prova quindi consentono di specificare i comportamenti del metodo in prova. Quando viene eseguito in NUnit questo ti dà un contesto come:

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

Che sembra il numero corretto di mock per un presentatore con un servizio che dovrebbe restituire un account.

Questo sembra più simile a un test di accettazione, piuttosto che un test di unità, anche se - forse, se si ridotto il vostro complessità affermazione si dovrebbe trovare un insieme ridotto di preoccupazioni prendendo in giro

.

Sì, il vostro disegno è viziata. Si utilizza schernisce:)

Più seriamente, sono d'accordo con la recensione precedente che suggerisce il vostro disegno dovrebbe essere a strati, in modo che ogni strato può essere testato separatamente. Penso che sia sbagliato in linea di principio che il codice test dovrebbe modificare il codice di produzione effettiva -. A meno che questo può essere fatto automatico e trasparente il codice modo può essere compilato per il debug o rilascio

E 'come il principio di indeterminazione di Heisenberg -. Una volta che hai le prende in giro in là, il codice è così modificato diventa un mal di testa manutenzione e la prende in giro se stessi hanno il potenziale per introdurre o bug maschera

Se si dispone di interfacce pulite, non ho alcun contenzioso con l'attuazione di una semplice interfaccia che simula (o deride) non implementato un'interfaccia ad un altro modulo. Questa simulazione potrebbe essere utilizzato allo stesso modo beffardo è, per unità di test, ecc.

Si potrebbe desiderare di utilizzare MockContainers al fine di sbarazzarsi di tutta la gestione finta, durante la creazione del presentatore. Semplifica unità test molto.

Questo va bene, ma mi sarei aspettato un contenitore IoC automocking lì da qualche parte. Il codice allude alla scrittrice di prova manualmente (esplicitamente) il passaggio tra gli oggetti deriso e reali nei test, che non dovrebbe essere il caso, perché se stiamo parlando di un test di Unità (con unità di essere una sola classe), è più semplice solo auto-finto tutte le altre classi e utilizzare i schernisce.

Quello che sto cercando di dire è che se si dispone di una classe di test che utilizza sia mainView e mockMainView, non avere una prova di unità nel senso stretto del termine -. Più come un test di integrazione

E 'mia opinione che se vi trovate a dover prende in giro, il vostro disegno non è corretto.

I componenti dovrebbero essere stratificati. Si costruisce e componenti del test A in isolamento. Poi si costruisce e prova B + A. Una volta felice, si costruisce strato C e test di C + B + A.

Nel tuo caso non dovrebbe essere necessario un "_mockAccountService". Se il vostro vero AccountService è stato testato, poi basta usarlo. In questo modo si sa eventuali bug sono in MainPresentor e non nel finto sé.

Se il tuo vero AccountService non è stato testato, stop. Tornare indietro e fare ciò che è necessario per garantire il corretto funzionamento. Get it al punto in cui si può davvero dipendere da esso, quindi non sarà necessario il finto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top