Unit Test di un metodo che contiene due parametri del tipo di output

2

Stavo seguendo un libro Art of Unit Testing , Questo libro dice che non devi avere alcuna logica nei tuoi questo riduce la leggibilità del test, o potrei testare troppe cose alla volta. Supponiamo che ci sia un metodo con firma come sotto:

public int DoSomething(int variable1, out string variable2, out float variable3)
{
...
}

Come dovrei testare questo metodo, come devo affermare sul valore restituito, variabile2, variabile3 ???

Metodo di prova del campione che verifica tutti questi tre aspetti:
    public void DosSomethingTest(){
        //add code for arrange
        int expectedvalue=12;
        string expectedVariable1="I am expected";
        string expectedVariable3=15.9;
        string actualVariable1, actualVariable3;
        var sut=new Foo();
        int actualValue=sut.DoSomething(15, out actualVariable1, out actualVariable3);

        //this is where I am testing 3 concerns in one test. Is there a better way? 
        if(actualValue==expectedvalue){
            if(expectedVariable1==actualVariable1){
                if(expectedVariable3!=actualVariable3){
                    Assert.Fail("Unexpected variable2 returned");//pseudo code
                }
            }
            else{
                Assert.Fail("Unexpected variable1 returned");//pseudo code
            }
        }
        else{
            Assert.Fail("Unexpected Value returned");//pseudo code
        }
    }

Approccio 2

Metodi di test separati che verificano i problemi separatamente:
    public void DosSomething_Passing15_Returns12Test(){
        //add code for arrange
        int expectedvalue=12;
        string expectedVariable1="I am expected";
        string expectedVariable3=15.9;
        string actualVariable1, actualVariable3;
        var sut=new Foo();
        int actualValue=sut.DoSomething(15, out actualVariable1, out actualVariable3);

        Assert.IsTrue(actualValue, expectedvalue);
    }

    public void DosSomething_Passing15_ReturnsExpectedVariable1Test(){
        //add code for arrange
        int expectedvalue=12;
        string expectedVariable1="I am expected";
        string expectedVariable3=15.9;
        string actualVariable1, actualVariable3;
        var sut=new Foo();
        int actualValue=sut.DoSomething(15, out actualVariable1, out actualVariable3);

        Assert.IsTrue(actualVariable1, expectedVariable1);
    }

    public void DosSomething_Passing15_ReturnsExpectedVariable2Test(){
        //add code for arrange
        int expectedvalue=12;
        string expectedVariable1="I am expected";
        string expectedVariable3=15.9;
        string actualVariable1, actualVariable3;
        var sut=new Foo();
        int actualValue=sut.DoSomething(15, out actualVariable1, out actualVariable3);

        Assert.IsTrue(actualVariable3, expectedVariable3);
    }

L'approccio 2 non aderisce a "I test non dovrebbero testare più di un problema"?

Conclusione

Se la riprogettazione di SUT è possibile:
  1. In teoria dovrebbe esserci un parametro no out invece di una classe aggregata dovrebbe essere valutato in Assert.
  2. Se ci sono parametri out non collegati, il metodo SUT dovrebbe essere refactored in più metodi, quindi quei nuovi metodi dovrebbero avere i propri metodi di test e quindi testare solo una preoccupazione.

Se la riprogettazione di SUT non è possibile, come da risposta ci saranno tre Assert

    
posta shankbond 30.03.2014 - 10:58
fonte

5 risposte

18

Se qualcuno ti dice che c'è qualcosa di sbagliato in:

int actualValue=sut.DoSomething(15, out actualVariable1, out actualVariable2);

AssertEquals("actualValue", 1, actualValue);
AssertEquals("actualVariable1", 23, actualVariable1);
AssertEquals("actualVariable2", 14, actualVariable2);

allora è molto probabile che siano eccessivamente pedanti, applicando una regola alla cieca senza comprendere nulla del contesto. Voglio dire, probabilmente dovresti prendere in considerazione la possibilità di semplificare una funzione con più argomenti.

Ma se li hai, è così che li metti alla prova.

    
risposta data 30.03.2014 - 13:16
fonte
2

Questa funzione viene chiamata un numero di volte con valori scelti per variabile1. Per ciascun valore di input, si controlla che ciascuna delle variabili di output e il valore di ritorno corrispondano al risultato previsto. Si scrivono quelli come singoli asseriti su ciascun valore previsto, in modo che un errore individua l'errore. Ogni valore di input e set di valori di output costituisce un singolo test.

I valori che scegli dovrebbero estendersi nell'intervallo di valori consentiti e dovrebbero esplorare in particolare tutti i valori noti che rappresentano condizioni di bordo per l'algoritmo utilizzato dalla funzione. Alcuni chiamano questo test "scatola grigia" (non del tutto nero).

Esegui anche il test (o prova a testare) la funzione per un comportamento anomalo (forse la variabile1 deve essere positiva, quindi controlli che non funzioni correttamente per i valori negativi.)

Un approccio basato sui dati funziona bene, dove tutti i valori rilevanti sono pre-calcolati e memorizzati in un database o altra struttura. Il test unitario effettivo dovrebbe contenere poco o nessun calcolo.

    
risposta data 30.03.2014 - 12:01
fonte
1

Words Of Wisdom per quelle anime coraggiose che stanno abbracciando i test unitari

Dopo aver lottato con questa domanda e aver cercato su google giorni interminabili, ho finalmente raggiunto conclusioni diverse:

Un buon Test unitario dovrebbe testare solo una preoccupazione , ma può testare molteplici aspetti .

Nella mia domanda, sto chiedendo di più aspetti, quindi è Ok avere tre asserzioni e potrebbe / non essere necessario per rifattorizzare il metodo SUT (Sistema sotto Test) come da risposte date:

Nei casi in cui ci sono preoccupazioni diverse che essenzialmente significa :

Different inputs and their corresponding outputs

come mostrato di seguito:

// Funziona solo in caso di Nunit.

 [TestCase(15, "I am expected", 15.9, 12)]
 [TestCase(17, "I am expected1", 100, 12)]
 [TestCase(18, "I am expected2", 112, 12)]
 public void DoSomething_ReturnsExpectedTest(int inputValue, string expectedVariable1, int expectedVariable3, int expectedValue){
    //add code for arrange
    string actualVariable1, actualVariable3;
    var sut=new Foo();
    int actualValue=sut.DoSomething(15, out actualVariable1, out actualVariable3);

    Assert.IsTrue(actualValue, expectedvalue);
    Assert.IsTrue(actualVariable1, expectedVariable1);
    Assert.IsTrue(actualVariable3, expectedVariable3);    }
    
risposta data 13.04.2014 - 18:11
fonte
0

L'idea alla base di "un metodo dovrebbe testare una cosa" è così che puoi immediatamente vedere che cosa sta causando il problema quando il test fallisce.

Se hai una asserzione su tre cose diverse, allora dovrai vedere che cosa sta andando esattamente male.

Un altro vantaggio di suddividerli è quindi avere una panoramica su cosa funziona e cosa no quando un assert fallisce. Se metti tutti e tre nello stesso metodo e il primo asserimento fallisce, non saprai se gli ultimi due funzionano. Ciò aumenterà la difficoltà per risolvere il problema: non saprai quando interromperesti accidentalmente un altro parametro di output.

A questo punto devi prendere una decisione da solo: consideri queste tre variabili di output strettamente correlate o vale la pena dividerci?

Se sono strettamente correlati puoi tenerli insieme ma se non lo sono ti consiglierei di metterli in metodi separati. Dovresti estrarre la loro logica comune in un altro metodo in modo da non dover scrivere due volte la configurazione.

    
risposta data 31.03.2014 - 18:46
fonte
-1

Un assert è un incapsulamento della logica di test:

assert foo(x) is y

può essere scritto come

if foo(x) is not y then throw AssertionError

L'uso di assert è incoraggiato poiché è più breve, più leggibile e le strutture di testing unitario potrebbero riconoscerlo e trattarlo in modo speciale.

Ora, vuoi avere quei vantaggi per le asserzioni che incapsulano una logica diversa - una che verifica anche i parametri. Per questo - dovresti scrivere il tuo assistente di asserzione!

La logica sarà all'interno dell'assistente helper e la dichiarazione di asserzione rimarrà priva di logica. Vero: la sintassi sarà un po 'complessa, in quanto è necessario specificare il metodo da testare, i parametri di input, il valore di ritorno previsto ei valori dei parametri previsti. Tuttavia, il fatto che tu abbia dei parametri vuol dire che stai usando un linguaggio che non è Java, quindi dovresti essere in grado di ottenere una bella sintassi, che può essere letta con le giuste convenzioni di interruzione di riga e di indentazione.

Forse il tuo framework di testing ha persino un aiuto simile!

UPDATE

Sei preoccupato di testare più cose con l'asserzione. OOP può aiutarti qui: prima crea un oggetto che incapsula la chiamata specifica al metodo, quindi utilizza i metodi di quell'oggetto per far valere le diverse parti del risultato di tale chiamata:

ComplexAssert.AssertWithOutParameters(sut.DoSomething,15)
    .AssertReturnValue(expectedValue,"Unexpected Value returned")
    .AssertOutParameter(1,expectedVariable1,"Unexpected variable1 returned")
    .AssertOutParameter(2,expectedVariable3,"Unexpected variable2 returned");

Ora tutto è pulito e leggibile: il metodo testato viene chiamato una sola volta e ogni asserzione ottiene la propria chiamata al metodo.

    
risposta data 30.03.2014 - 12:23
fonte

Leggi altre domande sui tag