Sì, SOLID è un ottimo modo per progettare codice che può essere facilmente testato. Come primer breve:
S - Principio di responsabilità singola: un oggetto dovrebbe fare esattamente una cosa e dovrebbe essere l'unico oggetto nella base di codice che fa quell'unica cosa. Ad esempio, prendi una classe di dominio, ad esempio una fattura. La classe Fattura dovrebbe rappresentare la struttura dei dati e le regole commerciali di una fattura come utilizzata nel sistema. Dovrebbe essere l'unica classe che rappresenta una fattura nella base di codice. Questo può essere ulteriormente suddiviso per dire che un metodo dovrebbe avere uno scopo e dovrebbe essere l'unico metodo nella base di codice che soddisfa questa esigenza.
Seguendo questo principio, aumenti la testabilità del tuo progetto diminuendo il numero di test che devi scrivere che testano la stessa funzionalità su oggetti diversi e di solito finisci con pezzi di funzionalità più piccoli che sono più facili da testare isolatamente.
O - Principio aperto / chiuso: una classe dovrebbe essere aperta all'estensione, ma chiusa al cambiamento . Una volta che un oggetto esiste e funziona correttamente, idealmente non dovrebbe essere necessario tornare a quell'oggetto per apportare modifiche che aggiungono nuove funzionalità. Invece, l'oggetto dovrebbe essere esteso, derivandolo o inserendo in esso implementazioni di dipendenza nuove o diverse, per fornire quella nuova funzionalità. Questo evita la regressione; è possibile introdurre la nuova funzionalità quando e dove è necessario, senza modificare il comportamento dell'oggetto in quanto è già utilizzato altrove.
Aderendo a questo principio, generalmente aumenti la capacità del codice di tollerare "beffe" e eviti anche di dover riscrivere i test per anticipare nuovi comportamenti; tutti i test esistenti per un oggetto dovrebbero comunque funzionare sull'implementazione non estesa, mentre dovrebbero funzionare anche i nuovi test per nuove funzionalità che utilizzano l'implementazione estesa.
L - Principio di sostituzione di Liskov: una classe A, dipendente dalla classe B, dovrebbe essere in grado di usare qualsiasi X: B senza conoscere la differenza. Ciò significa sostanzialmente che tutto ciò che usi come dipendenza dovrebbe avere un comportamento simile a quello visto dalla classe dipendente. Ad esempio, supponiamo di avere un'interfaccia IWriter che espone Write (string), che è implementato da ConsoleWriter. Ora devi invece scrivere su un file, quindi crei FileWriter. Nel fare ciò, è necessario assicurarsi che FileWriter possa essere utilizzato allo stesso modo di ConsoleWriter (il che significa che l'unico modo in cui il dipendente può interagire con esso è chiamare Scrivi (stringa)) e quindi ulteriori informazioni che FileWriter potrebbe aver bisogno di fare il lavoro (come il percorso e il file in cui scrivere) deve essere fornito da un'altra parte rispetto al dipendente.
Questo è enorme per la scrittura di codice testabile, perché un progetto conforme all'LSP può avere un oggetto "deriso" sostituito alla cosa reale in qualsiasi momento senza cambiare il comportamento previsto, consentendo di testare piccoli pezzi di codice in isolamento con la sicurezza che il sistema funzionerà quindi con gli oggetti reali collegati.
I - Principio di segregazione dell'interfaccia: un'interfaccia dovrebbe avere il minor numero di metodi possibile per fornire la funzionalità del ruolo definito dall'interfaccia . In poche parole, interfacce più piccole sono meglio di meno interfacce più grandi. Questo perché un'interfaccia di grandi dimensioni ha più motivi per cambiare e provoca altre modifiche altrove nella base di codice che potrebbero non essere necessarie.
L'adesione all'ISP migliora la testabilità riducendo la complessità dei sistemi in prova e delle dipendenze di tali SUT. Se l'oggetto che si sta testando dipende da un'interfaccia IDoThreeThings che espone DoOne (), DoTwo () e DoThree (), è necessario deridere un oggetto che implementa tutti e tre i metodi anche se l'oggetto utilizza solo il metodo DoTwo. Ma, se l'oggetto dipende solo da IDoTwo (che espone solo DoTwo), puoi deridere più facilmente un oggetto che ha quel metodo.
D - Principio di inversione di dipendenza: le concrezioni e le astrazioni non dovrebbero mai dipendere da altre concrezioni, ma dalle astrazioni . Questo principio applica direttamente il principio dell'accoppiamento libero. Un oggetto non dovrebbe mai sapere cosa sia un oggetto; dovrebbe invece preoccuparsi di ciò che fa un oggetto. Pertanto, l'uso di interfacce e / o classi di base astratte è sempre da preferire all'uso di implementazioni concrete quando si definiscono proprietà e parametri di un oggetto o metodo. Ciò consente di scambiare un'implementazione con un'altra senza dover cambiare l'utilizzo (se si segue anche LSP, che va di pari passo con DIP).
Ancora una volta, questo è enorme per la testabilità, in quanto ti consente, ancora una volta, di iniettare un'implementazione fittizia di una dipendenza anziché un'implementazione di "produzione" nel tuo oggetto in fase di test, pur testando l'oggetto nella forma esatta che avrà mentre in produzione. Questa è la chiave per test unitari "in isolamento".