Scherzando il contesto dell'applicazione

1

Abbiamo un contesto dell'applicazione che è una classe statica di nome Holder che contiene diverse proprietà statiche inizializzate da oggetti che sono utilizzati in tutta l'applicazione.

In tutti i test unitari, dobbiamo inizializzare il contesto con dei mock. Nell'attuale implementazione, la difesa del contesto stessa viene reinizializzata, quindi contiene Singletons. Per questo motivo tutti i test condividono i mock, quindi si assumono la responsabilità di cancellarli correttamente, voglio dire cancellare il loro comportamento.

Cosa ne pensi di avere normalmente uno stato così condiviso tra i test unitari? E se consideri questo come una cattiva pratica, allora sarò apprezzato per alcuni consigli.

Aggiornamento. Mi dispiace per aver ingannato. Una delle cose più importanti da ricordare è che non riesco a ricreare più volte lo stato interiore della classe Holder. Quali opzioni ho in questo caso?

    
posta EngineerSpock 13.11.2014 - 09:47
fonte

2 risposte

4

Ho avuto un problema simile con il mio codice con stato globale via singelton.

Poiché il commento di @ Thomas-Junk-s e @ SHODAN mi ha suggerito di refactored il mio codice per rendere statico lo stato globale non statico attraverso la dependency injection del costruttore.

Esempio

codice originale

public static class Global {
    public static int getSomeGlobalState() {...}
}

public class MyClass {
    public MyClass(...) {...}

    public myFunction(....) {
        ....
        int i = Global.getSomeGlobalState()
        ....
    }
}

refactored metodo globale statico al metodo globale non statico

public class Global {
    private Global global = new Global();

    // singelton to keep old code compatible
    public static Global getGlobal() {return global;}

    // not static anymore
    public int getSomeGlobalState() {...}
}

public class MyClass {
    public MyClass(...) {...}

    public myFunction(....) {
        ....
        // replacing call to static member Global.getSomeGlobalState()
        int i = Global.getGlobal().getSomeGlobalState()
        ....
    }
}

refactoring rimosso depency alla classe statica Global dal metodo myFunction al costruttore

public class MyClass {
    Global global;

    // depricated constructor compatible with old api.
    public MyClass(...) {this(..., ...;Global.getGlobal());}

    // new constructor to be used in future
    public MyClass(..., Global global) {...;this.global=global;}

    public myFunction(....) {
        ....
        int i = global.getSomeGlobalState()
        ....
    }
}   

dopo aver introdotto un di-contenitore: refactored per sostituire la classe statica singelton Gloal nel contenitore

public class Global {
    public Global() {...}

    public int getSomeGlobalState() {...}
}

public class MyClass {
    public MyClass(..., Global global) {...;this.global=global;}

    public myFunction(....) {
        ....
        int i = global.getSomeGlobalState()
        ....
    }
}   

Ogni refactoring fatto shure che il codice, che ueses MyClass non ha bisogno di modifiche.

Nell'ultimo refactoring ho aggiunto un di-contenitore che è responsabile della creazione di un singelton Global -Class e che inietta l'istanza globale nei suoi clienti come MyClass in questo modo il vecchio costruttore di MyClass potrebbe essere rimosso.

[aggiornamento 2014-12-11]

se non desideri to add a dependency all over the codebase come suggerito dal tuo commento, puoi inviare l'ultimo passo di refactoring:

public class MyClass {
    // constructor used in consuming classes.
    public MyClass(...) {this(..., ...;Global.getGlobal());}

    // constructor used for unittests where you can replace global.
    public MyClass(..., Global global) {...;this.global=global;}

} 
    
risposta data 13.11.2014 - 13:25
fonte
0

Sì, è meglio non condividere stato tra unit test e renderli indipendenti gli uni dagli altri. Ma ciò non significa che non puoi condividere codice tra unit test.

Ad esempio (utilizzando NUnit + FluentAssertions + FakeItEasy):

    [TestFixture]
    [Category("Unit")]
    public class when_rolling_back_unit_of_work : UnitOfWorkScenario
    {
        [Test]
        public void should_rollback_and_dispose()
        {
            //act
            UnitOfWork.Do(uow => uow.Rollback());

            //assert
            A.CallTo(() => SessionScope.Commit()).MustNotHaveHappened();
            A.CallTo(() => SessionScope.Rollback()).MustHaveHappened(Repeated.Exactly.Once);
            A.CallTo(() => SessionScope.Dispose()).MustHaveHappened(Repeated.Exactly.Once);
        }
    }

Nell'esempio sopra, deriviamo il nostro test dalla classe UnitOfWorkScenario che condivide la logica di inizializzazione comune tra i nostri test per UnitOfWork.

Questa classe assomiglia a:

public class UnitOfWorkScenario
    {
        protected ISessionScopeFactory SessionScopeFactory { get; set; }
        protected ISessionScope SessionScope { get; set; }
        public UnitOfWorkScenario()
        {
            SessionScopeFactory = A.Fake<ISessionScopeFactory>();
            SessionScope = A.Fake<ISessionScope>();
            A.CallTo(() => SessionScopeFactory.Open()).Returns(SessionScope);
            Func<string, ISessionScopeFactory> sessionScopeFactoryExtractor = name => (name == UnitOfWorkSettings.Default.StorageName ? SessionScopeFactory : null);
            UnitOfWork.SessionScopeFactoryExtractor = sessionScopeFactoryExtractor;
        }
    }

Il punto è che è possibile condividere la logica di inizializzazione comune tra i test, allo stesso tempo mantenendoli indipendenti gli uni dagli altri. Tuttavia, se hai bisogno di ripulire qualcosa (proprietà statica come esempio), potresti farlo nel costruttore della classe base, o usare le caratteristiche del tuo framework unit test (es. Metodo con l'attributo TearDown in NUnit) per farlo, MA specificando questa logica in l'unico posto nella tua classe base.

Scusa se ho frainteso un po 'la tua domanda.

    
risposta data 13.11.2014 - 11:51
fonte

Leggi altre domande sui tag