Panoramica sul principio di minima conoscenza
Il principio di minima conoscenza, noto anche come Legge di Demetra, afferma in generale che ogni componente o unità di un programma dovrebbe interagire solo con un ristretto numero di altri componenti che conosce direttamente.
In particolare, nel caso della programmazione ad oggetti, ogni metodo dovrebbe unicamente invocare metodi:
- Dell’oggetto di cui fa parte;
- Dei propri parametri;
- Degli attributi dell’oggetto di cui fa parte;
- Degli oggetti che istanzia;
- Di eventuali variabili globali.
Ne consegue che non dovrebbe invocare metodi:
- Degli oggetti ritornati dai metodi invocati;
- Degli attributi di oggetti che non quello di cui fa parte;
- ecc..
Seguendo il principio di minima conoscenza si riduce l’accoppiamento tra componenti, il che migliora la manutenibilità e l’adattabilità del codice. Lo svantaggio è la necessità di dover fare ampio uso di façade e metodi wrapper per accedere alle funzionalità necessarie.
Si noti che comunque questo principio viene violato abbastanza di frequente. Si pensi ad esempio al pattern fluent interface, in cui si concatenano numerose chiamate a metodi di classi diverse (e.g. Linq in C#). Tant’è che alcuni considerano la fluent interface un anti-pattern.
Un altro esempio è il caso in cui si crea un oggetto tramite una factory, per poi chiamare dei metodi sull’oggetto istanziato. In questo caso si può tuttavia, nella maggior parte dei casi, usare a monte la factory e iniettare poi direttamente l’oggetto nel metodo.
In generale, si tratta di trovare un compromesso tra minima conoscenza e mole di codice aggiuntivo.
Principio di minima conoscenza e test automatici
Un vantaggio importante dell’applicazione del principio di minima conoscenza è la riduzione del numero di dipendenze di cui effettuare il mocking nei test non end-to-end, e in particolare negli unit test.
Ad esempio, supponiamo di usare la seguente fluent interface per comandare dei macchinari di una pizzeria. La catena di chiamate parte da un oggetto di tipo Farina
e termina ritornandone uno di tipo Pizza
, passando per oggetti di tipo Impasto
, ImpastoLievitato
, BasePizza
, ecc.:
farina.Impasta(acqua, uova, lievito).Lievita().Stendi().Cuoci().Condisci(ingredienti)
Vogliamo creare degli unit test per il metodo che prende in input la farina e, tra le altre cose, contiene la catena di chiamate. Vogliamo quindi evitare che i vari metodi inviino comandi ai macchinari della pizzeria.
Dobbiamo quindi fare il mocking della comunicazione verso i macchinari. Supponiamo anche che le classi e i metodi sopraelencati facciano parte di una libreria fornita dal produttore dei macchinari, il quale non si è premurato di fornire un modo per iniettare il protocollo di comunicazione con le macchine dall’esterno (i.e. non possiamo fare new Farina(new MachinesCommunicationMock())
). L’unica opzione che ci resta è fare il mock dei metodi delle varie classi.
Bisogna dunque creare un mock dell’oggetto Farina
, in cui il metodo Impasta ritorna un mock dell’oggetto Impasto
, in cui il metodo Lievita ritorna un mock dell’oggetto ImpastoLievitato
, ecc.. Infine si passa il mock di Farina
al metodo da testare.
Consideriamo invece ora il caso in cui la catena di chiamate è sostituita dal seguente codice:
pizzeria.PreparaPizza(acqua, lievito, ...)
L’oggetto pizzeria viene passato in input al metodo chiamante al posto della farina. La catena di chiamate dell’esempio precedente viene spostata all’interno di PreparaPizza
.
In questo caso è sufficiente fare il mocking della classe Pizzeria
, facendo l’override di PreparaPizza
in modo che salti completamente l’esecuzione della catena di chiamate.
In questo caso, non abbiamo eliminato del tutto il problema, perché lo abbiamo spostato in PreparaPizza
, ma almeno l’abbiamo isolato in un metodo che fa solo quello. Ciò è particolarmente vantaggioso se la sequenza di operazioni per preparare la pizza va eseguita in più classi e metodi, o se i metodi che chiamano PreparaPizza
contengono anche altri dipendenze di cui fare il mockup.
Conclusioni
Attenersi al principio di minima conoscenza semplifica la scrittura di test automatici perché riduce il numero di dipendenze del SUT (System Under Test). Bisogna tuttavia bilanciare i vantaggi con la maggior quantità di codice necessaria per implementare il principio.
Per approfondimenti o consulenze in ambito testing e quality assurance, scrivere a info@dvisentin.com.