Visualizzazione post con etichetta test. Mostra tutti i post
Visualizzazione post con etichetta test. Mostra tutti i post

[10years] Test



I principi base di una buona suite di test sono condivisi tra tutti: 

  • efficaci
  • veloci
  • manutenibili ..
Entrando in maggiore dettaglio iniziano numerose guerre di religione e filosofie differenti e non sempre conciliabili.

Code Coverage


Nel ricordare la peggiore esperienza nella scrittura di test mi viene in mente un cliente che richiedeva a tutti i team di sviluppo di raggiungere una code coverage non inferiore al 90 % . 
In quel momento i vari team avevano una copertura di circa il 20 %, ed hanno interrotto gli sviluppi per aderire alla nuova regola. 
Mi ricordo che, trovando assurda quella scelta, scrissi un test che via reflection andava a verificare tutti i getter / setter in automatico, aiutando i team a raggiungere l'obiettivo. Chiaramente era una provocazione sottile, ma formalmente corretta.

Che conseguenza ha portato l'introduzione di quella regola ?  Le logiche di programmazione si erano spostate dal codice java ai sistemi dove lo strumento di code coverage non aveva 'potere': il database con stored procedures complesse e le view jsp. 

Dire quindi che è importante avere 'tanti' test non ha chissà che valore, rispetto ad averne anche pochi ma assai efficaci.
 
Ma quale principio ci guida nella scrittura dei test? 
Il peggiore che abbia mai incontrati è 

Il principio delle scimmia



piu volte enunciato da vari colleghi incontrati nella storia: se di notte una scimmia cancella una riga a caso della tua code base , almeno un test si deve rompere.

E' un principio sensato, se non fosse che: no, non ci sono scimmie di quel genere! E' anche vero che un qualsiasi programmatore può cancellare una riga per sbaglio...ma non ha senso basare la tua strategia di scrittura di test su una paura irrazionale e statisticamente improbabile. 

Un altro principio che reputo errato è 

Gli acceptance test devono essere end-to-end



Tale principio è arrivato tardi nella mia esperienza lavorativa. I primi AT (Acceptance Test) li scrissi con Fit/Fitnesse. Avevano forma tabellare o erano narrativi, e interrogavano le logiche di business implementate nel dominio. L'input, in qualsiasi forma arrivasse, tramite chiamate http , o file di testo, o comandi in una shell, non era importante. Era importante la logica sottostante, il motore del tuo progetto.

Dopo alcuni anni ho notato questa uguaglianza logica nella testa di molti: un AT deve esercitare i sistemi "veri". Ad esempio se è una webapp devono girare in un browser.

Secondo me i test end to end non sono acceptance test nella loro idea originaria. 
Al di là della nomenclatura, anche i test di 'integrazione' con i sistemi esterni che vedo scritti in giro non vanno bene! 

I test di integrazione devono garantire che il tuo sistema sa interpretare correttamente i dati di input e che costruisce corretti dati di output, in totale aderenza ad un protocollo comunicativo. 
Ad esempio durante lo sviluppo di un ecommerce che si basa su paypal, non ha senso che i tuoi test passino dalla sandbox simulando il giro completo effettuato da un utente. 
In tal modo ti stai legando al funzionamento di un sistema che non è sotto il tuo controllo e i tuoi test stanno testando il comportamento di un altro sistema su cui non stai lavorando !
E' invece importante garantire che le chiamate http scambiate tra il tuo ecommerce e paypal siano corrette ! (ma senza necessità di una simulazione via browser) 

Riassumendo: 
  • gli AT devono esplicitare la logica applicativa. Non è necessario che siano di integrazione
  • i test di integrazione devono testare solo il protocollo di comunicazione (input e output) e basta !

Buoni principi


Per metterti in condizione di poter testare il tuo codice le dipendenze vanno iniettate. Non sono particolarmente a favore di librerie di dependency injection (non ti danno nulla in piu di farlo tu direttamente) ma non è questo il punto. E' importante non avere dipendenze dirette nel codice ! 

Portando questo principio su diversi livelli, dal singolo oggetto al modulo applicativo, riuscirai a trasformare il tuo sistema in una 'stanza dei bottoni' dove, a piacere, puoi switchare tra un database vero e uno in memoria, tra un sistema di messaggistica via code vero a uno finto, tra una dipendenza vera a una finta. 
Questo tipo di flessibilità l'ho visto applicato solo in poche occasioni in team molto maturi. Ma quando ho avuto modo di lavorare su un progetto dominato dalla 'stanza dei bottoni' il divertimento e la produttività sono stati a livelli superlativi ! 



In quasi tutti i progetti che ho lavorato ho sempre spinto verso l'adozione di 'builder' : oggetti che ti costruiscono un oggetto di dominio di 'default' , con tutte le dipendenze necessarie e che va bene nella maggior parte dei casi. 


Ad esempio in un progetto di ecommerce, è utile avere un 'productBuilder' che , a fronte della chiamata di un metodo 'build' ti dà un prodotto generico , disponibile in magazzino, con un prezzo valido, una descrizione normale etc.
La maggior parte dei test lavora su piccole variazioni in merito, ed è pertanto importante costruire una struttura che ti dia facilmente il default ma che ti permetta anche di iniettare piccole modifiche. Il product builder potrebbe essere customizzato cosi: 

new productbuilder.build()  
new productbuilder.withName('something').build()

new productbuilder.withPrice(12).build()
new productbuilder.withQuantity(5).build() ...

Una tipologia di test difficili da scrivere e ideare, ma assai utili, sono quelli relativi ad uno 'scenario' . I builder rappresentano in vitro ciò che si può fare ad un più elevato livello di astrazione: la costruzione di uno scenario 'tipo' e la possibilità di introdurre flessibilità al fine di verificare il comportamento del tuo sistema. 
Voglio rimanere volutamente generico, ma quando sono riuscito a creare dei test di scenario con i miei compagni di team ne ho trovato davvero molto giovamento! 

[Test] Non ho mai capito i test JMock


Sono nato seguendo come filosofia di scrittura dei test quella state-based.
Ho sempre fatto un po fatica a studiare la filosofia interaction-based.
Spesso quest'ultima è molto sponsorizzata e apprezzata dai vari programmatori che incontro.
Eppure temo di non aver ancora visto un esempio virtuoso di sua applicazione.

Penso che i test interaction-based siano:

  • un buono strumento per esplorare le dipendenze e le interrelazioni tra oggetti; ma una volta scelto il diagramma di comunicazione tra gli oggetti il loro scopo si esaurisce e sarebbe meglio buttarli.
  • un pessimo strumento per quanto riguarda il supporto al refactoring e al testing vero e proprio. Questo perchè il piu delle volte si trasformano in un copia/incolla del codice applicativo. Non supportano il refactoring nel senso che se decido che un certo pattern di comunicazione tra oggetti non mi convince e voglio cambiarlo (che so, A non parla piu con B ma con C) molti test si spaccano, mentre a me interessa solo sapere che il sistema continua a funzionare con la nuova idea che sto implementando.

A margine di queste discussioni generali, JMock è davvero pessimo.
Stavamo implementando una funzionalità che, a fronte della presenza di un parametro http,
presenta un certo messaggio su una web page.

Primo difetto: no reset delle expectations

La suite di test ha una set up che imposta delle registrazioni di default:
context.checking(new Expectations() {
{
allowing(request).getUser();
will(throwException(new WebRequest.NoUserInSession()));
allowing(request).isPost();
will(returnValue(true));
...
}


La nostra chiamata è in GET e quindi il nostro test cozza con la setup. Soluzione?
JMock non permette il reset di una registrazione (ho visto che è una funzionalità richiesta
ma non ancora implementata
)
Soluzione? Tagliare dalla setup la chiamata
allowing(request).isPost(); will(returnValue(true));

e copia/ incollarla in *ognuno* dei test case precedenti. Bleah

Secondo difetto: verbosità

Altra cosa che non mi piace è che ogni test dovrebbe risaltare la parte sotto esame, nascondendo
i dettagli poco importanti. In questo esempio,
context.checking(new Expectations() {
{
oneOf(authenticator).authenticate("someMerchantId", "someUser", "somePassword");
will(throwException(exception));
oneOf(observer).notifyUserNotFound(exception);
allowing(view).toHtml(); will(returnValue("someHtml"));
oneOf(view).setError("Login fallito");
oneOf(response).writeHtml(with(containsString("someHtml")));
}
});

solo la parte evidenziata è importante ; il resto è tutta noia.

Terzo difetto: messaggio di fail poco efficace

Supponiamo nel contesto del test precedente di aver dimenticato nel codice
la scrittura del metodo observer.notifyUserNotFound .
Il test fallisce con questo messaggio di output.

java.lang.AssertionError: not all expectations were satisfied
expectations:
allowed, already invoked 2 times: webRequest.getUser(); throws
allowed, already invoked 1 time: webRequest.isPost(); returns
allowed, already invoked 1 time: webRequest.getParameter("username"); returns "someUser"
allowed, already invoked 1 time: webRequest.getParameter("password"); returns "somePassword"
allowed, already invoked 1 time: webRequest.getParameter("merchantId"); returns "someMerchantId"
expected once, already invoked 1 time: authenticator.authenticate("someMerchantId", "someUser", "somePassword"); throws
expected once, never invoked: authenticationObserver.notifyUserNotFound(); returns a default value
allowed, already invoked 1 time: notificationHtmlNode.toHtml(); returns "someHtml"
expected once, already invoked 1 time: notificationHtmlNode.setError("Login fallito"); returns a default value
expected once, already invoked 1 time: webResponse.writeHtml(a string containing "someHtml"); returns a default value

Ora, non è impossibile capire cosa non va ma è estremamente difficile.

[Test] How to sort junit tests by execution time


Today with my colleague Luca Pucacco i've searched a method to detect which (junit) test are slow without any known or reasonable motivation. When you run a junit suite with eclipse you can see the execution time but you are not able to see an order.
So:
  1. export Test run with the gui; let's call this file SuiteOutput.xml
  2. Open a shell and write:
  3. cat SuiteOutput.xml | grep "<testsuite" | sed 's/\(.*time="\)\([^"]*\)\(.*\)/\2\1\2\3/' | sort -rg

With cat SuiteOutput.xml | grep "<testsuite" you are simply extracting the interesting rows
With sed s/..../../ we are going to substitute something. In details. Let's assume a row is in the form of:
<testsuite name="xxxTest" time="10.0">
The first group
(.*time="\) is the first part of the row ( <testsuite name="xxxTest" time=" )
The second group
([^"]*\) is all before the " character ( the 10.0 string)
The last group is the rest ( ">)

The final row contains as the first information the execution time. The last operation is sorting these data by evaluating it like a number and not like a string.


[Test] how to measure the execution time of Fitnesse tests


We are using Fitnesse as our current acceptance testing framework.
1/3 of our tests are built upon a fixture that uses Selenium.

Selenium is the worst test framework i never known. It's hard to write, hard to maintain, it's hard to read and, most of all, is SLOW, absolutely slow.

It's a long time that i wish to know how much time our selenium tests needs to be run but fitnesse doesn't say this information.

The solution is to call via http get the url with the instruction to run your suite and measure its execution time. Then i move all selenium tests in a dedicated suite.

And here you can see:

time wget http://localhost:8181/XXXProject.SeleniumSuite?suite

real 6m56.227s (!)

God save us

[Test] Parameterized tests

I never used a not-well-known functionality of junit 4. I mean: the Parameterized tests.
Every time i extract a method called "verifyXXXwith(parameter)" and i create a lot of small test case.
Reading this article: http://www.testearly.com/2007/04/13/take-heed-of-mixing-junit-4s-parameterized-tests/ i think my solution is the best.
What's your feedback?

[Performance] Analyzing database performance



Ok, it's time to release your software product in a production environment !
You need to analyze your database and collect some data, just to know what are the most problematic queries.
I read this interesting article by Tom Copeland
but it's pretty old.

Let's follow these instructions:

your dabatase should log all the queries executed. My db is postgreSQL 8.3, so:
  • sudo vim /etc/postgresql/8.3/main/postgresql.conf and edit:
log_statement = all
syslog_facility = 'LOCAL0'
syslog_ident = 'postgres'
  • sudo /etc/init.d/postgresql-8.3 reload
  • cp /var/log/messages log.txt
  • (i start to clear my file with this: grep -v "CET DETAIL" log.txt > log.txt)
  • grep select log.txt | cut -d ' ' -f 13- | sort | uniq -c | sort -nr | less (execute it without some pipe to understanding the meaning)

Now you know what are the most famous queries.
My suggestion is to start from here, adding custom data in used tables and executing the first two or three queries found before.

[Test] The fifth wicket secret

Some time ago my coworker Alessandro Novarini had explored a way to test ajax events inside wicket tests. Look here !

Now you can explore another secret.
With this method you can see ALL the wicket paths you need to provide in your tests:


 public void printAllComponentPaths() {
    Page p = tester.getLastRenderedPage();
    p.visitChildren(new IVisitor() {

       @Override
       public Object component(Component component) {
          System.out.println(component.getPath());
          return null;
       }
    });
 }

[Test] Terzo (e ultimo) segreto di wicket



System.out.println(tester.getServletResponse().getDocument());

[Test] Secondo segreto di wicket


Dopo il primo segreto, hai aperto le porte del secondo segreto per testare wicket:










DropDownChoice yourDropDown = (DropDownChoice) tester.getComponentFromLastRenderedPage(wicketPath);

assertEquals(expected, yourDropDown.getModelObject());

[Test] Primo segreto di wicket



Aziona il tuo wicket tester:
tester.startPage(YourPage.class);

e poi recupera dalla root la tipologia di ogni componente introdotta:
  1. tester.assertComponent("form", Form.class) -> barra verde
  2. tester.assertComponent("form:table", ...) -> barra verde
  3. tester.assertComponent("form:table:rows", ...) -> barra verde
  4. .... -> barra verde
  5. tester.assertComponent("form:table:rows:1:cells:2:cell:select", DropDownChoice.class); -> barra verde