The Hitchhiker's Guide to testable code: semplici regole per scrivere codice semplice da testare
The Hitchhiker's
            Guide to
          testable code



semplici regole per scrivere codice semplice da testare
                                  Davide Cerbo - davidecerbo@gmail.com - JUG Roma
                                       Nicola Raglia - n.raglia@gmail.com - JUG Roma
Non parleremo di...

   – XP Programming

– Test-Driven Development

         – Agile

        – Scrum

        – etc etc
...ma parleremo di...

                 come scrivere codice
                   TESTABILE
perchè l'unico modo per applicare le metodologie dette in
                precedenza è scrivere i
                 TEST UNITARI
     e l'unico modo per scriverli è produrre codice
                   TESTABILE
Definizioni assortite

Test: processo atto ad individuare carenze funzionali e non
funzionali durante la fase di sviluppo del software.

Test Unitario: è un test atto a verificare una componente
elementare del software possibilmente in termini di
isolamento dalle dipendenze

Refactoring: è il processo che prevede una ristrutturazione
del codice modificando il meno possibile le interfacce
Ancora definizioni assortite

Design Pattern: soluzione progettuale generale a un
problema ricorrente

Mock Object: oggetti destinati a simulare il comportamento
di oggetti reali.
Durante il test con i mock object abbiamo:
    o creazione mock
    o definizione del comportamento del mock object
    o esecuzione del test
    o verifica del comportamento del mock object
Esempio di codice brutto
Iniziamo dal costruttore

public RubricaImpl(Properties properties, ApplicationContext applicationContext) {
    this.user = applicationContext.getAuthenticationContext().getUser();
    this.url = properties.getProperty("url");
    this.userName = properties.getProperty("userName");
    this.password = properties.getProperty("password");
    try {
       this.connection = DriverManager.getConnection(url, userName, password);
    } catch (SQLException e) {
       //gestione eccezione
    }
    this.database = new DatabaseImpl(connection);
  }
Il nostro primo (non) Unit Test
public void testConstructor() throws Exception {
    Properties properties = new Properties();
    properties.load(new FileInputStream("database.properties"));
    ApplicationContext applicationContext = ApplicationContext.getContext();
    Rubrica rubrica = new RubricaImpl(properties, applicationContext);
 }

con i Mock Objects:
public void testConstructor() throws Exception {
    Properties properties = new Properties();
    properties.setProperty("user", "dbuser");
    properties.setProperty("password","dbpassword");
    properties.setProperty("url", "jdbc:db:///test");
    ApplicationContext applicationContext = createMock(ApplicationContext.class);
    AuthenticationContext authContext = createMock(AuthenticationContext.class);
    expect(applicationContext.getAuthenticationContext()).andReturn(authContext);
    expect(authContext.getUser()).andReturn(createMock(User.class));
    replay(authContext, applicationContext);
    Rubrica rubrica = new RubricaImpl(properties, applicationContext);
    verify(authContext, applicationContext);
  }
Rispettiamo la legge

public RubricaImpl(String url, String userName, String password, User user) {
   this.user = user;
   this.url = url;
   this.userName = userName;
   this.password = password;
   Connection connection=DriverManager.getConnection(url,userName,password);
   this.database = new DatabaseImpl(connection);
 }


     Per rispettare la legge di Demeter un oggetto può solo invocare i metodi:
 •    propri
 •    dei suoi parametri
 •    di ogni oggetto che crea
 •    delle sue variabili
Mai dire CONTEXT
public RubricaImpl(Properties properties, ApplicationContext applicationContext) {
    this.user = applicationContext.getUser();
    this.url = properties.getProperty("url");
    .......
    .......
}

public RubricaImpl(String url, String userName, String password, User user) {
    this.user = user;
    this.url = url;
    .......
    .......
}

    applicationContext e properties sono oggetti         di contesto
    quindi difficilmente testabili unitariamente e richiedono
    fatica aggiuntiva nel test con i mock object.
Vietato affaticare
public RubricaImpl(String url, String userName, String password, User user) {
    .....
   this.userName = userName;
    Connection connection = DriverManager.getConnection(url,userName,password);
   this.database = new DatabaseImpl(connection);
}

public RubricaImpl(String url, String userName, String password, User user) {
   .....
   this.userName = userName;
   this.database = DatabaseManager.getDatabase(url,userName,password);
 }




   Questa è una soluzione ma non va bene perché si usa un
                         metodo statico
Solo l'indispensabile
public RubricaImpl(String url, String userName, String password, User user) {
    this.userName =userName;
    this.database = DatabaseManager.getDatabase(url,userName,password);
  }




             2 SOLUZIONI DA EVITARE!!!
public RubricaImpl(User user) {
    this.user = user;
    this.database = DatabaseSingleton.getInstance();
  }


                     Ecco fatta un po' di pulizia!
     Non era giusto far conoscere alla Rubrica le informazioni per
                        accedere al database!
         Ma è spuntato un singleton e questo è male!
Dependency Injection

public RubricaImpl(Database database, User user) {
    this.user = user;
    this.database = database;
  }




               Il costruttore è stato alleggerito da
                   responsabilità non proprie.
                       Ma ora come lo usiamo?
Pattern Abstract Factory

public class RubricaFactoryImpl implements RubricaFactory {
  private final DatabaseFactory databaseFactory;

    public RubricaFactoryImpl(DatabaseFactory databaseFactory) {
      this.databaseFactory = databaseFactory;
    }

    public Rubrica getRubrica(User user) {
      return new RubricaImpl(databaseFactory.getDatabase(), user);
    }

}

       La responsabilità di creare oggetti sarà sempre data
           ad una Factory o ad altri pattern creazionali.
Passiamo al Database
public class DatabaseFactoryImpl implements DataBaseFactory {
  private final Properties properties;

    public DatabaseFactoryImpl(Properties properties) {
      this.properties = properties;
    }
    public Database getDatabase(){
      String url = properties.getProperty("url");
      String userName = properties.getProperty("userName");
      String password = properties.getProperty("password");
      Connection connection = null;
      try {
         connection = DriverManager.getConnection(url, userName, password);
      } catch (SQLException e) { //gestione eccezione }
      return new DatabaseImpl(connection);
    }
}

                DatabaseFactoryImpl non è testabile,
               andrebbe fatto ulteriore refactoring, ma il
                           tempo è poco :(
Il Test (quasi) finale (1/2)‫‏‬

public void testConstructor() throws Exception {
    Database database = createMock(Database.class);
    User user = createMock(User.class);
    replay(database, user);
    Rubrica rubrica = new RubricaImpl(database, user);
    verify(database, user);
}




         Non c'è bisogno di descrivere comportamento per gli
         oggetti mock perchè il costruttore non fa niente altro
                        che costruire l'oggetto.
                    Ma le factory appena create?
Il Test (quasi) finale (2/2)‫‏‬

public void testFactory() throws Exception {
    DatabaseFactory databaseFactory = createMock(DatabaseFactory.class);
    Database database = createMock(Database.class);
    User user = createMock(User.class);
    expect(databaseFactory.getDatabase()).andReturn(database);
    replay(databaseFactory, user, database);
    RubricaFactory rubricaFactory = new RubricaFactoryImpl(databaseFactory);
    Rubrica rubrica = rubricaFactory.getRubrica(user);
    verify(databaseFactory, user, database);
    assertNotNull(rubrica);
  }
Gli obbiettivi raggiunti

Single responsability
Assegnare la giusta responsabilità
Utilizzare la Dependency Injection
Dividere il fare dal creare
Evitare stati globali
Design by Interface
Andiamo avanti...

public void publish(){
  Context context = new InitialContext();
  Object reference = context.lookup("PublisherService");
  PublisherEjb home = (PublishEjb)PortableRemoteObject.narrow(reference,PublishEjb.class);
  PublisherService publisher = home.create();
  publisher.publish(this);
}
Testiamolo...

Totalmente non testabile in termini
            unitari!!!
Via il Sevice Locator

public RubricaImpl(Database database, User user, PublisherService publisher) {
    this.user = user;
    this.database = database;
    this.publisher = publisher;
}


public void publish(){
     this.publisher.publish(this);
}



         Iniettiamo una classe che abbia la responsabilità di
         pubblicare. Nel nostro caso lo farà tramite EJB, ma sarà
         semplice sostituire la tecnologia.
Ancora non è finita...

public RubricaImpl(Database database, User user) {
    this.user = user;
    this.database = database;
  }


public void publishWith(PublisherService publisher){
  publisher.publish(this);
}



         Passare l'oggetto PublisherService al costruttore è errato
         perché non è necessario al normale ciclo di vita della
         Rubrica, ma serve solo nel caso di una richiesta di
         pubblicazione
Problema solo spostato

Abbiamo solo spostato il problema, infatti
l'implementazione PublisherServiceEJB sarà
intestabile unitariamente...


      ...ma fortunatamente la nuova specifica EJB
      3.0 ci viene in aiuto eliminando il
      ServiceLocator


Ma non è lo scopo di questo talk spiegare
come :D
Il Test finale

public void testPublish() throws Exception {
    Database database = createMock(Database.class);
    User user = createMock(User.class);
    replay(database, user);
    Rubrica rubrica = new RubricaImpl(database, user);
    verify(database, user);

      PublisherService publisherService = createMock(PublisherService.class);
      publisherService.publish(rubrica);
      replay(publisherService, user);
      rubrica.publishWith(publisherService);
      verify(publisherService, user);
  }
Bibliografia
Google Testing Blog
http://googletesting.blogspot.com/

Refactoring: Improving the Design of Existing Code (Martin Fowler)‫‏‬
http://www.refactoring.com/

Refactoring Workbook (William C. Wake)‫‏‬
http://xp123.com/rwb/

Applicare UML e Pattern (Craig Larman)‫‏‬
http://www.craiglarman.com

Principi di ingegneria del software (Pressman)‫‏‬
http://highered.mcgraw-hill.com/sites/0072853182/

Wikipedia
http://www.wikipedia.org
Strumenti utili
Unit Test:
www.junit.org

Test code coverage:
http://cobertura.sourceforge.net/
http://emma.sourceforge.net/

Testability:
http://code.google.com/p/testability-explorer/
http://testabilityexplorer.org/report

Mock objects:
http://www.easymock.org/
http://www.jmock.org/
http://code.google.com/p/mockito/
I nostri contatti

Davide Cerbo
davidecerbo@gmail.com
davide.cerbo@pro-netics.com
http://jesty.it

Nicola Raglia
n.raglia@gmail.com
nicola.raglia@pro-netics.com
http://sourcengineering.org
Q&A




Q&A
Q&A

More Related Content

PDF
Clean android code - Droidcon Italiy 2014
PDF
Introduzione a node.js
PPT
Java lezione 18
PPT
Java lezione 17
PPT
Introduzione al Test Driven Development
PPTX
Entity Framework 6 for developers, Code-First!
PPTX
Pomeriggio Entity Framework - Code First
PPTX
Clean android code - Droidcon Italiy 2014
Introduzione a node.js
Java lezione 18
Java lezione 17
Introduzione al Test Driven Development
Entity Framework 6 for developers, Code-First!
Pomeriggio Entity Framework - Code First

Similar to The Hitchhiker's Guide to testable code: semplici regole per scrivere codice semplice da testare (20)

PDF
Unit Testing
PPTX
PowerMock TDD User Group Milano
PDF
Presentazione Testing automatizzato
PPT
Programmazione a oggetti tramite la macchina del caffé (pt. 3)
PPTX
Il web 2.0
PDF
Spring Intro
PDF
Java Unit Testing - JUnit (2)
PPTX
Dependency injection: the good parts
PPTX
Test Driven Development @ Xe.Net
PPTX
Design Pattern Creazionali
PPTX
Inversion of Control @ CD2008
PDF
Repository pattern
PDF
Modulo 6 Spring Framework Core E Aop
PDF
Lezione 01 - Singleton
PDF
Java Unit Testing - Introduction
ODP
Design Pattern
PDF
Spring E Spring Web Flow Nel Progetto Jug Avis Web
PPT
Spring 2.5
PDF
Baby Steps TripServiceKata
PDF
Programmazione web libera dai framework
Unit Testing
PowerMock TDD User Group Milano
Presentazione Testing automatizzato
Programmazione a oggetti tramite la macchina del caffé (pt. 3)
Il web 2.0
Spring Intro
Java Unit Testing - JUnit (2)
Dependency injection: the good parts
Test Driven Development @ Xe.Net
Design Pattern Creazionali
Inversion of Control @ CD2008
Repository pattern
Modulo 6 Spring Framework Core E Aop
Lezione 01 - Singleton
Java Unit Testing - Introduction
Design Pattern
Spring E Spring Web Flow Nel Progetto Jug Avis Web
Spring 2.5
Baby Steps TripServiceKata
Programmazione web libera dai framework
Ad

More from Davide Cerbo (11)

PDF
Kotlin: forse è la volta buona (Trento)
PDF
Kotlin: maybe it's the right time
PDF
From 0 to React with ES6 and Tests
PDF
Kotlin: foss' a vota bona (could be the right time)
PDF
Di cosa parlano?
PPTX
Adesso In Onda
PDF
Il web intelligente
ODP
Working between the clouds (versione completa)
ODP
Working between the clouds
ODP
Non Conventional Android Programming (English)
ODP
Non Conventional Android Programming (Italiano)
Kotlin: forse è la volta buona (Trento)
Kotlin: maybe it's the right time
From 0 to React with ES6 and Tests
Kotlin: foss' a vota bona (could be the right time)
Di cosa parlano?
Adesso In Onda
Il web intelligente
Working between the clouds (versione completa)
Working between the clouds
Non Conventional Android Programming (English)
Non Conventional Android Programming (Italiano)
Ad

The Hitchhiker's Guide to testable code: semplici regole per scrivere codice semplice da testare

  • 2. The Hitchhiker's Guide to testable code semplici regole per scrivere codice semplice da testare Davide Cerbo - davidecerbo@gmail.com - JUG Roma Nicola Raglia - n.raglia@gmail.com - JUG Roma
  • 3. Non parleremo di... – XP Programming – Test-Driven Development – Agile – Scrum – etc etc
  • 4. ...ma parleremo di... come scrivere codice TESTABILE perchè l'unico modo per applicare le metodologie dette in precedenza è scrivere i TEST UNITARI e l'unico modo per scriverli è produrre codice TESTABILE
  • 5. Definizioni assortite Test: processo atto ad individuare carenze funzionali e non funzionali durante la fase di sviluppo del software. Test Unitario: è un test atto a verificare una componente elementare del software possibilmente in termini di isolamento dalle dipendenze Refactoring: è il processo che prevede una ristrutturazione del codice modificando il meno possibile le interfacce
  • 6. Ancora definizioni assortite Design Pattern: soluzione progettuale generale a un problema ricorrente Mock Object: oggetti destinati a simulare il comportamento di oggetti reali. Durante il test con i mock object abbiamo: o creazione mock o definizione del comportamento del mock object o esecuzione del test o verifica del comportamento del mock object
  • 8. Iniziamo dal costruttore public RubricaImpl(Properties properties, ApplicationContext applicationContext) { this.user = applicationContext.getAuthenticationContext().getUser(); this.url = properties.getProperty("url"); this.userName = properties.getProperty("userName"); this.password = properties.getProperty("password"); try { this.connection = DriverManager.getConnection(url, userName, password); } catch (SQLException e) { //gestione eccezione } this.database = new DatabaseImpl(connection); }
  • 9. Il nostro primo (non) Unit Test public void testConstructor() throws Exception { Properties properties = new Properties(); properties.load(new FileInputStream("database.properties")); ApplicationContext applicationContext = ApplicationContext.getContext(); Rubrica rubrica = new RubricaImpl(properties, applicationContext); } con i Mock Objects: public void testConstructor() throws Exception { Properties properties = new Properties(); properties.setProperty("user", "dbuser"); properties.setProperty("password","dbpassword"); properties.setProperty("url", "jdbc:db:///test"); ApplicationContext applicationContext = createMock(ApplicationContext.class); AuthenticationContext authContext = createMock(AuthenticationContext.class); expect(applicationContext.getAuthenticationContext()).andReturn(authContext); expect(authContext.getUser()).andReturn(createMock(User.class)); replay(authContext, applicationContext); Rubrica rubrica = new RubricaImpl(properties, applicationContext); verify(authContext, applicationContext); }
  • 10. Rispettiamo la legge public RubricaImpl(String url, String userName, String password, User user) { this.user = user; this.url = url; this.userName = userName; this.password = password; Connection connection=DriverManager.getConnection(url,userName,password); this.database = new DatabaseImpl(connection); } Per rispettare la legge di Demeter un oggetto può solo invocare i metodi: • propri • dei suoi parametri • di ogni oggetto che crea • delle sue variabili
  • 11. Mai dire CONTEXT public RubricaImpl(Properties properties, ApplicationContext applicationContext) { this.user = applicationContext.getUser(); this.url = properties.getProperty("url"); ....... ....... } public RubricaImpl(String url, String userName, String password, User user) { this.user = user; this.url = url; ....... ....... } applicationContext e properties sono oggetti di contesto quindi difficilmente testabili unitariamente e richiedono fatica aggiuntiva nel test con i mock object.
  • 12. Vietato affaticare public RubricaImpl(String url, String userName, String password, User user) { ..... this.userName = userName; Connection connection = DriverManager.getConnection(url,userName,password); this.database = new DatabaseImpl(connection); } public RubricaImpl(String url, String userName, String password, User user) { ..... this.userName = userName; this.database = DatabaseManager.getDatabase(url,userName,password); } Questa è una soluzione ma non va bene perché si usa un metodo statico
  • 13. Solo l'indispensabile public RubricaImpl(String url, String userName, String password, User user) { this.userName =userName; this.database = DatabaseManager.getDatabase(url,userName,password); } 2 SOLUZIONI DA EVITARE!!! public RubricaImpl(User user) { this.user = user; this.database = DatabaseSingleton.getInstance(); } Ecco fatta un po' di pulizia! Non era giusto far conoscere alla Rubrica le informazioni per accedere al database! Ma è spuntato un singleton e questo è male!
  • 14. Dependency Injection public RubricaImpl(Database database, User user) { this.user = user; this.database = database; } Il costruttore è stato alleggerito da responsabilità non proprie. Ma ora come lo usiamo?
  • 15. Pattern Abstract Factory public class RubricaFactoryImpl implements RubricaFactory { private final DatabaseFactory databaseFactory; public RubricaFactoryImpl(DatabaseFactory databaseFactory) { this.databaseFactory = databaseFactory; } public Rubrica getRubrica(User user) { return new RubricaImpl(databaseFactory.getDatabase(), user); } } La responsabilità di creare oggetti sarà sempre data ad una Factory o ad altri pattern creazionali.
  • 16. Passiamo al Database public class DatabaseFactoryImpl implements DataBaseFactory { private final Properties properties; public DatabaseFactoryImpl(Properties properties) { this.properties = properties; } public Database getDatabase(){ String url = properties.getProperty("url"); String userName = properties.getProperty("userName"); String password = properties.getProperty("password"); Connection connection = null; try { connection = DriverManager.getConnection(url, userName, password); } catch (SQLException e) { //gestione eccezione } return new DatabaseImpl(connection); } } DatabaseFactoryImpl non è testabile, andrebbe fatto ulteriore refactoring, ma il tempo è poco :(
  • 17. Il Test (quasi) finale (1/2)‫‏‬ public void testConstructor() throws Exception { Database database = createMock(Database.class); User user = createMock(User.class); replay(database, user); Rubrica rubrica = new RubricaImpl(database, user); verify(database, user); } Non c'è bisogno di descrivere comportamento per gli oggetti mock perchè il costruttore non fa niente altro che costruire l'oggetto. Ma le factory appena create?
  • 18. Il Test (quasi) finale (2/2)‫‏‬ public void testFactory() throws Exception { DatabaseFactory databaseFactory = createMock(DatabaseFactory.class); Database database = createMock(Database.class); User user = createMock(User.class); expect(databaseFactory.getDatabase()).andReturn(database); replay(databaseFactory, user, database); RubricaFactory rubricaFactory = new RubricaFactoryImpl(databaseFactory); Rubrica rubrica = rubricaFactory.getRubrica(user); verify(databaseFactory, user, database); assertNotNull(rubrica); }
  • 19. Gli obbiettivi raggiunti Single responsability Assegnare la giusta responsabilità Utilizzare la Dependency Injection Dividere il fare dal creare Evitare stati globali Design by Interface
  • 20. Andiamo avanti... public void publish(){ Context context = new InitialContext(); Object reference = context.lookup("PublisherService"); PublisherEjb home = (PublishEjb)PortableRemoteObject.narrow(reference,PublishEjb.class); PublisherService publisher = home.create(); publisher.publish(this); }
  • 21. Testiamolo... Totalmente non testabile in termini unitari!!!
  • 22. Via il Sevice Locator public RubricaImpl(Database database, User user, PublisherService publisher) { this.user = user; this.database = database; this.publisher = publisher; } public void publish(){ this.publisher.publish(this); } Iniettiamo una classe che abbia la responsabilità di pubblicare. Nel nostro caso lo farà tramite EJB, ma sarà semplice sostituire la tecnologia.
  • 23. Ancora non è finita... public RubricaImpl(Database database, User user) { this.user = user; this.database = database; } public void publishWith(PublisherService publisher){ publisher.publish(this); } Passare l'oggetto PublisherService al costruttore è errato perché non è necessario al normale ciclo di vita della Rubrica, ma serve solo nel caso di una richiesta di pubblicazione
  • 24. Problema solo spostato Abbiamo solo spostato il problema, infatti l'implementazione PublisherServiceEJB sarà intestabile unitariamente... ...ma fortunatamente la nuova specifica EJB 3.0 ci viene in aiuto eliminando il ServiceLocator Ma non è lo scopo di questo talk spiegare come :D
  • 25. Il Test finale public void testPublish() throws Exception { Database database = createMock(Database.class); User user = createMock(User.class); replay(database, user); Rubrica rubrica = new RubricaImpl(database, user); verify(database, user); PublisherService publisherService = createMock(PublisherService.class); publisherService.publish(rubrica); replay(publisherService, user); rubrica.publishWith(publisherService); verify(publisherService, user); }
  • 26. Bibliografia Google Testing Blog http://googletesting.blogspot.com/ Refactoring: Improving the Design of Existing Code (Martin Fowler)‫‏‬ http://www.refactoring.com/ Refactoring Workbook (William C. Wake)‫‏‬ http://xp123.com/rwb/ Applicare UML e Pattern (Craig Larman)‫‏‬ http://www.craiglarman.com Principi di ingegneria del software (Pressman)‫‏‬ http://highered.mcgraw-hill.com/sites/0072853182/ Wikipedia http://www.wikipedia.org
  • 27. Strumenti utili Unit Test: www.junit.org Test code coverage: http://cobertura.sourceforge.net/ http://emma.sourceforge.net/ Testability: http://code.google.com/p/testability-explorer/ http://testabilityexplorer.org/report Mock objects: http://www.easymock.org/ http://www.jmock.org/ http://code.google.com/p/mockito/
  • 28. I nostri contatti Davide Cerbo davidecerbo@gmail.com davide.cerbo@pro-netics.com http://jesty.it Nicola Raglia n.raglia@gmail.com nicola.raglia@pro-netics.com http://sourcengineering.org