Qual è la migliore pratica per un test di regressione che voglia conoscere le informazioni incapsulate?

3

Supponi di avere due classi: Alfa e Bravo.

Alpha costruisce una nuova classe Bravo durante il proprio costruttore e non mostra visibilità pubblica ad esso.
Bravo ha una stringa chiamata charlie (e un metodo per ottenere quella stringa).

Supponiamo che tu abbia impostato un test che costruisca una nuova classe Alpha. Alla fine di questo test, si vuole affermare che bravo.getCharlie () è uguale ad alcune String previste. Come dovrebbe il test ottenere l'accesso a questa stringa?

Dovremmo aggiungere un metodo getBravo () ad Alpha solo perché questo test sembra averne bisogno?

assertEquals("expectedString", alpha.getBravo().getCharlie());

Dovremmo continuare a nascondere Bravo ma aggiungere un getter per Charlie all'interno di Alpha?

assertEquals("expectedString", alpha.getCharlie());

(dove alpha.getCharlie () è solo un modo di scrivere alpha.getBravo (). getCharlie () senza rendere pubblico Bravo Getter)

Altre opzioni?

    
posta Wallace Brown 07.05.2018 - 21:49
fonte

4 risposte

9

Hai scritto

Alpha constructs a new Bravo class during its own constructor and exposes no public visibility to it

Quindi, qualunque cosa sia Alpha , usando Bravo è un dettaglio di implementazione interno, giusto? Quindi questo

at the end of this test, you want to assert that bravo.getCharlie()

significa che chiedi come scrivere un test che si basa su questo dettaglio di implementazione interna .

Nella maggior parte dei casi, è meglio evitare questo . Se possibile, scrivi i tuoi test utilizzando l'interfaccia pubblica di Alpha in modo esclusivo, quindi i dettagli di implementazione possono essere modificati in seguito più facilmente senza la necessità di modificare il test. Se bravo.getCharlie() è utilizzato da qualche parte all'interno di Alpha , prova a richiamare un metodo pubblico che si basa su quel risultato.

(Notare che tutto ciò che è stato fatto / non rendere pubbliche le cose private solo a scopo di test è stato chiesto e risposto in questo sito più volte. Inizia con questo o questo ).

    
risposta data 07.05.2018 - 22:15
fonte
1

L'opzione con cui di solito vado, sta iniettando la dipendenza. Ciò mi consente di iniettare un oggetto fittizio durante i test e verificare l'output in questo modo. Programmo anche un'interfaccia, invece di usare una classe concreta per rendere più semplice il mocking. Non hai specificato la lingua - sto assumendo Java, ma tutto quello che so è C # - quindi spero che sia abbastanza vicino:

Quindi nel tuo caso avresti:

public interface IBravo
{
   string Charlie { get; set; }
}

public class Bravo : IBravo
{
   public string Charlie { get; set; }
}       

private readonly IBravo _bravo;
public class Alpha(IBravo bravo)
{
   bravo = _bravo;
}   

void main()
{
   Alpha alpha = new Alpha(new Bravo());
}

Quindi, durante i test delle tue unità, dovresti eseguire le seguenti operazioni (usando il framework per il mocking di Moq):

[Test]
public void Test_Charlie_String()
{
   var bravoMock = new Mock<IBravo>();
   bravoMock.SetupProperty(f => f.Charlie);

   var alpha = new Alpha(mockBravo.Object);

   Assert.AreEqual("expectedString", mockBravo.Charlie);
}
    
risposta data 07.05.2018 - 22:05
fonte
1

Let's say you have set up a test that constructs a new Alpha class. At the end of this test, you want to assert that bravo.getCharlie() is equal to some expected String. How should the test get access to this String?

Una delle idee interessanti alla base del "testing driven design": se il tuo test è difficile da scrivere, è un suggerimento che potrebbe esserci un difetto nella progettazione.

Alpha constructs a new Bravo class during its own constructor and exposes no public visibility to it.

Questo è un odore di codice , un suggerimento che qualcosa è andato storto nel tuo codice.

Lo schema di corrispondenza del tuo codice è che hai un effetto collaterale (una scrittura allo stato Charlie) che stai cercando di valutare nel tuo test. In realtà è piuttosto comune avere effetti collaterali che non possono essere letti più tardi (ad esempio, una scrittura sulla console).

Il modello comune per testare una cosa del genere è utilizzare un TestDouble , in modo che tu possa catturare il comportamento.

Ma per che funziona, devi implementare il tuo codice in modo tale che sia apri all'estensione .

Misko Hevery ha scritto una serie di saggi su questioni relative ai costruttori; il suo Top 10 cose che rendono il tuo Codice difficile da testare sarebbe un buon punto di partenza.

Just ask for all of the collaborators you need in your constructor.

L'idea è che mentre questo esempio è chiuso all'estensione

Alpha () {
    this.beta = new Beta();
}

rendere la classe aperta all'estensione è semplicemente una questione di ri-organizzazione del codice leggermente.

Alpha (Beta beta) {
    this.beta = beta;
}

Tuttavia, se Alpha() fa parte dell'interfaccia pubblicata , la modifica della firma in questo modo potrebbe rompere la compatibilità con le versioni precedenti. Di solito è una cattiva idea, quindi potresti invece fare questa modifica

Alpha () {
    this(new Beta());
}

Alpha (Beta beta) {
    this.beta = beta;
}

Il costruttore che è difficile da testare delega il suo lavoro al costruttore che è facile da testare.

Ovviamente, questo a sua volta significa che il costruttore no argument non è coperto da questo test - abbiamo scritto codice migliore, ma i nostri numeri di copertura del test sono diventati "peggiori". Quindi devi decidere come riconciliarlo.

Nel mio caso, ho permesso a me stesso di sentirsi a proprio agio con i consigli di Kent Beck

I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence....

e C.A.R. Hoare

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies....

Il costruttore di default qui incontra la mia barra per "ovviamente nessuna carenza"; il codice è troppo semplice per essere la fonte di un errore.

    
risposta data 08.05.2018 - 15:24
fonte
-1

Per vedere Charlie devi provare Bravo, non Alpha. Charlie è un'implementazione dettagliata che Alpha non è interessata, quindi anche il tuo test non avrà alcun interesse su questo.

Quindi, se hai un codice come:

class Alpha {
    Bravo bravo;
}

class Bravo {
    Charlie charlie;
}

class Charlie {

}

I tuoi test saranno come:

//AlphaTest
assertEquals(expectedBravo, alpha.getBravo());

//BravoTest
assertEquals(expectedCharlie, bravo.getCharlie());
    
risposta data 07.05.2018 - 22:47
fonte

Leggi altre domande sui tag