UnitTest e Code Coverage, parliamone

Premetto che capisco l'importanza di test, quando ancora il test strutturato era qualcosa di sconosciuto quasi ogni mia classe java aveva un metodo main per testare la classe stessa.

Il Testing strutturato è - da qualche anno - entrato a fare parte del mondo .NET, prima con strumenti di terze parti NUnit - giunto direttamente dal lato oscuro, vedi JUnit - poi e ora con strumenti di testing integrati direttamente in Visual Studio. Senza discuisire su quelle strumento sia migliore per il testing, credo che l'aver creato una "standardizzazione" del testing sia un grande passo avanti nell'ottica di creare codice qualitativamente buono e facilmente mantenibile. Tuttavia, nonostante ho infinito apprezzamento verso le varie disclipine di testing - che sicuramente richiede un certo rigore da parte di chi sviluppa - non si può negare che tale disciplina aumenta - anche notevolmente - i tempi di sviluppo.

Con l'introduzione del testing strutturato ecco la nascita di nuove fobie e nuove interessanti parole con cui ci si riempie la bocca (esempio Code Coverage). Il Code Coverage NON misura la qualità del codice ma la quantità di codice coperto dal testing... che NON è la stessa cosa. Ed in effetti nessuno ha mai detto il contrario nel definirlo: "Code coverage is a measure used in software testing. It describes the degree to which the source code of a program has been tested." (cit. Wiki: Code Coverage)

Seguite le seguenti prove.

Class 1

public class Class1
{
    public int Divide(int a, int b)
    {
        return a / b;
    }
}

 

[TestMethod()]
public void DivideTest()
{
    Class1 target = new Class1();
    int a = 1;
    int b = 1;
    int expected = 1;
    int actual;
    actual = target.Divide(a, b);
    Assert.AreEqual(expected, actual);
}

[TestMethod()]
public void Class1ConstructorTest()
{
    Class1 target = new Class1();
    Assert.IsNotNull(target);
}

Ho Code Coverage 100%, ma se venisse richiesta una divisione per 0 sarebbe sollevata l'eccezione "DivideByZeroException".

Class 2

public class Class2
{
    private Object[] items = new Object[] { "Class2_Item0" };

    public Object Get(int index)
    {
        return items[index];
    }
}

 

[TestMethod()]
public void Class2ConstructorTest()
{
    Class2 target = new Class2();
    Assert.IsNotNull(target);
}

[TestMethod()]
public void GetTest()
{
    Class2 target = new Class2();
    int index = 0;
    object expected = "Class2_Item0";
    object actual;
    actual = target.Get(index);
    Assert.AreEqual(expected, actual);           
}

Ho Code Coverage 100%, ma se venisse richiesto un indice non presente sarebbe sollevata l'eccezione "ArgumentOutOfRangeException".

Class 3

public class Class3
{
    private Object[] items = new Object[] { "Class3_Item0" };

    public object Get(int index)
    {
        if (index >= items.Length)
        {
            return null;
        }
        else
        {
            return items[index];
        }
    }

 

[TestMethod()]
public void Class3ConstructorTest()
{
    Class3 target = new Class3();
    Assert.IsNotNull(target);
}

[TestMethod()]
public void GetTest()
{
    Class3 target = new Class3();
    int index = 0;
    object expected = "Class3_Item0";
    object actual;
    actual = target.Get(index);
    Assert.AreEqual(expected, actual);
}

Il codice di Class3 è sicuramente da ritenere "migliore" ma avrò un Code Coverage 83,33% in quanto effettivamente il test copre solo l'istruzione nello scope dell'else. beh non è certo difficile avere il 100% ma non è questo il mio scopo.

image

Da queste semplici prove ne ricavo che il code coverage è importante per chi sviluppa in quanto ha la sicurezza che il codice è coperto dai test... ma non è garanzia per nessuno che il codice sia qualitativamente buono e privo di bug. Sarebbe opportuno che - come in java - i metodi dichiarassero le eccezioni sollevate? Sarebbe oppotuno che il calcolo del code coverage sapesse analizzare in autonomia il codice e le eccezioni sollevabilili? Sinceramente non ho un'idea chiara - al momento - di quello che sarebbe opportuno. Ma per quanto detto  leggere un buon code coverage - sto parlando quello calcolato da VS - mi lascia il tempo che trova.

Ora che ho dato una dimensione al code coverage, voglio continuare con le considerazioni... seguite questo esempio.

Class 4

public class Class4
{
  public String GetValue()
  {
     string value = Facade.Get();
     return value;
   }

  class Facade
  {
     public static IProvider provider;

     private static IProvider Provider
     {
       get
       {
          if (provider == null)
          {

//Qui si potrebbe leggere provider da configurazione o usare qualche application block per inversion of control
             Type type = typeof(ProviderImpl);
            provider = (IProvider) Activator.CreateInstance(type);
          }
          return provider;
        }
      }

       public static String Get()
       {
          return Provider.Get();
        }
      } 

      interface IProvider
      {
          String Get();
      }

      class ProviderImpl : IProvider
      { 
           #region IProvider Members

           public String Get()
           {
                return "Esempio";
           }

           #endregion
      }
  }

 

[TestMethod()]
public void GetValueTest()
{
    Class4 target = new Class4();
    string expected = "Esempio";
    string actual;
    actual = target.GetValue();
    Assert.AreEqual(expected, actual);           
}

[TestMethod()]
public void Class4ConstructorTest()
{
    Class4 target = new Class4();
    Assert.IsNotNull(target);
}

Ho Code Coverage 100% senza necessità di testare ogni singola componente nello specifico... senza Fake o Mock. Come dicevo il Code Coverage lascia il tempo che trova anche se poi effettivamente il codice del mini-sistema è completamente coperto. Qual'è la vera esigenza di testare ogni singola componente/layer? Una attenta analisi di evenuali errori nell'esecuzione dei test non possono compensare i test capillari? Con "attenta analisi" intendo tenere in considerazione lo stack trace dell'errore in caso di runtime oppure Assert intelligenti per la valutazione di errori funzionali.  Cosa si possono prevenire con i test capillari delle singole componenti? Errori funzionali dei singoli layer che se isolati evitano perdite di tempo e sessioni di debug per identificare la causa del malfunzionamento. In genere tendo a preferire i test verticali di tutto l'impianto integrato fatta eccezione per componenti molto particolari per le quali giudico opportuno testare isolatamente.

Quindi che ne deduco? I test capilari solo se davvero ne vale  la pena... in gerere 1 ora di debug credo che sia meglio di 4 ore di implemetazione dei test.

Il mantenimento dopo un refactoring? Se il code coverage è alto ci sono buone possibilità di scovare le regressioni dopo un refactoring in componenti di layer comune a più parti nella stessa misura di un test capillare; se la componente rifattorizzata non dispone di test specifici per la stessa vorrà dire che probabilemente dovrò spendere un pò più di tempo per scovare la vera origine dell'errore... ma in fondo a volte la rifattorizzazione di componenti significa anche il cambio funzionale di alcuni suoi comportamenti, per cui quanto dovrei anche spendere per risistemare tutti i TestCase relativi?

oO0 (... io questa moda del test di ogni cosa ad ogni costo non so se la approvo ...)

posted @ giovedì 5 giugno 2008 00:59

Print

Comments on this entry:

# re: UnitTest e Code Coverage, parliamone

Left by M.rkino at 05/06/2008 04:13
Gravatar
"nessun numero sostituisce il buon senso un po è quello che dici anche tu se capisco bene", si esattamente... dire ho un code coverage del 100% lascia il tempo che trova se si valutano semplici situazioni come quelle che ho descritto... figuriamoci situazioni complesse. Per questo dico "la percentuale di code coverage lascia un pò il tempo che trova".

"solo che l'ora al debugger una volta spesa svasice x sempre mentre il test resta e verrà utilizzato moltissime volte" è vero è un'interessante notazioni... ma è anche vero è veloce spendere a posteriori il tempo per ricavare il test dopo aver scovato il bug con un sessione di debug, il test poi rimarrà ovviamente.

"i test aiutano molto a disegnare bene la applicazione" concordo, se poi si parla di TDD il test assume tutta un'altra dimensione.

Fondamentalmente il mio post vuole essere meno polemico di qllo che sembra si può riassumere in:
a) non credo nel dover testare - creare unit test voglio dire -necessariamente tutto, subito e a tutti i costi
b) il code coverage ha una sua dimensione a volte sopravalutata.

grazie per aver letto e grazie per il feedback.

# re: UnitTest e Code Coverage, parliamone

Left by Mario Duzioni at 05/06/2008 11:59
Gravatar
Ciao Markino, concordo forse su tutto ed aggiungo una piccola nota.

Personalmente continuo a considerare i test come una risorsa "privata" dello sviluppatore, che decide di scriverli quando si rende conto che serve scriverli. Sono invece totalmente contrario al considerare i test come requisito non funzionale di una applicazione, magari (ancora peggio) su vincolo espresso dal cliente e, peggio ancora, come "metro" di qualità del software! :-S

Il cliente, IMHO, dovrebbe solo esigere un prodotto con pochi bugs già nelle fasi alpha e beta e praticamente quasi nessun bug in produzione, invece continuo a notare che spesso il bug (in informatica!) è considerato come un elemento quasi naturale della produzione software.

Sta al produttore poi avere la volontà, che molto spesso manca, di produrre software stabile e di qualità, utilizzando gli strumenti che reputa più validi.

Buona giornata!

# re: UnitTest e Code Coverage, parliamone

Left by M.rkino at 05/06/2008 13:16
Gravatar
LOL.. il test del costruttore è dovuto alla classe di test di default che VS crea che in effetti ha poco senso se il costruttore non è esplicitamente definito o è privo di codice... ma si sa mai che le classi da cui si deriva possono avere problemi ;-p

Scherzi a parte. Quello che voglio dire è che la copertura 100% (o prossima a tale valore) non garantisce IMHO la bontà del codice ma garantisce sicurezza che il codice è tutto testato. Quello che voglio mostare con "Class 4" è che per avere 100% di copertura non necessario testare ogni singola componente implementato Fake e Mock. Non esiste il test specifico - nel mio caso -per ProviderImp o per Facade in quanto il test di Class4 include tutto (ovviamente ho usato delle classe nested per creare velocemente un esempio). Testo il caso d'uso "GetValue()" e non le singole microfunzionalità dell'implementazione del caso d'uso (quelle che chiamo le "fobie").

"Il caso della Class4 non è un esempio significativo perchè se tu fossi partito dai test è improbabile che saresti arrivato a quella soluzione." è vero... ma io no sto parlando di TDD.

# re: UnitTest e Code Coverage, parliamone

Left by M.rkino at 05/06/2008 14:40
Gravatar
Se mi vuoi dire in un test di un sistema integrato è più difficile collocare la provenienza degli errori sono d'accordo... ma IMHO se i casi d'uso del sistema che testo sono totalmente coperti (ovviamente sto parlando del "code coverage 500%") le regressioni sono assolutamente tutte individuabili.

# re: UnitTest e Code Coverage, parliamone

Left by M.rkino at 05/06/2008 14:47
Gravatar
mumble scusa Antonio, non credo di aver capito. :O

# re: UnitTest e Code Coverage, parliamone

Left by Antonio Ganci at 05/06/2008 14:57
Gravatar
Si in effetti ci siamo un pò dilungati. :-)
Supponi di avere due classi una che faccia la divisione ed un'altra che viene usata da quella che fa la divisione.
Quello che tu mi dici è che basta testare la divisione e per te anche l'altra è testata.
Quello che sostengo io è che è rischioso perchè il test della seconda classe è un effetto collaterale del test della prima.
Se un domani, ad esempio, cambi l'implementazione della divisione che non usa più la seconda classe ti trovi con del codice non testato.
Ora questo non vederlo come un dogma, dipende un pò anche dai casi ma come linea generale.
Spero di essere stato più chiaro.

# re: UnitTest e Code Coverage, parliamone

Left by M.rkino at 05/06/2008 16:49
Gravatar
yep... comprendo, come ci siamo detti il contesto in cui siamo deve sempre determinare le nostre scelte e per aclune classi è opportuno dedicare dei propri test ;-p

# Architettura e mondo reale

Left by ExternalBlogs at 20/10/2008 15:44
Gravatar
Ho appena letto un bellissimo post di Roberto Messora , nel quale Roberto evidenzia come cercare la perfezione
Comments have been closed on this topic.
«ottobre»
domlunmarmergiovensab
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789