Componenti in.net, Dependency Injection e Software testabile. Giovanni Lagorio lagorio@disi.unige.it



Documenti analoghi
Progettazione : Design Pattern Creazionali

Programmazione Orientata agli Oggetti in Linguaggio Java

Mac Application Manager 1.3 (SOLO PER TIGER)

Capitolo 3. L applicazione Java Diagrammi ER. 3.1 La finestra iniziale, il menu e la barra pulsanti

Corso di Informatica

Guida all uso di Java Diagrammi ER

Uso di JUnit. Fondamenti di informatica Oggetti e Java. JUnit. Luca Cabibbo. ottobre 2012

Unit Testing. Giovanni Lagorio

Funzioni in C. Violetta Lonati

Registratori di Cassa

Modulo 4: Ereditarietà, interfacce e clonazione

Inizializzazione, Assegnamento e Distruzione di Classi

Il web server Apache Lezione n. 3. Introduzione

Esercizi su. Funzioni

MANUALE PARCELLA FACILE PLUS INDICE

Software per Helpdesk

INSTALLAZIONE NUOVO CLIENT TUTTOTEL (04 Novembre 2014)

IL MIO PRIMO SITO: NEWS

12 - Introduzione alla Programmazione Orientata agli Oggetti (Object Oriented Programming OOP)

MonoDay 2010 FSGateway Ing. Torello Querci

Modulo 4 Il pannello amministrativo dell'hosting e il database per Wordpress

Creare un sito Multilingua con Joomla 1.6

FPf per Windows 3.1. Guida all uso

Oggetti Lezione 3. aspetti generali e definizione di classi I

COSTER. Import/Export su SWC701. SwcImportExport

Domande e Risposte ALLEGATI CLIENTI E FORNITORI. DATALOG Soluzioni Integrate

progecad NLM Guida all uso Rel. 10.2

Office Web Components in programmi C# da

Il calendario di Windows Vista

Architettura MVC-2: i JavaBeans

IL MODELLO CICLICO BATTLEPLAN

Programmazione a Oggetti Modulo B

Software di interfacciamento sistemi gestionali Manuale di installazione, configurazione ed utilizzo

Siti web centrati sui dati Architettura MVC-2: i JavaBeans

Guida Rapida all uso del License Manager di ROCKEY4Smart (V )

Il sofware è inoltre completato da una funzione di calendario che consente di impostare in modo semplice ed intuitivo i vari appuntamenti.

GPL 3 e Creative Commons Le licenze per la vostra libertà. Di Martino Martyn Colucci

Soluzione dell esercizio del 2 Febbraio 2004

risulta (x) = 1 se x < 0.

Algoritmi e strutture dati. Codici di Huffman

Invio SMS. DM Board ICS Invio SMS

Object Oriented Software Design

Programmazione a Oggetti Lezione 10. Ereditarieta

Elementi di Psicometria con Laboratorio di SPSS 1

DESIGN PATTERNS Parte 6. State Proxy

COMUNICAZIONE UTENTI SISTEMI-PROFIS INSTALLAZIONE GE.RI.CO e PARAMETRI2015

Manuale servizio Webmail. Introduzione alle Webmail...2 Webmail classica (SquirrelMail)...3 Webmail nuova (RoundCube)...8

Allocazione dinamica della memoria - riepilogo

Gestione Filtri. InfoBusiness 2.8 Gestione Filtri Pag. 1/ 11

Test di unità con JUnit4

GENERAZIONE PREVENTIVI

Manuale Utente Albo Pretorio GA

Università per Stranieri di Siena Livello A1

Guida Migrazione Posta Operazioni da effettuare entro il 15 gennaio 2012

Gli array. Gli array. Gli array. Classi di memorizzazione per array. Inizializzazione esplicita degli array. Array e puntatori

LA GESTIONE DELLE VISITE CLIENTI VIA WEB

PROGRAMMA GESTIONE TURNI MANUALE UTENTE. Programma Gestione Turni Manuale Utente versione 1.1

Se invece si usa come Client non serve l interfaccia e si possono usare anche le applicazioni per Smartphone.

Creare una nuova spedizione personalizzata.

Software standard Alpi Label Ver

Google Apps for Education F.A.Q. a cura di Luigi Parisi Servizio Marconi T.S.I. - USR Emilia-Romagna. aggiornata al 18 maggio 2015.

Progetto di simulazione molecolare per il corso di Complementi di algoritmi A.A

File, Modifica, Visualizza, Strumenti, Messaggio

L amministratore di dominio

IL BUDGET 04 LE SPESE DI REPARTO & GENERALI

Tale attività non è descritta in questa dispensa

I Thread. I Thread. I due processi dovrebbero lavorare sullo stesso testo

Il sistema monetario

L Open Source un mondo che forse dovresti conoscere? Viaggio alla scoperta dell open source e le sue caratteristiche.

13 - Gestione della Memoria nella Programmazione Orientata agli Oggetti

La VPN con il FRITZ!Box Parte II. La VPN con il FRITZ!Box Parte II

LE MEDIE MOBILI CENTRATE

Procedura di installazione di Xubuntu 8.10 su un PC

13. Campi vettoriali

MODELLO CLIENT/SERVER. Gianluca Daino Dipartimento di Ingegneria dell Informazione Università degli Studi di Siena

E' sempre valido il comando che si usa anche con Outlook Express e con Windows Mail: shift più control più V.

Guida alla configurazione della posta elettronica dell Ateneo di Ferrara sui più comuni programmi di posta

I TUTORI. I tutori vanno creati la prima volta seguendo esclusivamente le procedure sotto descritte.

VINCERE AL BLACKJACK

Note su quicksort per ASD (DRAFT)

Sistema operativo. Sommario. Sistema operativo...1 Browser...1. Convenzioni adottate

Visual basic base Lezione 01. L'ambiente di sviluppo

POSTA ELETTRONICA Per ricevere ed inviare posta occorrono:

Tipi primitivi. Ad esempio, il codice seguente dichiara una variabile di tipo intero, le assegna il valore 5 e stampa a schermo il suo contenuto:

FIRESHOP.NET. Gestione del taglia e colore.

Informatica per la comunicazione" - lezione 13 -

GUIDA UTENTE MONEY TRANSFER MANAGER

Manuale d'uso. Manuale d'uso Primo utilizzo Generale Gestione conti Indici di fatturazione Aliquote...

Amministrazione gruppi (Comunità)

Corso Eclipse. Prerequisiti. 1 Introduzione

Vlan Relazione di Sistemi e Reti Cenni teorici

Esercizio 1 Dato il gioco ({1, 2, 3}, v) con v funzione caratteristica tale che:

FASCI DI RETTE. scrivere la retta in forma esplicita: 2y = 3x + 4 y = 3 2 x 2. scrivere l equazione del fascio di rette:

Regione Toscana. ARPA Fonte Dati. Manuale Amministratore. L. Folchi (TAI) Redatto da

Joomla! 2.5:Utenti e permessi - Il wiki di Joomla.it

STAMPA UNIONE DI WORD

Excel. A cura di Luigi Labonia. luigi.lab@libero.it

Come si può vedere, la regola è stata fatta in modo da spostare tutti i messaggi di Spam nella cartella del cestino.

Un modello matematico di investimento ottimale

Transcript:

Componenti in.net, Dependency Injection e Software testabile Giovanni Lagorio lagorio@disi.unige.it

Licenza Questi lucidi sono rilasciati sotto la licenza Creative Commons Attribuzione-Non commerciale-non opere derivate 3.0 Unported. Per leggere una copia della licenza visita il sito web http://creativecommons.org/licenses/by-nc-nd/3.0/ o spedisci una lettera a Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. In due parole, possono essere liberamente usati, copiati e distribuiti purché: 1. Venga citata la fonte originale 2. Non vengano usati in ambito commerciale 3. Non vengano modificati in nessun modo

Partiamo da un esempio: EmailSender public class EmailSender { } public bool SendEmail(string to, string body) { if (to == null) throw new ArgumentNullException("to"); if (body == null) throw new ArgumentNullException("body"); //... return false; } Questa classe e le sue classi/struct ausiliarie sono un buon esempio di componente (una volta impacchettate in una DLL)

BulkEmailSender public class BulkEmailSender { private readonly EmailSender _emailsender; private readonly string _footer; public BulkEmailSender(string footer) { this._emailsender = new EmailSender(); this._footer = footer; } } public void SendEmail(List<string> addresses, string body) { if (addresses == null) throw new ArgumentNullException("addresses"); if (body == null) throw new ArgumentNullException("body"); foreach (var a in addresses) { if (!this._emailsender.sendemail(a, body + this._footer)) throw new Exception("Cannot send email"); } }

Dipendenze Di cosa abbiamo bisogno per distribuire BulkEmailSender? Se EmailSender... non funziona/non ancora implementata? dipende da un DB? dall esistenza di una particolare cartella nel filesystem? Se il cliente volesse usare un altra componente per spedire le singole email? Come possiamo testarla? Se siamo offline? Se non vogliamo spammare durante il testing?

Classi come tipo Usare il nome di una classe come tipo crea un legame (troppo) forte Non si può: distribuire/riusare (facilmente) una classe se dipende da un altra sostituire un implementazione con un altra Come risolvere questi problemi? Lo sapete Usiamo le interfacce, ma certo! ma come si creano gli oggetti? Usiamo delle factory Sì, ma è molto meno ovvio di quello che sembra (la versione classica, costruttore privato, campo statico TheInstance NON va bene)

Non vogliamo/possiamo usare new Ovviamente, solo per quanto riguarda le classi che implementano le astrazioni/interfacce che ci interessano Va benissimo istanziare le collection, le classi di sistema e quelle appartenenti alla stessa componente Vari approcci Factory (tipicamente un Singleton, statico) Service Locator Dependency Injection

Situazione iniziale Bulk istanzia ES Assembly (DLL o EXE) Una componente è un unità indipendete di: produzione distribuzione/acquisizione In.NET questo vuol dire: componente=assembly

Un assembly, chiaramente, non basta usa/ dipende da IES implementa Bulk ES ES Possiamo astrarre introducento l interfaccia IES Nota: Bulk non ha bisogno di creare degli ES, ma gliene serve uno (rimuoveremo dopo questa semplificazione, che per Bulk è assolutamente ragionevole ma in altri scenari potrebbe non esserlo)

Ci siamo quasi usa/ dipende da IES implementa Bulk c d a ES x ES y z

Ma se Bulk dovesse creare degli IES? usa/dipende da IES IES Factory implementa Bulk ES istanzia ESF ES istanzia ES F Dimentichiamoci le altre classettine per semplicità

e consideriamo solo le dipendenze fra assembly IES IES Factory Bulk ES ESF ES ES F

Un esempio: il Testing Assert Unit Test SUT

Stub e Mock Assert Stub Unit Test SUT Mock Ok Se il costruttore istanzia gli oggetti o gli oggetti sono dei Singleton (statici)...come posso sostituirci degli stub/mock?

Cose ovvie Per testare un metodo (d istanza) la classe che lo contiene va istanziata Se il costruttore fa troppo può diventare difficile o troppo lento (connessione di rete, DB...) La presenza di inizializzatori/campi statici peggiora le cose Globali alla VM, non all applicazione Ogni unit-test deve, logicamente, istanziare una (porzione) di nuova app Devono fare/contenere cose diverse durante il testing L ordine di esecuzione diventa importante I test non possono essere lanciati in parallelo!

Classi (facilmente) testabili La responsabilità di creare degli oggetti va separata! Nei costruttori, invece di istanziare gli oggetti che servono, si richiedono tali oggetti Disinteressandosi sul come vengono istanziati si richiedono solo gli oggetti che servono direttamente (quelli che vengono copiati nei campi) Idealmente, il corpo di un costruttore dovrebbe essere una sequenza di assegnazioni Ricevere x e poi fare this.f = x.qualcosa... è molto sospetto

Principio di minima conoscenza (anche: Legge di Demetra) Supponete di dover pagare una birra. Al barista date: i soldi oppure il portafoglio, in modo che si prenda i soldi?

Equivalente in codice public void Purchase(Customer c) { Money m = c.getwallet().getmoney(); this.recordsale(, m); } Nel testing: Money m = new Money(5); Wallet w = new Wallet(m); Customer c = new Customer(w); Goods g = g.purchase(c); //...assert...

Classe Factory (singleton alla GoF ) E già qualcosa, Minimizza i cambiamenti (una sola new, invece di tante sparse qua e là) ma non risolve: Le classi dipendono dalla Factory invece che dalle implementazioni, ma lei dipende da loro... per transitività siamo al punto di partenza E statica (se non lo fosse, chi la istanzierebbe e come? Si sposterebbe solo il problema...) il singleton non static va benissimo ne parliamo dopo

Service Locator (Factory non statica?) Scarica la responsabilità di istanziare gli oggetti su un oggetto, il Service Locator (o Registry, Context, Manager, Environment,...), che si passa alle classi che devono istanziare interfacce Le vere dipendenze sono nascoste dal fatto che ogni classe dipende dal Service Locator Possibile, ma difficile il testing: cosa ridefinisco/modifico nel Service Locator per creare gli stub? Ogni classe dipende dal Service Locator, che sa istanziare tutte le classi, quindi ogni classe dipende da tutte le altre...

Dependency Injection Esistono vari tipi di DI, consideriamo la constructor injection (è quella da usare di default ) che inietta tramite i costruttori Altre sono la method, property, field,... L idea è estremamente semplice: i costruttori richiedono gli oggetti che servono direttamente, invece di crearli

Nel nostro esempio: public BulkEmailSender(string footer) { this._emailsender = new EmailSender(); this._footer = footer; } public BulkEmailSender(IEmailSender emailsender, string footer) { } this._emailsender = emailsender; this._footer = footer;

Il testing diventa facile! [TestFixture] public class TestBulkEmailSender { [Test] public void TestSendMailEmailSenderSucceeds() { var bulk = new BulkEmailSender(mock_ok, "bla bla"); bulk.sendemail(new List<string> { "a@a.com", "b@b.com" }, "hi!"); } } [Test] [ExpectedException(typeof(Exception))] public void TestSendMailEmailSenderFails() { var bulk = new BulkEmailSender(stub_fails, "bla bla"); bulk.sendemail(new List<string> { "a@a.com", "b@b.com" }, "hi!"); }

Nei test è ragionevole Notate che: Usare new: non c è nulla di male nel fatto che il codice di test dipenda da ciò che sta testando Testare classi non pubbliche (i dettagli di come vedere cose internal li vedremo quando parleremo di Unit Testing) Per TAP i test saranno gli stessi per tutte le diverse implementazioni, quindi dovrete istanziare interfacce (come spiegheremo nel seguito) Il fatto che il codice sia più facilmente testabile è solo uno dei vantaggi della DI, non l unico motivo di usarla

Dubbi (classici) Ma se ho 10 campi il costruttore deve richiedere 10 oggetti? Non sono troppi? Salvo eccezioni, sì deve richiedere 10 parametri Eccezioni: le classi valore (o le collection), ovvero le foglie del grafo degli oggetti, si possono istanziare senza problemi (poiché non hanno dipendenze a loro volta) Se i parametri sono troppi, è probabile che la classe abbia troppe responsabilità, ma ce le aveva già prima di usare la DI! La DI rende esplicite le dipendenze, non le diminuisce e non le aumenta Quindi, se una classe di basso livello ha bisogno, diciamo, di un logger, lo devo passare lungo tutta la catena? NO! Tipico fraintendimento... vediamo un esempio

Tradizionalmente...

Con la DI...

In codice... (versione tradizionale con singleton) DB.Init("..."); // inizializza il singleton Logger.Init(); // idem, l'ordine e` importante! var bulk = new BulkEmailSender(); // apparentemente indipendente da sopra // ma se il Logger non e` stato inizializzato // EmailSender (istanziato da BulkEmailSender) // si schianta

Con la DI... var db = new DB(...); var logger = new Logger(db); var emailsender = new EmailSender(logger); var bulk = new BulkEmailSender(emailSender, ""); // sbagliando l'ordine, la compilazione // fallisce! :-)

DI container Siccome le dipendenze sono esplicite, la composizione degli oggetti a volte si può automatizzare E quello che fanno i container DI Si programmano associando implementazioni a interfacce: Via codice, max flessibilità e controllo statico sui tipi, ma la riconfigurazione richiede ricompilazione Via file di configurazione (tipicamente XML) E poi loro gestiscono la creazione e lo scope (singleton, factory o container)

Configurazione Autofac (via codice) var builder = new ContainerBuilder(); builder.register<emailsender>().as<iemailsender>(); builder.register<bulkemailsender>(); // sto barando un pochino (assumo che l'unico // argomento sia l'iemailsender) IContainer container = builder.build(); var bulk = container.resolve<bulkemailsender>(); bulk.sendemail(new List<string>() {"a@a.com"}, "");

Senza barare... ( ma usando le che dobbiamo ancora vedere a lezione) var builder = new ContainerBuilder(); builder.register<emailsender>().as<iemailsender>(); builder.register(c => new BulkEmailSender( c.resolve<iemailsender>(), "ciao" ) );

Scope degli oggetti Singleton (è il default): viene creato un solo oggetto Si abilita con SingletonScoped() sul builder Factory: a ogni richiesta viene creato un nuovo oggetto Container: viene creato un oggetto per container I container possono essere innestati, viene gestita la distruzione deterministica via IDisposable Contextual: simile a container, viene creato un oggetto per contesto

Configurazione via XML builder.registermodule(new ConfigurationSettingsReader("autofac", "pippo.xml")); <?xml version="1.0" encoding="utf-8"?> <configuration> <configsections> <section name="autofac" type="autofac.configuration.sectionhandler, Autofac"/> </configsections> <autofac> <components> <component type="emailer.emailsender, Emailer" service="emailerinterfaces.iemailsender, EmailerInterfaces" /> <component type="emailer.bulkemailsender, Emailer" scope="factory"> <parameters> <parameter name="footer" value="ciao" /> </parameters> </component> </components> </autofac> </configuration>

Altro dubbio classico Ma si devono per forza creare tutti gli oggetti all inizio? Subito dopo la prima chiamata a Resolve? No! All inizio vengono creati solo gli oggetti necessari a far partire il sistema Se una classe ha bisogno di creare istanze (di intefacce) riceverà nel costruttore un oggetto Factory (di tipo interfaccia) per crearle NON si passa il container in giro (altrimenti sarebbe un Service Locator) Vediamo un esempio...

Esempio di (non static) Factory public class C { private readonly IPointFactory _pointfactory; private readonly ILineFactory _linefactory; public C(IPointFactory pointfactory, ILineFactory linefactory) { this._pointfactory = pointfactory; this._linefactory = linefactory; } public void DoSomething(/*... */) { //... ILine newline = CreateLine(/*... */); //... } private ILine CreateLine(int x0, int y0, int x1, int y1) { IPoint p0 = this._pointfactory.create(x0, y0); IPoint p1 = this._pointfactory.create(x1, y1); return this._linefactory.create(p0, p1); } }

Terminologia Va di moda parlare di: Inversion of Control e Dependency Injection e non mancano: le implementazioni di IoC/DI container Autofac, Castle of Windsor, Guice, NanoContainer, Ninject, PicoContainer, Spring, Spring.NET, Unity, Winter4net la confusione: IoC e DI sono la stessa cosa?

IoC e DI In questi contesti, sì; sono la stessa cosa (IoC container = DI container), ma IoC è un concetto più generale Ogni framework usa IoC (quando associate un azione a un pulsante in una GUI lo state usando) DI, pur apparentemente fuorviante, è il nome più corretto sebbene Autofac si autodefinisca a fresh approach to IoC (uff!)

Approfondimenti/crediti The clean code talks... (ce ne sono diversi) by Miško Hevery http://www.youtube.com/watch?v=rlflcwkxhj0 Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler http://martinfowler.com/articles/injection.html