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à.