In definitiva i test sono uno strumento. Come tutti gli strumenti, c'è un costo e un possibile beneficio. In questo caso, il test ha un costo elevato (il test sta dettando i dettagli dell'implementazione e si interromperà se l'implementazione cambia, impedendo il refactoring) e un basso beneficio (il codice che stai testando è così semplice che è banalmente verificabile), quindi non posso giustificare questo test.
Non scrivo test per metodi banali, perché posso essere sicuro che funzionano solo guardandoli. E anche se non funzionano, probabilmente vengono comunque utilizzati in un contesto più ampio e, se stai testando i comportamenti piuttosto che i metodi, gli altri test dovrebbero individuare il problema.
Mantra come "è necessario raggiungere una copertura del 100% del codice" o "è necessario scrivere un test per ogni metodo" sono mal concepiti e si oppongono agli ingegneri produttivi che stanno cercando di fornire un codice di qualità che promuova gli obiettivi dell'organizzazione.
To do so, I had to create a fake DbContext and DbSet. I'm basically just asserting that Add() was called on the DbSet, and that Save() was called on the DbContext.
My concern is that it's too tightly coupling the test to the code. The test is basically dictating how the code does its job, instead of what it does.
Questo è esattamente corretto. Un test del genere non sta asserendo un comportamento, ma piuttosto semplicemente ridimensionando il codice implementato al suo interno. Un test come questo asserisce che il codice non è cambiato, non che il comportamento non è cambiato.
Ad esempio, immagina se db.Foo
avesse un altro metodo, chiamato AddAndSave(...)
che gestiva entrambe le operazioni per te. Se hai sostituito la tua implementazione con una che chiama semplicemente questo metodo AddAndSave
, il test fallirebbe, eppure il comportamento è lo stesso. Ancora peggio, immagina se db
ha un metodo UndoLastSave()
che ha annullato la precedente chiamata di salvataggio. Potresti aggiungere questo alla fine del tuo metodo e il test passerebbe comunque anche se il tuo metodo non ha più il comportamento desiderato.
Di solito, quando vedo persone che scrivono questo tipo di test, è un sintomo di test con la granularità errata. Si afferma comunemente che i test unitari devono testare solo un metodo, ma che è un interpretazione distorta dei test unitari. Il tuo sistema non deve essere isolato con un singolo metodo o classe, perché non c'è nulla che ti costringa a trattare quelli come la tua 'unità'. Vorrei invece provare a scrivere un test più lungo le linee di questo (scusate il mio pseudo-codice):
//given
system.Clear()
Foo foo = FooFactory.SomeFoo()
//when
system.Write(foo)
//then
assertTrue(system.Contains(foo))
L'idea qui è che vogliamo testare il comportamento della scrittura. Come è cambiato lo stato del mondo dopo che è stata chiamata la scrittura? Questo test chiama più di un metodo sul sistema e questo è fine .