2012-01-23 12 views
6

Sto refactoring del codice che implementa una formula e voglio farlo test-first, per migliorare le mie capacità di test e lasciare il codice coperto.Come testare la formula multiparametro

Questo particolare codice è una formula che accetta 3 parametri e restituisce un valore. Ho anche alcune tabelle di dati con risultati attesi per diversi input, quindi in teoria potrei scrivere un test di zillion, semplicemente cambiando i parametri di input e verificando i risultati rispetto al valore atteso corrispondente.

Ma ho pensato che ci dovrebbe essere un modo migliore per farlo, e guardando i documenti ho trovato test di parametri parametrizzati.

Quindi, con questo ora so come creare automaticamente i test per i diversi input.
Ma come ottengo il risultato atteso corrispondente per confrontarlo con quello calcolato?

L'unica cosa che sono riuscito a ottenere è una tabella di ricerca statica e un membro statico nel dispositivo di testo che è un indice della tabella di ricerca e viene incrementato in ogni esecuzione. Qualcosa di simile a questo:

#include "gtest/gtest.h" 

double MyFormula(double A, double B, double C) 
{ 
    return A*B - C*C; // Example. The real one is much more complex 
} 

class MyTest:public ::testing::TestWithParam<std::tr1::tuple<double, double, double>> 
{ 
protected: 

    MyTest(){ Index++; } 
    virtual void SetUp() 
    { 
     m_C = std::tr1::get<0>(GetParam()); 
     m_A = std::tr1::get<1>(GetParam()); 
     m_B = std::tr1::get<2>(GetParam()); 
    } 

    double m_A; 
    double m_B; 
    double m_C; 

    static double ExpectedRes[]; 
    static int Index; 

}; 

int MyTest::Index = -1; 

double MyTest::ExpectedRes[] = 
{ 
//    C = 1 
//  B: 1  2  3  4  5  6  7  8  9 10 
/*A = 1*/ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 
/*A = 2*/ 1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 
/*A = 3*/ 2.0, 5.0, 8.0, 11.0, 14.0, 17.0, 20.0, 23.0, 26.0, 29.0, 

//    C = 2 
//  B:  1  2  3  4  5  6  7  8  9 10 
/*A = 1*/ -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 
/*A = 2*/ -2.0, 0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 
/*A = 3*/ -1.0, 2.0, 5.0, 8.0, 11.0, 14.0, 17.0, 20.0, 23.0, 26.0, 
}; 

TEST_P(MyTest, TestFormula) 
{ 
    double res = MyFormula(m_A, m_B, m_C); 
    ASSERT_EQ(ExpectedRes[Index], res); 
} 

INSTANTIATE_TEST_CASE_P(TestWithParameters, 
         MyTest, 
         testing::Combine(testing::Range(1.0, 3.0), // C 
              testing::Range(1.0, 4.0), // A 
              testing::Range(1.0, 11.0) // B 
             )); 

È questo un buon approccio o c'è un modo migliore per ottenere il giusto risultato per ogni run previsto?

risposta

0

Non ho molta esperienza con i test di unità, ma come matematico, penso che non ci sia molto più che si possa fare.

Se si conoscessero alcuni invarianti della formula, è possibile testarli, ma penso che abbia senso solo in pochissimi scenari.

Ad esempio, se si desidera testare, se è stata implementata correttamente la funzione esponenziale naturale, è possibile fare uso della conoscenza, che la derivata deve avere lo stesso valore della funzione stessa. È quindi possibile calcolare un'approssimazione numerica alla derivata per un milione di punti e vedere se sono vicini al valore effettivo della funzione.

1

Vedere hard coding il risultato atteso è come se si stesse limitando di nuovo il no dei casi di test. Se si desidera ottenere un modello completo basato sui dati, suggerirei piuttosto di leggere gli input, i risultati attesi da un file flat file/xml/xls.

+0

Grazie. In questo caso, la specifica che devo seguire fornisce la formula più alcune tabelle con i risultati previsti per alcuni valori. Posso essere abbastanza sicuro che se i miei risultati corrispondono a quei tavoli, l'ho fatto bene.In effetti, ho già trovato alcuni valori errati nelle specifiche, grazie ai test. – MikMik

+0

È grandioso. Se si aggiungono i dati del test all'interno della classe di test, il problema sarà, in futuro, se si desidera testare un altro scenario, quindi è necessario aggiornare nuovamente la classe. Ma se si accede ai dati del test da un file esterno, la manutenzione del caso di test verrà ridotta. Perché si aggiornerà quel file flat per aggiungere nuovi dati di test. questo è tutto. Non c'è bisogno di costruire la tua classe e poi di nuovo distribuirla. –

+0

@PritamKarmakar, vuoi dire che la manutenibilità del test case sarà aumentata, giusto? L'impegno richiesto per mantenerlo sarà ridotto. – Alan

6

Includere il risultato previsto insieme agli input. Invece di una tripla di valori di input, rendi i tuoi parametri di prova una tupla di 4 elementi.

class MyTest: public ::testing::TestWithParam< 
    std::tr1::tuple<double, double, double, double>> 
{ }; 

TEST_P(MyTest, TestFormula) 
{ 
    double const C = std::tr1::get<0>(GetParam()); 
    double const A = std::tr1::get<1>(GetParam()); 
    double const B = std::tr1::get<2>(GetParam()); 
    double const result = std::tr1::get<3>(GetParam()); 

    ASSERT_EQ(result, MyFormula(A, B, C)); 
} 

L'aspetto negativo è che non sarà in grado di mantenere i parametri di prova conciso con testing::Combine. Invece, è possibile utilizzare testing::Values per definire ciascuna tupla da 4 distinta che si desidera testare. Potresti raggiungere il limite numero di argomenti per Values, quindi puoi dividere le tue istanziazioni, ad esempio mettendo tutti i casi C = 1 in uno e tutti i casi C = 2 in un altro.

INSTANTIATE_TEST_CASE_P(
    TestWithParametersC1, MyTest, testing::Values(
    //   C  A  B 
    make_tuple(1.0, 1.0, 1.0, 0.0), 
    make_tuple(1.0, 1.0, 2.0, 1.0), 
    make_tuple(1.0, 1.0, 3.0, 2.0), 
    // ... 
)); 

INSTANTIATE_TEST_CASE_P(
    TestWithParametersC2, MyTest, testing::Values(
    //   C  A  B 
    make_tuple(2.0, 1.0, 1.0, -3.0), 
    make_tuple(2.0, 1.0, 2.0, -2.0), 
    make_tuple(2.0, 1.0, 3.0, -1.0), 
    // ... 
)); 

Oppure si può mettere tutti i valori in un array separato dal tuo esemplificazione e quindi utilizzare testing::ValuesIn:

std::tr1::tuple<double, double, double, double> const FormulaTable[] = { 
    //   C  A  B 
    make_tuple(1.0, 1.0, 1.0, 0.0), 
    make_tuple(1.0, 1.0, 2.0, 1.0), 
    make_tuple(1.0, 1.0, 3.0, 2.0), 
    // ... 
    make_tuple(2.0, 1.0, 1.0, -3.0), 
    make_tuple(2.0, 1.0, 2.0, -2.0), 
    make_tuple(2.0, 1.0, 3.0, -1.0), 
    // ... 
}; 

INSTANTIATE_TEST_CASE_P(
    TestWithParameters, MyTest, ::testing::ValuesIn(FormulaTable)); 
Problemi correlati