I. Présentation de ContiPerf▲
Bonne nouvelle, cela existe et porte le nom de ContiPerf.
ContiPerf se présente comme une surcouche à Junit 4 pour les tests de performance, avec toutes ces facilités et tous ces avantages :
- configuration par annotations ;
- intégration avec les IDE supportant Junit 4 comme Eclipse, Netbeans… ;
- intégration avec Apache Maven ;
- aucune dépendance à part JUnit ;
- export des résultats au format CSV et HTML ;
- regroupement des tests en Test Suites.
Attention, ContiPerf ne remplace pas une bonne méthode de test de performance (avoir des résultats reproductibles, avoir les bons jeux de test…) et en particulier les tests de charge.
De plus, il n'est pas encore adapté au microbenchmarking.
Pour plus d'informations, le site officiel est très complet.
Avant de commencer à écrire des tests unitaires de performance, regardons comment intégrer ContiPerf à notre environnement de développement.
II. Intégration dans un IDE▲
Afin d'intégrer ContiPerf à votre IDE, il suffit d'ajouter la librairie contiperf.jar dans le Classpath.
Puis afin de lancer le test, on fait comme pour JUnit.
Par exemple avec Netbeans :
III. Intégration avec Maven▲
L'intégration à Maven est tout aussi simple.
2.
3.
4.
5.
6.
<dependency>
<groupId>org.junit </groupId>
<artifactId>com.springsource.org.junit</artifactId>
<version>4.7.0</version>
<scope>test</scope>
</dependency>
IV. Go pour la pratique▲
Maintenant que vous êtes entièrement convaincu (enfin j'espère !), regardons d'un peu plus près comment écrire un test de performance ContiPerf.
Pour ceux qui n'ont pas l'habitude d'utiliser JUnit, je leur conseille de l'étudier avant.
V. Écrire un test ContiPerf▲
Pour un test, il nous faut :
- un objet ContiPerfRule et son annotation @Rule ;
- un protocole de test (nombre d'itération, nombre de thread, durée du test) défini à l'aide de l'annotation @PerfTest ;
- des critères d'acceptance (moyenne / médian / max / percentiles / durée totale / throughput / temps de réponse) définis à l'aide de l'annotation @Required ;
- du code métier.
Le tout aura la forme suivante :
Si l'on veut que ContiPerf génère aussi les rapports, il faut remplacer la ligne :
2.
@
Rule
public
ContiPerfRule i =
new
ContiPerfRule
(
);
par :
2.
3.
4.
5.
6.
@
Rule
public
ContiPerfRule rule =
new
ContiPerfRule
(
new
HtmlReportModule
(
),
new
CSVSummaryReportModule
(
),
new
CSVInvocationReportModule
(
),
new
CSVLatencyReportModule
(
));
VI. Exemple 1 : maximum 15 ms avec une moyenne inférieure à 10 ms▲
Nous allons écrire des tests ContiPerf pour l'application Spring PetClinic.
Dans un premier temps, nous allons tester la class org.springframework.samples.petclinic.Pet
Testons la création (new Pet) de 100 chiens prénommés Medor (setName).
2.
3.
4.
5.
6.
7.
8.
9.
10.
@
Rule
public
ContiPerfRule i =
new
ContiPerfRule
(
);
@
Test
@
PerfTest
(
invocations =
100
, threads =
100
)
@
Required
(
max =
15
, average =
10
)
public
void
testsetName
(
) {
Pet monChien =
new
Pet
(
);
monChien.setName
(
"
Medor
"
);
}
Dans ce test, on crée 100 chiens (invocations = 100) en parallèle (1 par thread) et on veut qu'une création dure au maximum 15 ms (max) avec une moyenne (average) inférieure à 10 ms.
VII. Exemple 2 : maximum 15 ms avec une moyenne inférieure à 10 ms▲
Reprenons le même exemple, mais cette fois-ci pour certaines raisons (par exemple un pool), on sait qu'il n'y aura jamais plus de 10 créations de chiens en même temps.
L'exemple devient :
2.
3.
4.
5.
6.
7.
8.
9.
10.
@
Rule
public
ContiPerfRule i =
new
ContiPerfRule
(
);
@
Test
@
PerfTest
(
invocations =
100
, threads =
10
)
@
Required
(
max =
15
, average =
10
)
public
void
testsetName
(
) {
Pet monChien =
new
Pet
(
);
monChien.setName
(
"
Medor
"
);
}
Ici chaque thread va créer 10 chiens (invocations/threads) pour un total de 100 chiens.
VIII. Exemple 3 : throughput au minimum de 400▲
Toujours avec le même exemple, mais maintenant on veut que le throughput soit au minimum de 400 invocations par seconde.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@
Rule
public
ContiPerfRule i =
new
ContiPerfRule
(
);
@
Test
@
PerfTest
(
duration =
100000
, threads =
10
)
@
Required
(
throughput =
400
)
public
void
testsetName
(
) {
Pet monChien =
new
Pet
(
);
monChien.setName
(
"
Medor
"
);
}
Ici le test dure 100s.
IX. Exemple 4 : percentil▲
Enfin, pour ceux qui trouvent que l'utilisation du percentil est plus intéressante que la moyenne, il suffit de modifier le test de l'exemple 2 en remplaçant average par percentile99.
Par exemple, si on veut que 99 % des exécutions ne durent pas plus de 6 ms, l'exemple devient :
2.
3.
4.
5.
6.
7.
8.
9.
10.
@
Rule
public
ContiPerfRule i =
new
ContiPerfRule
(
);
@
Test
@
PerfTest
(
invocations =
100
, threads =
10
)
@
Required
(
max =
10
, percentile99 =
6
)
public
void
testsetName
(
) {
Pet monChien =
new
Pet
(
);
monChien.setName
(
"
Medor
"
);
}
X. Exemple 5 : mutualisation des critères d'acceptance▲
Continuons avec la class org.springframework.samples.petclinic.Pet.
Ajoutons le test de la fonction setType :
2.
3.
4.
5.
6.
7.
8.
9.
@
Test
@
PerfTest
(
invocations =
100
, threads =
10
)
@
Required
(
max =
10
, percentile99 =
5
)
public
void
testsetType
(
) {
PetType bulldog =
new
PetType
(
);
bulldog.setName
(
"
bulldog
"
);
Pet monChien =
new
Pet
(
);
monChien.setType
(
bulldog);
}
Lorsqu'on a plusieurs tests pour une classe, on peut regrouper le protocole de test (@PerfTest) et les critères d'acceptance (@Required) au niveau de la classe et plus au niveau des fonctions.
Par exemple, pour mettre en commun notre protocole de test pour les deux tests ContiPerf, il suffit d'écrire le test de la manière suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
@
PerfTest
(
invocations =
100
, threads =
10
)
public
class
PetTestsPerf {
@
Rule
public
ContiPerfRule i =
new
ContiPerfRule
(
);
public
PetTestsPerf
(
) {
}
@
Test
@
Required
(
max =
10
, percentile99 =
6
)
public
void
testsetName
(
) {
Pet monChien =
new
Pet
(
);
monChien.setName
(
"
Medor
"
);
}
@
Test
@
Required
(
max =
10
, percentile99 =
5
)
public
void
testsetType
(
) {
PetType bulldog =
new
PetType
(
);
bulldog.setName
(
"
bulldog
"
);
Pet monChien =
new
Pet
(
);
monChien.setType
(
bulldog);
}
}
XI. Exemple 6▲
Poursuivons avec un test plus complexe de la classe org.springframework.samples.petclinic.web.VisitsAtomView.
Pour cela, nous allons reprendre le test unitaire qui existe déjà et le transformer en test ContiPerf.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
package
org.springframework.samples.petclinic.web;
import
com.sun.syndication.feed.atom.Entry;
import
com.sun.syndication.feed.atom.Feed;
import
java.util.*;
import
org.databene.contiperf.PerfTest;
import
org.databene.contiperf.Required;
import
org.junit.Before;
import
org.junit.Test;
import
org.springframework.samples.petclinic.Pet;
import
org.springframework.samples.petclinic.PetType;
import
org.springframework.samples.petclinic.Visit;
@
PerfTest
(
invocations =
100
, threads =
10
)
public
class
VisitsAtomViewTestsPerf {
@
Rule
public
ContiPerfRule i =
new
ContiPerfRule
(
);
private
VisitsAtomView visitView;
private
Map model;
private
Feed feed;
@
Before
public
void
setUp
(
) {
visitView =
new
VisitsAtomView
(
);
PetType dog =
new
PetType
(
);
dog.setName
(
"
dog
"
);
Pet bello =
new
Pet
(
);
bello.setName
(
"
Bello
"
);
bello.setType
(
dog);
Visit belloVisit =
new
Visit
(
);
belloVisit.setPet
(
bello);
belloVisit.setDate
(
new
Date
(
2009
, 0
, 1
));
belloVisit.setDescription
(
"
Bello
visit
"
);
Pet wodan =
new
Pet
(
);
wodan.setName
(
"
Wodan
"
);
wodan.setType
(
dog);
Visit wodanVisit =
new
Visit
(
);
wodanVisit.setPet
(
wodan);
wodanVisit.setDate
(
new
Date
(
2009
, 0
, 2
));
wodanVisit.setDescription
(
"
Wodan
visit
"
);
List visits =
new
ArrayList
(
);
visits.add
(
belloVisit);
visits.add
(
wodanVisit);
model =
new
HashMap
(
);
model.put
(
"
visits
"
, visits);
feed =
new
Feed
(
);
}
@
Test
@
Required
(
max =
30
, percentile99 =
7
)
public
void
buildFeedMetadata
(
) {
visitView.buildFeedMetadata
(
model, feed, null
);
}
@
Test
@
Required
(
max =
80
, percentile99 =
42
)
public
void
buildFeedEntries
(
) throws
Exception {
List entries =
visitView.buildFeedEntries
(
model, null
, null
);
}
}
XII. Performance Test Suites▲
Bien sûr, on peut regrouper plusieurs tests ContiPerf dans une Test Suite.
Pour cela, il faut utiliser ces deux annotations :
@RunWith(ContiPerfSuiteRunner.class)
;
@Suite.SuiteClasses({liste des classes à tester})
.
Dans notre exemple on aura :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
package
org.springframework.samples.petclinic;
import
org.databene.contiperf.junit.ContiPerfSuiteRunner;
import
org.junit.After;
import
org.junit.AfterClass;
import
org.junit.Before;
import
org.junit.BeforeClass;
import
org.junit.runner.RunWith;
import
org.junit.runners.Suite;
@
RunWith
(
ContiPerfSuiteRunner.class
)
@
Suite
.SuiteClasses
(
{
org.springframework.samples.petclinic.PetTestsPerf.class
, org.springframework.samples.petclinic.OwnerTestsPerf.class
}
)
public
class
PetclinicTestSuite {
@
BeforeClass
public
static
void
setUpClass
(
) throws
Exception {
}
@
AfterClass
public
static
void
tearDownClass
(
) throws
Exception {
}
@
Before
public
void
setUp
(
) throws
Exception {
}
@
After
public
void
tearDown
(
) throws
Exception {
}
}
XIII. Rapports▲
Penchons-nous un peu plus sur les rapports générés par ContiPerf.
Avec la version 2.0.1, deux formats d'exports sont possibles.
- Le format HTML
Pour avoir ce rapport (fichier index.html), il faut utiliser public ContiPerfRule i = new ContiPerfRule(); ou public ContiPerfRule i = new ContiPerfRule(new HtmlReportModule());
- Le format CSV
Comme on peut le voir, on génère jusqu'à trois fichiers CSV.
summary.csv.
- Fichier contenant le résumé du test ContiPerf
2.
serviceId,startTime,duration,invocations,min,average,median,90%,95%,99%,max
org.springframework.samples.petclinic.OwnerTestsPerf.testHasPet,1329047753040,78,1000,0,0.8,0,0,0,24,6
Pour l'obtenir, il faut utiliser public ContiPerfRule i = new ContiPerfRule(new CSVSummaryReportModule());
org.springframework.samples.petclinic.OwnerTestsPerf.testHasPet.inv.csv
- Fichier contenant le temps de réponse de chaque itération
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
latency,startTimeNanos
67,9452032779934
63,9452036182461
0,9452100152044
0,9452100162800
0,9452100186266
0,9452100197511
0,9452100212177
0,9452100223492
62,9452037698296
62,9452037612671
64,9452035922372
67,9452033027032
59,9452040662290
59,9452040958767
59,9452041381935
61,9452038566633
Pour l'obtenir, il faut utiliser public ContiPerfRule i = new ContiPerfRule(new CSVInvocationReportModule());
org.springframework.samples.petclinic.OwnerTestsPerf.testHasPet.stat.csv
- Fichier contenant la répartition des temps de réponse
Par exemple, ici on voit qu'il y a eu une réponse avec un temps de réponse de 9 ms, neuf cent soixante-dix-neuf réponses avec un temps de réponse de 0 ms…
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
latency,sampleCount
0,979
1,0
2,2
3,0
4,2
5,0
6,0
7,0
8,0
9,1
10,0
11,0
Pour l'obtenir, il faut utiliser public ContiPerfRule i = new ContiPerfRule(new CSVLatencyReportModule());
Comme on peut le voir, on peut faire des choses intéressantes avec les rapports standards (surtout avec les fichiers CSV). Si cela n'est pas suffisant, ContiPerf étant open source et libre, on peut ajouter facilement enrichir des rapports et des métrics.
Supposons que nous voulions ajouter une metric dans le rapport HTML.
Pour cela, nous devons modifier/utiliser trois fichiers sources :
- HtmlReportModule : code générant le rapport HTML ;
- LatencyCounter : code calculant les metrics de base ;
- PerformanceRequirement : code contenant les critères d'acceptance.
Dans le cas de mon besoin initial, les metrics présentés suffisent largement. Cela dit, je reste curieux de vos remarques et des évolutions que vous aurez pu opérer en fonction des vôtres.
XIV. Conclusion ▲
On retient que ContiPerf est un utilitaire de test léger qui permet à l'utilisateur d'exploiter facilement JUnit 4 en faisant de tests de performance ; par exemple pour tester les performances en continu.