Test di unità fragili a causa della necessità di deridere eccessivamente

21

Ho avuto a che fare con un problema sempre più fastidioso per quanto riguarda i nostri test unitari che stiamo implementando nel mio team. Stiamo tentando di aggiungere test unitari in un codice legacy che non è stato ben progettato e, mentre non abbiamo avuto alcuna difficoltà con l'aggiunta effettiva dei test, stiamo iniziando a lottare con il modo in cui i test si stanno rivelando.

Come esempio del problema, supponiamo tu abbia un metodo che chiama altri 5 metodi come parte della sua esecuzione. Un test per questo metodo potrebbe essere quello di confermare che un comportamento si verifica in seguito alla chiamata di uno di questi 5 altri metodi. Quindi, poiché un test unitario dovrebbe fallire per una ragione e una sola ragione, si vogliono eliminare potenziali problemi causati dal chiamare questi altri 4 metodi e deriderli. Grande! Il test unitario viene eseguito, i metodi simulati vengono ignorati (e il loro comportamento può essere confermato come parte di altri test unitari) e la verifica funziona.

Ma c'è un nuovo problema: il test unitario ha una conoscenza approfondita di come hai confermato quel comportamento e qualsiasi modifica alla firma di uno qualsiasi di questi altri 4 metodi in futuro, o di qualsiasi nuovo metodo che necessiti di essere aggiunto al "metodo genitore", comporterà la necessità di cambiare il test dell'unità per evitare possibili guasti.

Ovviamente il problema potrebbe essere mitigato piuttosto semplicemente avendo più metodi a realizzare meno comportamenti, ma speravo che esistesse forse una soluzione più elegante disponibile.

Ecco un esempio unit test che cattura il problema.

Come nota rapida 'MergeTests' è una classe di test unitario che eredita dalla classe che stiamo testando e sovrascrive il comportamento secondo necessità. Questo è un "modello" che utilizziamo nei nostri test per permetterci di ignorare le chiamate a classi / dipendenze esterne.

[TestMethod]
public void VerifyMergeStopsSpinner()
{
    var mockViewModel = new Mock<MergeTests> { CallBase = true };
    var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());

    mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
    mockViewModel.Setup(
        m =>
        m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
                         It.IsAny<bool>()));
    mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
    mockViewModel.Setup(m => m.SwitchToOverviewTab());
    mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
    mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
    mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));

    mockViewModel.Object.OnMerge(It.IsAny<MergeState>());    

    mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}

In che modo il resto di voi ha affrontato questo problema o non c'è un modo "semplice" per gestirlo?

Aggiornamento: apprezzo il feedback di tutti. Sfortunatamente, e non c'è da meravigliarsi davvero, non sembra esserci una grande soluzione, modello o pratica che si possa seguire nei test unitari se il codice da testare è scadente. Ho segnato la risposta che meglio ha catturato questa semplice verità.

    
posta PremiumTier 08.03.2013 - 19:41
fonte

3 risposte

18
  1. Correggi il codice per essere progettato meglio. Se i tuoi test hanno questi problemi, il tuo codice avrà problemi peggiori quando proverai a cambiare le cose.

  2. Se non puoi, allora forse hai bisogno di essere meno ideale. Test contro le pre e post-condizioni del metodo. A chi importa se usi gli altri 5 metodi? Presumibilmente hanno i loro test unitari che rendono chiaro (er) che cosa ha causato l'errore quando i test falliscono.

"i test unitari dovrebbero avere solo una ragione per fallire" è una buona guida, ma nella mia esperienza, poco pratica. I test difficili da scrivere non vengono scritti. I test fragili non vengono creduti.

    
risposta data 08.03.2013 - 20:29
fonte
8

Rompere i grandi metodi in piccoli metodi più focalizzati è sicuramente una buona pratica. Lo vedi come un dolore nel verificare il comportamento dei test di unità, ma stai sperimentando il dolore anche in altri modi.

Detto questo, è un'eresia ma io personalmente sono un fan della creazione di ambienti di test temporanei realistici. Cioè, piuttosto che prendere in giro tutto ciò che è nascosto all'interno di questi altri metodi, assicurati che ci sia un ambiente temporaneo facile da configurare (completo di database e schemi privati - SQLite può aiutare qui) che ti permette di eseguire tutte queste cose. La responsabilità di sapere come costruire / abbattere quell'ambiente di test è quella che richiede il codice, in modo che quando cambia non sia necessario modificare tutto il codice di test dell'unità che dipende dalla sua esistenza.

Ma noto che questa è un'eresia da parte mia. Le persone che sono pesantemente in fase di test unitario sostengono test unitari "puri" e chiamano ciò che ho descritto "test di integrazione". Personalmente non mi preoccupo di questa distinzione.

    
risposta data 08.03.2013 - 21:01
fonte
3

Prenderò in considerazione l'andamento dei mock e formulerò solo test che potrebbero includere i metodi a cui è chiamato.

Non testare come , testare cosa . È il risultato che conta, includi i sotto-metodi se necessario.

Da un'altra angolazione potresti formulare un test, farlo passare con un grande metodo, refactoring e finire con un albero di metodi dopo il refactoring. Non è necessario testarli singolarmente. È il risultato finale che conta.

Se i metodi secondari rendono difficile testare alcuni aspetti, prendi in considerazione la possibilità di suddividerli in classi separate, in modo da poterli prendere in giro in modo più pulito senza che la tua classe sottoposta a test sia pesantemente strumentata / cucita. È piuttosto difficile dire se stai effettivamente testando qualsiasi implementazione concreta nel tuo esempio di test.

    
risposta data 09.03.2013 - 00:15
fonte

Leggi altre domande sui tag