Test delle unità: un test è in parte un "superset" di un altro, è sbagliato?

6

Non sapevo come pronunciare quel titolo, scusa!

Ho appena trovato questo nella nostra base di codice, e mi chiedevo quale fosse il consenso su come testarlo unitamente:

C # (-ish, scusa, questo è parafrasato dalla memoria)

class DateCalculator
{
   public static DateTime CalculateLeadTime(DateTime deliveryDate)
   {
      //return the result of some determinate calculation involving deliveryDate
   }
}

//New example
class OrderController
{
    public ActionResult GetLeadTime(WidgetOrder order)
    {
        var leadTime = DateCalculator.CalculateLeadTime(order.GetDeliveryDate() );
        return Json(leadTime.Value.Date.ToString("dd/MM/yyyy") );
    }
}

//Old, incorrect example
class OrderService
{
   public DateTime GetLeadTime(WidgetOrder order)
   {
      return DateCalculator.CalculateLeadTime(order.GetDeliveryDate() );
   }
}

Abbiamo quindi ottenuto due test unitari, uno per verificare che CalculateLeadTime funzioni (questo è molto semplice, puoi inserire diversi valori di test e prevedere ciò che viene fuori) e uno che GetLeadtime funziona (ancora facile, ma .. .). La mia domanda è: cosa testeresti qui e dove?

Per me è logico testare CalculateLeadTime in isolamento, perché potrebbe essere un calcolo complicato, ed è già una piccola unità prevedibile. Ma poi andresti a testare alcune delle stesse cose (magari un singolo input e output rappresentativo?) Nel tuo metodo GetLeadTime? O testare semplicemente che viene restituito il valore any DateTime e si presuppone che il calcolo sia corretto perché lo stai testando da qualche altra parte? Oppure non testerai il metodo Calcola da solo, perché sai che lo testerai quando lo utilizzerai e non ti piace la duplicazione?

EDIT: per confondere un po 'le acque, ora posso vedere il codice, e piuttosto che un servizio, la classe chiamante è un controller MVC e l'azione sotto esame è un metodo AJAX, quindi restituisce il risultato del calcolo completato in JSON. Il test corrente verifica che JSON venga restituito, ma non il suo contenuto. Questo cambia qualcosa?

    
posta frumious 15.06.2014 - 15:45
fonte

2 risposte

9

Stai facendo un grosso errore quando metti delle ipotesi sui tuoi test unitari su come l'unità è implementata.

Considera che cosa accadrebbe se l'implementazione di OrderService dovesse cambiare in modo che non usasse più DateCalculator (o, più in generale, se si volesse testare un'implementazione diversa con gli stessi test di unità). Il tuo unit test per OrderService non sarebbe più in grado di catturare la maggior parte dei potenziali errori.

Sebbene sia vero che è possibile utilizzare le informazioni white-box per adattare i test unitari al codice, i test unitari non dovrebbero essere progettati per formulare ipotesi su come funziona il codice. Quindi, sì: scriverò assolutamente dei test che potrebbero finire per eseguire lo stesso codice, perché il codice non è scritto in pietra e non voglio che i miei test siano concretamente accoppiati al codice che si suppone stia verificando.

    
risposta data 15.06.2014 - 19:12
fonte
3

Idealmente dovresti cambiare un po 'il tuo design e disaccoppiare OrderService da DateCalculator.CalculateLeadTime usando l'iniezione di dipendenza:

class OrderService
{
   Func<DateTime,WidgetOrder> _calculateLeadTime;

   public OrderService(Func<DateTime,WidgetOrder> calculateLeadTime)
   {
       _calculateLeadTime=calculateLeadTime;
   }

   public DateTime GetLeadTime(WidgetOrder order)
   {
      return _calculateLeadTime(order.GetDeliveryDate() );
   }
}

Ora è facile test unitario OrderService in isolamento: invece di DateCalculator.CalculateLeadTime , passa una funzione fittizia attraverso il costruttore per il tuo test e controlla se è chiamato correttamente da GetLeadTime . Questo cambia la situazione in modo che i test significativi per OrderService non siano più un superset dei test per DateCalculator .

Naturalmente, puoi aggiungere un test di integrazione alla tua suite di test, che verifica se OrderService.GetLeadTime restituisce il valore previsto quando è costruito con la funzione DateCalculator.CalculateLeadTime desiderata. Ma in genere per quei test di integrazione è necessaria solo una gamma più piccola di casi di test come per i test di unità. Poiché hai test per il comportamento individuale di entrambe le unità, hai già fiducia nelle loro funzionalità principali e devi solo assicurarti che funzionino entrambe insieme come previsto.

Nell'esempio semplicistico attuale, è discutibile se la situazione ha davvero bisogno di tutti e tre i tipi di test, ma in generale (supponendo alcune funzioni più complesse nello scenario del mondo reale), questo di solito paga.

    
risposta data 15.06.2014 - 17:21
fonte

Leggi altre domande sui tag