I risultati attesi del test unitario dovrebbero essere hardcoded?

24

I risultati attesi di un test unitario devono essere hardcoded o possono dipendere da variabili inizializzate? I risultati hardcoded o calcolati aumentano il rischio di introdurre errori nel test dell'unità? Ci sono altri fattori che non ho considerato?

Per esempio, quale di questi due è un formato più affidabile?

[TestMethod]
public void GetPath_Hardcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\Output Folder\fields\that later\determine\a folder";
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

[TestMethod]
public void GetPath_Softcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\Output Folder\" + string.Join("\", target.Field1, target.Field2, target.Field3, target.Field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

MODIFICA 1: In risposta alla risposta di DXM, l'opzione 3 è una soluzione preferita?

[TestMethod]
public void GetPath_Option3()
{
    string field1 = "fields";
    string field2 = "that later";
    string field3 = "determine";
    string field4 = "a folder";
    MyClass target = new MyClass(field1, field2, field3, field4);
    string expected = "C:\Output Folder\" + string.Join("\", field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}
    
posta Hand-E-Food 22.12.2011 - 23:30
fonte

5 risposte

20

Penso che il valore atteso calcolato porti a casi di test più solidi e flessibili. Inoltre, utilizzando i nomi delle variabili validi nell'espressione che calcola il risultato previsto, è molto più chiaro da dove proviene il risultato previsto.

Detto questo, nel tuo esempio specifico NON mi fiderei del metodo "Softcoded" perché usa il tuo SUT (sistema sotto test) come input per i tuoi calcoli. Se c'è un bug in MyClass in cui i campi non sono archiviati correttamente, il test verrà effettivamente passato perché il calcolo del valore previsto utilizzerà la stringa sbagliata proprio come target.GetPath ().

Il mio suggerimento sarebbe quello di calcolare il valore atteso dove ha senso, ma assicurati che il calcolo non dipenda da alcun codice dal SUT stesso.

In risposta all'aggiornamento dell'OP alla mia risposta:

Sì, in base alle mie conoscenze, ma in qualche modo limitata esperienza nel fare TDD, sceglierei l'opzione # 3.

    
risposta data 22.12.2011 - 23:39
fonte
14

Cosa succede se il codice era come segue

MyTarget() // constructor
{
   Field1 = Field2 = Field3 = Field4 = "";
}

Il tuo secondo esempio non catturerebbe il bug, ma il primo esempio sarebbe.

In generale, mi raccomando contro il soft coding perché potrebbe nascondere bug. Ad esempio:

string expected = "C:\Output Folder" + string.Join("\", target.Field1, target.Field2, target.Field3, target.Field4);

Puoi individuare il problema? Non si farebbe lo stesso errore in una versione codificata. È più difficile ottenere i calcoli corretti, quindi i valori hardcoded. Questo è il motivo per cui preferisco lavorare con valori codificati e poi con codificatori software.

Ma ci sono delle eccezioni. Cosa succede se il codice deve essere eseguito su Windows e Linux? Non solo il percorso deve essere diverso, deve utilizzare diversi separatori di percorso! Calcolare il percorso usando funzioni che astraggono la differenza tra potrebbe avere senso in quel contesto.

    
risposta data 22.12.2011 - 23:52
fonte
4

Secondo me, entrambi i tuoi suggerimenti sono tutt'altro che ideali. Il modo ideale per farlo è questo:

[TestMethod]
public void GetPath_Hardcoded()
{
    const string f1 = "fields"; const string f2 = "that later"; 
    const string f3 = "determine; const string f4 = "a folder";

    MyClass target = new MyClass( f1, f2, f3, f4 );
    string expected = "C:\Output Folder\" + string.Join("\", f1, f2, f3, f4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

In altre parole, il test dovrebbe funzionare esclusivamente in base all'input e all'output dell'oggetto e non in base allo stato interno dell'oggetto. L'oggetto dovrebbe essere trattato come una scatola nera. (Tralascio altri problemi, come l'inappropriatezza nell'uso di string.Join invece di Path.Combine, perché questo è solo un esempio.)

    
risposta data 22.12.2011 - 23:58
fonte
2

Ci sono due aspetti nella discussione:

1. Utilizzo del target stesso per il caso di test
La prima domanda dovrebbe / puoi usare la classe stessa per fare affidamento e ottenere parte del lavoro svolto nello stub del test? - La risposta è NO in quanto, in generale, non devi mai fare ipotesi sul codice che stai testando. Se questo non viene fatto correttamente, nel tempo i bug diventano immuni a qualche test unitario.

2. Hardcoding
dovresti hard code ? Di nuovo la risposta è No . perché come qualsiasi altro software, la difficile codificazione delle informazioni diventa difficile quando le cose si evolvono. Ad esempio, quando si desidera modificare nuovamente il percorso sopra, è necessario scrivere un'unità aggiuntiva o continuare a modificare. Un metodo migliore consiste nel mantenere l'input e la data di valutazione derivata dalla configurazione separata che può essere facilmente adattata.

per esempio qui è come avrei corretto lo stub del test.

[TestMethod]
public void GetPath_Tested(int CaseId)
{
    testParams = GetTestConfig(caseID,"testConfig.txt"); // some wrapper that does read line and chops the field. 
    MyClass target = new MyClass(testParams.field1, testParams.field2);
    string expected = testParams.field5;
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}
    
risposta data 23.12.2011 - 03:50
fonte
0

Ci sono molti concetti possibili, fatti alcuni esempi per vedere la differenza

[TestMethod]
public void GetPath_Softcoded()
{
    //Hardcoded since you want to see what you expect is most simple and clear
    string expected = "C:\Output Folder\fields\that later\determine\a folder";

    //If this test should also use a mocked filesystem it might be that you want to use
    //some base directory, which you could set in the setUp of your test class
    //that is usefull if you you need to run the same test on different environments
    string expected = this.outputPath + "fields\that later\determine\a folder";


    //another readable way could be interesting if you have difficult variables needed to test
    string fields = "fields";
    string thatLater = "that later";
    string determine = "determine";
    string aFolder = "a folder";
    string expected = this.outputPath + fields + "\" + thatLater + "\" + determine + "\" + aFolder;
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    //in general testing with real words is not needed, so code could be shorter on that
    //for testing difficult folder names you write a separate test anyway
    string f1 = "f1";
    string f2 = "f2";
    string f3 = "f3";
    string f4 = "f4";
    string expected = this.outputPath + f1 + "\" + f2 + "\" + f3 + "\" + f4;
    MyClass target = new MyClass(f1, f2, f3, f4);

    //so here we start to see a structure, it looks more like an array of fields
    //so what would make testing more interesting with lots of variables is the use of a data provider
    //the data provider will re-use your test with many different kinds of inputs. That will reduce the amount of duplication of code for testing
    //http://msdn.microsoft.com/en-us/library/ms182527.aspx


    The part where you compare already seems correct
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

Riassumendo: In generale, il tuo primo test appena codificato ha più senso per me perché è semplice, dritto al punto ecc. Se inizi a codificare un percorso troppe volte basta metterlo nel metodo di configurazione.

Per ulteriori test strutturati futuri vorrei andare a verificare le origini dati in modo da poter aggiungere più righe di dati se hai bisogno di più situazioni di test.

    
risposta data 24.09.2013 - 09:38
fonte

Leggi altre domande sui tag