I. Introduction▲
Afin d'automatiser les tests de services web, par exemple pour l'intégration continue, il est tout à fait possible de lancer totalement ou partiellement un projet SoapUI à partir de Junit. Le fichier de projet SoapUI étant normalisé et décrivant uniquement les tests, il peut fonctionner aussi bien dans Junit sans qu'une modification du fichier de projet ne soit nécessaire.
Cet article propose de décrire trois méthodes pour exécuter les tests d'un projet SoapUI avec JUnit :
- une méthode simple permettant de lancer facilement tous les tests d'un projet SoapUI, sans mise en échec possible du test JUnit, les résultats étant indiqués dans les logs ;
- une méthode intermédiaire permettant un meilleur contrôle des tests ainsi que leur automatisation pour l'intégration continue ;
- une méthode avancée qui est une optimisation de la méthode intermédiaire permettant d'exécuter un test JUnit par cas de test SoapUI et faciliter ainsi leur identification en cas d'échec.
On suppose que le lecteur connaît déjà SoapUI, ce document s'appuie sur la version 4.5.3.
II. Généralités sur le lancement de tests à partir du code▲
Les bibliothèques de SoapUI proposent un certain nombre de classes permettant l'intégration de ses tests dans Junit.
Ces classes représentent différents lanceurs de tests, communément appelés « runner », permettant d'exécuter les tests d'un projet SoapUI directement à partir du code. Plus généralement, un « runner » est une instance de classe permettant d'exécuter des tests.
Par exemple JUnit possède son propre « runner » (org.junit.runner.JUnitCore) pour exécuter des tests à partir du code. En voici une implémentation :
2.
3.
4.
5.
6.
7.
public
class
Main {
public
static
void
main
(
String[] args) {
JUnitCore runner =
new
JUnitCore
(
);
runner.addListener
(
new
TextListener
(
System.out));
runner.run
(
MaClasseDeTest.class
);
}
}
Dans l'exemple ci-dessus, le lanceur de test est configuré pour exécuter tous les tests de la classe MaClasseDeTest et afficher leur résultat sur la sortie standard.
III. Mise en place▲
III-A. Ajout des bibliothèques au projet▲
Afin de pouvoir exécuter des tests de services web avec SoapUI à partir de JUnit, les bibliothèques nécessaires doivent être ajoutées au projet. En l'occurrence, tous les jars des répertoires bin/ et lib/ situés dans le dossier d'installation de SoapUI sont à ajouter manuellement dans le Java Build Path ou avec Maven en ajoutant les éléments suivants au pom.xml :
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.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
<repositories>
<repository>
<id>
eviware</id>
<url>
http://www.soapui.org/repository/maven2</url>
</repository>
</repositories>
...
<dependencies>
...
<dependency>
<groupId>
commons-lang</groupId>
<artifactId>
commons-lang<
/artifactId>
<version>
2.4</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
commons-beanutils</groupId>
<artifactId>
commons-beanutils</artifactId>
<version>
1.7.0</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
commons-collections</groupId>
<artifactId>
commons-collections</artifactId>
<version>
3.2.1</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
1.2.8</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
commons-cli</groupId>
<artifactId>
commons-cli</artifactId>
<version>
1.0</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
saxon</groupId>
<artifactId>
saxon-dom</artifactId>
<version>
9.1.0.8j</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
javax.jms</groupId>
<artifactId>
jms</artifactId>
<version>
1.1</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
javax.mail</groupId>
<artifactId>
mail</artifactId>
<version>
1.4</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
org.mortbay.jetty</groupId>
<artifactId>
servlet-api</artifactId>
<version>
2.5-20081211</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
wsdl4j</groupId>
<artifactId>
wsdl4j</artifactId>
<version>
1.6.2</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
eviware</groupId>
<artifactId>
soapui</artifactId>
<version>
4.5.1</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
eviware</groupId>
<artifactId>
soapui-xmlbeans</artifactId>
<version>
4.5.1</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
xmlbeans</groupId>
<artifactId>
xbean_xpath</artifactId>
<version>
2.4.0</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
xmlbeans</groupId>
<artifactId>
xbean</artifactId>
<version>
fixed-2.4.0</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
org.apache.httpcomponents</groupId>
<artifactId>
httpclient</artifactId>
<version>
4.1.1</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
fife</groupId>
<artifactId>
rsyntaxtextarea</artifactId>
<version>
2.0.1</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
xerces</groupId>
<artifactId>
xercesImpl</artifactId>
<version>
2.9.1</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
bouncycastle</groupId>
<artifactId>
bcprov-jdk15</artifactId>
<version>
144</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
xmlunit</groupId>
<artifactId>
xmlunit</artifactId>
<version>
1.2</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
amf</groupId>
<artifactId>
flex-messaging-common</artifactId>
<version>
1.0</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
amf</groupId>
<artifactId>
flex-messaging-core</artifactId>
<version>
1.0</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
eviware</groupId>
<artifactId>
soap-xmlbeans</artifactId>
<version>
1.2</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
saxon</groupId>
<artifactId>
saxon</artifactId>
<version>
9.1.0.8j</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
jetty</groupId>
<artifactId>
jetty</artifactId>
<version>
6.1.26</version>
<scope>
test</scope>
</dependency>
<dependency>
<groupId>
jetty</groupId>
<artifactId>
jetty-util</artifactId>
<version>
6.1.26</version>
<scope>
test</scope>
</dependency>
</dependencies>
III-B. Le fichier de projet SoapUI▲
Enregistrons le fichier de projet SoapUI dans notre projet Java. Une bonne pratique consiste à le placer dans un répertoire resources/ correspondant à l'arborescence suivante :
RACINE_DU_PROJET/
src/
test/
resources/
Dans le cas d'un projet Maven, elle serait la suivante :
module/
src/
main/
java/
test/
java/
resources/
Les exemples de cet article utilisent une adresse absolue pour définir le projet SoapUI. Cependant, on peut aussi spécifier une adresse relative, pour cela il est nécessaire de préciser son répertoire de base dans les options du projet SoapUI :
Enfin la méthode System.out.println() des exemples est naturellement à remplacer par celle de votre logger, par exemple log4j.
III-C. Méthode simple▲
Cette méthode permet d'exécuter facilement toutes les suites de tests d'un projet SoapUI sans mise en échec du test JUnit, les résultats sont alors indiqués dans les logs.
En voici l'implémentation :
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Test
public
void
test
(
) throws
Exception {
SoapUITestCaseRunner testCaseRunner =
new
SoapUITestCaseRunner
(
);
testCaseRunner.setProjectFile
(
"D:/Exemple/ExempleWS/test/resources/Exemple-soapui-project.xml"
);
try
{
testCaseRunner.run
(
);
}
catch
(
Exception e) {
e.printStackTrace
(
);
}
}
Cette méthode n'a pas directement d'utilité dans le cadre d'une automatisation des tests pour l'intégration continue. En effet, le test JUnit n'échoue jamais et l'identification des cas de tests SoapUI échoués ne se fait que dans les logs. Cependant, cette méthode peut être utile pour le développeur souhaitant vérifier rapidement et ponctuellement l'impact des modifications.
III-D. Méthode intermédiaire▲
Cette méthode propose de mieux contrôler le signalement d'erreurs et la mise en échec du test JUnit, le cas échéant, via sa méthode assertEquals. Elle permet ainsi de détecter automatiquement une régression dans le processus d'intégration continue.
En voici l'implémentation :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
@Test
public
void
JunitTest1
(
)
throws
XmlException, IOException, SoapUIException {
// Créer une nouvelle instance de WsdlProject en spécifiant le chemin absolu du projet SoapUI
WsdlProject project =
new
WsdlProject
(
"D:/Exemple/ExempleWS/test/resources/Exemple-soapui-project.xml"
);
// Récupère tous les TestSuites inclus dans le projet SoapUI
List<
TestSuite>
testSuiteList =
project.getTestSuiteList
(
);
// Itération sur tous les TestSuites du projet
for
(
TestSuite ts : testSuiteList) {
System.out.println
(
"******Running "
+
ts.getName
(
) +
"***********"
);
// Récupère tous les TestCases d'une TestSuite
List<
TestCase>
testCaseList =
ts.getTestCaseList
(
);
// Itération sur tous les TestCases d'un TestSuite particulier
for
(
TestCase testcase : testCaseList) {
System.out.println
(
"******Running "
+
testcase.getName
(
) +
"***********"
);
// Exécute le TestCase spécifié
TestRunner testCaseRunner =
testcase.run
(
new
PropertiesMap
(
), false
);
// Vérifie si le TestCase s'est terminé correctement
// ou s'il a échoué à cause d'un échec d'assertion
assertEquals
(
TestRunner.Status.FINISHED, testCaseRunner.getStatus
(
));
}
}
}
Cet exemple généraliste permet d'exécuter l'ensemble des suites de tests du projet SoapUI spécifié. Il est également possible d'exécuter seulement une suite de tests particulière ou encore un cas de test particulier. Il suffit alors de remplacer la boucle for de l'exemple souhaitée par la méthode appropriée :
Pour récupérer une suite de tests à partir de son nom (spécifié dans le projet SoapUI) :
TestSuite testSuite =
project.getTestSuiteByName
(
"NomDeLaTestSuite"
);
Pour récupérer un cas de test à partir de son nom (spécifié dans le projet SoapUI) :
TestCase testCase =
testSuite.getTestCaseByName
(
"NomDuTestCase"
);
III-E. Méthode avancée▲
Dans la méthode intermédiaire, la totalité des tests SoapUI sont effectués dans un seul test JUnit. Cette méthode ne permet donc pas d'identifier automatiquement le cas de test SoapUI de la méthode de service web mis en cause. Cette façon de faire n'est donc pas totalement satisfaisante. Afin de faire correspondre un cas de test SoapUI avec un test JUnit, on va utiliser les tests JUnit paramétrés (JUnit Parameterized).
Les tests paramétrés permettent d'exécuter une méthode de tests avec plusieurs jeux de données, ici différents cas de test SoapUI. Pour cela, on ajoute l'annotation @RunWith(Parameterized.class) devant la classe, puis l'annotation @Parameters devant le jeu de données.
En voici l'implémentation :
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.
@RunWith
(
Parameterized.class
)
public
class
JunitTest7 {
private
String testCaseName;
private
static
String soapuiProjectName =
"D:/Exemple/ExempleWS/test/resources/Exemple-soapui-project.xml"
;
public
JunitTest7
(
String testCaseName) {
this
.testCaseName =
testCaseName;
}
@Parameters
(
name=
"{0}"
)
public
static
Collection<
String[] >
getTestCases
(
) throws
XmlException, IOException, SoapUIException {
final
ArrayList<
String[]>
testCases =
new
ArrayList<
String[]>(
);
WsdlProject soapuiProject =
new
WsdlProject
(
soapuiProjectName);
WsdlTestSuite wsdlTestSuite =
soapuiProject.getTestSuiteByName
(
"TNR2 TestSuite"
);
List<
TestCase>
testCaseStrings =
wsdlTestSuite.getTestCaseList
(
);
for
(
TestCase ts : testCaseStrings) {
if
(!
ts.isDisabled
(
)) {
testCases.add
(
new
String[] {
ts.getName
(
) }
);
}
}
return
testCases;
}
@Test
public
void
testSoapUITestCase
(
) throws
XmlException, IOException, SoapUIException {
System.out.println
(
"Nom du cas de test SoapUI : "
+
testCaseName);
assertTrue
(
true
);
assertTrue
(
runSoapUITestCase
(
this
.testCaseName));
}
public
static
boolean
runSoapUITestCase
(
String testCase) throws
XmlException, IOException, SoapUIException {
TestRunner.Status exitValue =
TestRunner.Status.INITIALIZED;
WsdlProject soapuiProject =
new
WsdlProject
(
soapuiProjectName);
WsdlTestSuite testSuite =
soapuiProject.getTestSuiteByName
(
"TNR2 TestSuite"
);
if
(
testSuite ==
null
) {
System.err.println
(
"runner soapUI, la suite de test est null : "
+
testSuite);
return
false
;
}
WsdlTestCase soapuiTestCase =
testSuite.getTestCaseByName
(
testCase);
if
(
soapuiTestCase ==
null
) {
System.err.println
(
"runner soapUI, le cas de test est null : "
+
testCase);
return
false
;
}
soapuiTestCase.setDiscardOkResults
(
true
);
WsdlTestCaseRunner runner =
soapuiTestCase.run
(
new
PropertiesMap
(
), false
);
exitValue =
runner.getStatus
(
);
System.out.println
(
"cas de test soapUI terminé ('"
+
testSuite +
"':'"
+
testCase +
"') : "
+
exitValue);
if
(
exitValue ==
TestRunner.Status.FINISHED) {
return
true
;
}
else
{
return
false
;
}
}
}
Le test JUnit testSoapUITestCase() est alors exécuté autant de fois qu'il y a de cas de test SoapUI.
Le paramètre name = "{0}" de l'annotation @Parameters permet d'afficher le nom du cas de test SoapUI au lieu d'un simple numéro d'incrément, ce qui permet de l'identifier directement. Ce paramètre est supporté à partir de la version 4.11 de JUnit. Vous trouverez plus d'informations sur le sujet au chapitre « Identify Individual test cases » de la documentation de Junit.
IV. Conclusion et remerciements▲
Grâce au contenu de cet article, le lecteur est désormais en mesure d'automatiser totalement les tests de services web jusque dans un processus d'intégration continue. Une régression sur un cas de test de service web est alors automatiquement identifiée.
SoapUI a l'avantage d'être un outil suffisamment simple d'utilisation pour être manipulé par un personnel non développeur. Aussi, l'écriture des tests de services web peut être déléguée.
Je tiens à remercier milkoseck pour sa relecture attentive de cet article et Mickaël Baron pour la mise au gabarit.