Rosario Turco
SoapUI è uno strumento Java Open Source estremamente utile e semplice, specie in ambito architetture
SOA e utilizzabile su piattaforme qualsiasi (Windows/Unix/Linux etc) grazie alla neutralità di Java ed adatto
per:
test di servizi web (http/https/jms), per creare stub di servizi offerti da un server o per creare client
di servizi;
test di connettività dei servizi e scambio WSDL;
verifica di correttezza;
test di un servizio sviluppato che ha bisogno di altri servizi esterni da simulare (to mock external
service);
risparmio di memoria in ambienti di test, poiché gli stub ottenibili pesano pochissimo e possono
essere messi su PC;
automatizzazione dei i test, a partire dalle WSDL;
predisposizione di un catena di test;
predisposizione di un Load Test.
Impiego rapido in Metodologie Agili e di Continuos Testing
Il vantaggio che fornisce è quello di rendersi indipendenti dai sistemi, che offrono i servizi e di disporre di
un framework open source gratuito e robusto, che permette rapidamente di testare quanto sviluppato, sia
a livello di test unitario che di test di sistema.
Ci soffermeremo soprattutto sul come utilizzare in questo caso SOAPUI, ovviamente iniziate prima a
scaricarvi l’ultima versione, che attualmente è 3.6.1. Vi consiglio di unzippare il tutto sotto C:\Tools\soapui-
3.6.1. In Tools io di solito metto tutti i prodotti Java necessari ai miei sviluppi. Da C:\Tools\ soapui-3.6.1\bin
create uno shortcut sul desktop del file soapui.bat ed ora cliccate sul file batch e siete pronti ad iniziare.
Nell'esempio di seguito viene descritto come disporre SOAPUI per testare dei servizi. Faremo due scenari di
due tipi di test diversi.
Le WSDL e gli XSD relativi sono disponibili in APPENDICE per fare un semplice “past & cut” per le prove di
apprendimento. Aprirle con un editor XML/WSDL adeguato in una cartella del desktop.
Innanzitutto cambiare nella WSDL, in fondo nel binding, cambiare l’IP con quello del proprio PC e
controllare che sul sistema Windows o Unix (dove cioè è presente SOAPUI running) non fosse già in uso la
porta 8088 che è quella di default usata da SOAPUI, altrimenti cambiarla; ad esempio sul Windows da
comandi DOS con nestat –a | find “<porta>” oppure su Unix con netstat –a | grep <porta>.
1
Ovviamente il servizio chiamante sotto test deve essere configurato per chiamare l’IP del PC e la porta
prescelta.
Creazione Progetto
Su SOPAUI nel menu selezionare "File" → "Nuovo soapUI Project". Impostare il nome del progetto, ad
esempio "PrepaidMobileStub".
Creazione MockService
A questo punto occorre creare il mock service, poggiando il puntatore del mouse su
PrepaidMobileNumberInformationQueryBinding e con tasto destro fare Generate Mock Service e accettare
il nome proposto, mentre in path occorre mettere ciò che contiene la WSDL cioè:
/IB/services/PrepaidMobileNumberInformationQuery-v1
Spesso su PC sia per la request che per per la response (vedi MockService Properties di entrambi), occorre
mettere l’alias con cui è visto il PC e non l’IP ad esempio:
http://NBW72006184478:8088/IB/services/PrepaidMobileNumberInformationQuery-v1
Se occorresse cambiare (se il test non funziona) basta farlo con doppio click su Request che apre l’editor
per lanciare il test e una location per modificare la url (usare Edit current).
Valorizzate la parte XML della request (doppio click su Request a sinistra) e della response (analogamente)
riempendo dove vi sono i ?.
A questo punto si può fare sul Mock Service tasto destro e Start Minimized.
Per fare un test che è tutto OK mandate una request valorizzata (triangolo verde in alto) e vedrete sempre
nell’editor della request (a destra, l’altra porzione di finestra la risposta) . A questo punto il vostro Mock
Service è uno STUB pronto a ricevere richieste dal servizio sviluppato reale che dovete testare.
Suggerimento
Salvate sempre il progetto e accumulate i file di test da fare (le request) con un Test case, questo vi
permetterà di attrezzarvi per le regressioni. Suggerimento denominate le request secondo il caso di test
della vostra progettazione Request-OK-12, Request-NOK-13 etc e alla fine l’obiettivo è di predisporre in
sequenza tutti i test. E’ possibile anche predisporre il risultato atteso (lo vedremo nel prossimo esempio).
Ipotizziamo un servizio che calcola, ad esempio, il prezzo di un viaggio (TripPriceService) e per farlo, ad
esempio, sono invocati un servizio che restituisce i prezzi per le camere d'albergo (HotelPriceService) ed un
servizio che restituisce i prezzi dei soli voli (FlightPriceService).
2
Qua ipotizziamo che TripPriceService è un servizio da voi effettivamente sviluppato e deployato, mentre gli
altri due servizi sono servizi elementari esterni da richiamare da TripPriceService e da cui ottenere risposta
e sono quest’ultimi da sottoporre a mocking.
In sostanza il prezzo da pagare dipende dalla durata (o numero di giorni), dal numero di stanze, dal prezzo
per stanza, dal numero di adulti e il prezzo del volo da una partenza ad una destinazione.
Creare un progetto SOAPUI con un servizio reale da testare e non da esporre su SOAPUI
Nel menu selezionare "File" → "Nuovo soapUI Project". Impostare il nome del progetto, ad esempio "trip-
prezzo-servizio". Poiché il servizio TripPriceService è sviluppato e deployato (ad esempio su TIBCO) ed è
quello a cui siamo interessati per il test, mentri gli altri servizi saranno degli STUB, qua Initial WSDL occorre
fare l’invocazione:
"http://localhost:8080/trip-price-0.0.1-SNAPSHOT/webservices/TripPriceService?wsdl" .
Ora per i servizi che invoca TripPriceService e che dovremmo esporre come STUB su SOAPUI ipotizzeremo
che siano su porta 8088 di default di SOAPUI; però conviene sempre controllare che sul sistema Windows o
su Unix (dove cioè funziona SOAPUI) non fosse già in uso; ad esempio sul Windows da comandi DOS con
nestat –a | find “<porta>” oppure su Unix con netstat –a | grep <porta>
Ricordarsi ora che per i due servizi STUB occorre cambiare nella WSDL in fondo nel binding IP e/o porta di
dove è SOAPUI e analogamente occorre configurare TripPriceService per puntare ad essi.
3
Generare una suite di test
L'impostazione di un timeout per il test è una buona pratica, in modo che il test non rimanga in attesa
infinita, in caso qualcosa vada storto. A sinistra col tasto destro del mouse sul TestCase, fare clic su
Options. Impostare TestCase timeout in millisecondi, per esempio "5000". Fare clic su OK.
Fare doppio clic sulla richiesta "getTripPrice" Test e modificare la Request in modo da chiedere il prezzo dei
viaggi seguenti: da Berlino a Parigi, per 2 adulti, della durata di 3 giorni, una camera necessarie. Potete per
prova fare “past & cut” del messaggio successivo e incollarlo al posto della Request 1:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:trip="http://trip.price.service">
<soapenv:Header/>
<soapenv:Body>
<trip:getTripPrice>
<trip>
<adults>2</adults>
<duration>3</duration>
<from>Berlin</from>
<rooms>1</rooms>
<to>Paris</to>
</trip>
4
</trip:getTripPrice>
</soapenv:Body>
</soapenv:Envelope>
Fare clic su "Invia la richiesta di URL specificato endpoint" tasto (triangolo verde in alto alla finestra della
Request 1). Come primo risultato, otteniamo un errore SOAP, perché i servizi esterni di cui abbiamo
bisogno non sono disponibili.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
</soap:Fault>
</soap:Body>
</soap:Envelope>
Sul nome del progetto a sinistra fare ADD WSDL e Impostare il percorso WSDL di HotelPriceService.wsdl.
Fare clic su "OK". Come risultato, si aggiunge una nuova interfaccia per il progetto:
"HotelPriceServiceFacadeServiceSoapBinding".
Torniamo a getTripPrice Test Case. Tasto destro del mouse e selezionare "Aggiungi Step" -> "Mock
Response". Specificare un nome per il nuovo passaggio, per esempio "getRoomPrice Mock Response" e fare
clic su OK.
/external-services-0.0.1-SNAPSHOT/webservices/HotelPriceService
Porta 8088.
Fare clic su OK. Modificare la response generata, per restituire un prezzo, per esempio "55.0". Impostare la
fase di avvio della risposta mock (start step) nel "MockResponse Properties" a "getTripPrice". Così il servizio
mock inizierà ad ascoltare le richieste non appena la richiesta è getTripPrice.
Attivare una richiesta al servizio mock, così questo renderà più facile la configurazione dell’assertion. Fare
clic su triangolo verde nell'editor caso di test. Si dovrebbe vedere una richiesta nell'editor risposta mock.
Fare clic sulla scheda "Assertions". Cliccare sul pulsante "Add an Assertion for this item ". Selezionare
"Schema Compliance" e fare clic su OK. Controllare l'url definizione da convalidare e fare clic su OK.
L'assertion deve apparire come valido.
5
Ripetiamo con FlightPriceService
Fare clic destro sul project e selezionare "Aggiungi WSDL". Impostare il percorso WSDL
FlightPriceService.wsdl. Fare clic su OK. Come risultato, si aggiunge una nuova interfaccia per il progetto:
"FlightPriceServiceFacadeServiceSoapBinding". Sempre sul TestCase tasto destro del mouse e selezionare
"Aggiungi Step" -> "Mock Response". Specificare un nome per il nuovo passaggio, per esempio
"getFlightPrice Mock Response" e fare clic su OK. Selezionare l'operazione "getFlightPrice" di interfaccia
"FlightPriceServiceFacadeServiceSoapBinding". Impostare la porta, qui "8088".
Modifica la risposta generata, per restituire un prezzo, ad esempio, "49,99". Impostare la fase di avvio della
risposta mock nel "MockResponse Properties" a "getTripPrice". Così il servizio mock inizierà ad ascoltare le
richieste non appena la richiesta inviata è getTripPrice.
Avviare (triangolo verde) il Test Case. Si dovrebbe vedere una richiesta nell'editor risposta mock.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getFlightPrice xmlns:ns2="http://external.services/flight">
<from>Berlin</from>
<to>Paris</to>
</ns2:getFlightPrice>
</soap:Body>
</soap:Envelope>
Fare clic sulla scheda "asserzioni". Clicca sul pulsante "Add an assertion of this item". Selezionare "Schema
Compliance" e fare clic su OK.
Controllare l'url di definizione da convalidare e fare clic su OK. L'affermazione dovrebbe apparire come
valida.
Clicca sul pulsante "Add an assertion of this item". Selezionare "XPath Match" e fare clic su "OK", si aprirà la
finestra di configurazione XPath Match. Fare clic su "dichiarare" per generare automaticamente le
dichiarazioni
L'affermazione dovrebbe apparire come valido. Fare lo stesso per il campo "A" e controllare che sia Parigi.
6
Verificare la risposta
Eseguire il caso di test, ora dovrebbe avere successo. Aprire l'editor richiesta di test. Fare clic sulla scheda
"asserzioni". Aggiungere un "Schema Compliance" affermazione, per verificare lo schema di risposta.
Aggiungere un "Non SOAP Fault" affermazione, per verificare la risposta non è un errore SOAP. Aggiungere
un "SOAP Response" affermazione, per verificare la risposta è una risposta SOAP. Aggiungere un "XPath
Match" affermazione di controllare il prezzo .
XPath Expression
//ns2:getTripPriceResponse/return
(3 * 1 * 55 + 2 * 49.99) (3 * 1 * 55 + 2 * 49,99)
Conclusioni
Un ottimo strumento. Vanno affinate solo le vostre conoscenze su esso su come usarlo. Studiare a fondo il
tutorial messi a disposizione in [1][2].
[1] http://www.soapui.org
[2] http://www.soapui.org/JMS/getting-started.html
APPENDICE
SCENARIO A
WSDL_PrepaidMobileNumberInformationQuery_Concrete-v1.wsdl
PrepaidMobileNumberInformationQuery.xsd
8
<!-- getCreditInfo END -->
<!-- ComplexTypes START -->
<xs:complexType name="ProcessData">
<xs:sequence>
<xs:element name="returnCode" type="sm:noEmptyFieldType"/>
<xs:element name="returnDescription" type="sm:noEmptyFieldType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!-- ComplexTypes END -->
<!-- SimpleTypes START -->
<xs:simpleType name="noEmptyFieldType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="dateType">
<xs:restriction base="xs:date"/>
</xs:simpleType>
<!-- SimpleTypes END -->
</xs:schema>
PrepaidMobileNumberInformationQueryEntities.xsd
9
</xs:complexType>
<!-- Product END -->
<!-- BaseTypes START -->
<xs:complexType name="TimePeriod">
<xs:sequence>
<xs:element name="endDateTime" type="cst:dateTimeType"/>
</xs:sequence>
</xs:complexType>
<!-- BaseTypes END -->
<!-- Entities END -->
<!-- CommonTypes START -->
<xs:simpleType name="telephoneNumber">
<xs:annotation>
<xs:appinfo>
<bvi:primitiveType>
<bvi:className>TelephoneNumber</bvi:className>
</bvi:primitiveType>
</xs:appinfo>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:pattern value="\+?\d{1,}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="decimal">
<xs:annotation>
<xs:appinfo>
<bvi:primitiveType>
<bvi:className>Decimal</bvi:className>
</bvi:primitiveType>
</xs:appinfo>
</xs:annotation>
<xs:restriction base="xs:decimal"/>
</xs:simpleType>
<xs:simpleType name="dateTime">
<xs:annotation>
<xs:appinfo>
<bvi:primitiveType>
<bvi:className>DateTime</bvi:className>
</bvi:primitiveType>
</xs:appinfo>
</xs:annotation>
<xs:restriction base="xs:dateTime"/>
</xs:simpleType>
<!-- CommonTypes END -->
<!-- SimpleTypes START -->
<xs:simpleType name="telephoneNumberType">
<xs:restriction base="cst:telephoneNumber">
<xs:pattern value="\d{1,}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="decimalType">
<xs:restriction base="cst:decimal"/>
</xs:simpleType>
<xs:simpleType name="dateTimeType">
<xs:restriction base="cst:dateTime"/>
</xs:simpleType>
<xs:simpleType name="noCommonConstraintsType" final="#all">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<!-- SimpleTypes END -->
</xs:schema>
SOAPHeader_v1.1.xsd
10
// Created: 05/03/2009
// Modified 16/03/2010
// Developed by: Simone Avossa
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:h="http://telecomitalia.it/SOA/SOAP/SOAPHeader"
targetNamespace="http://telecomitalia.it/SOA/SOAP/SOAPHeader" elementFormDefault="qualified" version="1.1">
<!-- Start Types Definition -->
<xs:complexType name="HeaderType">
<xs:annotation>
<xs:documentation>Informazioni di contesto dell'invocazione del servizio</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="sourceSystem" type="h:sourceSystemType" minOccurs="0">
<xs:annotation>
<xs:documentation>Sistema da cui proviene la richiesta</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="interactionDate" type="h:interactionDateType" minOccurs="0">
<xs:annotation>
<xs:documentation>Data e Ora di invocazione del servizio</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="businessID" type="h:businessIDType" minOccurs="0">
<xs:annotation>
<xs:documentation>Identifica univocamente il processo di
business</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="messageID" type="h:messageIDType" minOccurs="0">
<xs:annotation>
<xs:documentation>Identifica il messaggio in maniera univoca</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="transactionID" type="h:transactionIDType" minOccurs="0">
<xs:annotation>
<xs:documentation>Identifica la transazione per gestire i ritorni
sincroni</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="dateType">
<xs:restriction base="xs:string">
<xs:pattern value="\d{4}-\d{2}-\d{2}"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="timeType">
<xs:restriction base="xs:string">
<xs:pattern value="\d{2}:\d{2}:\d{2}((Z)|(\.\d{1,}Z?)|((\+|-)\d{2}:\d{2}))?"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="businessIDType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="messageIDType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="sourceSystemType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="transactionIDType">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="interactionDateType">
<xs:sequence>
<xs:element name="Date" type="h:dateType">
<xs:annotation>
11
<xs:documentation>Per compatibilità con i diversi prodotti o librerie software (es.
Axis2 e BW) si è scelto di utilizzare il tipo string. La restizione applicata accetta il formato: CCYY-MM-DD. Non sono presenti restrizioni
sul range dei valori.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Time" type="h:timeType">
<xs:annotation>
<xs:documentation>Per compatibilità con i diversi prodotti o librerie software (es.
Axis2 e BW) si è scelto di utilizzare il tipo string. La restizione applicata accetta il formato: hh:mm:ss.sss. Non sono presenti restrizioni
sul range dei valori. Per gli ulteriori dettagli sul formato fare riferimento alla definizione di Time Data Type W3C, presente al link:
http://www.w3schools.com/Schema/schema_dtypes_date.asp</xs:documentation>
</xs:annotation>
</xs:element>
</xs:sequence>
</xs:complexType>
<!-- End Types Definition -->
<xs:element name="Header" type="h:HeaderType"/>
</xs:schema>
SCENARIO B
TripPriceService.wsdl
12
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="TripPriceServiceFacadeServiceSoapBinding" type="tns:ITripPriceServiceFacade">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getTripPrice">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="getTripPrice">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getTripPriceResponse">
<soap:body use="literal"/>
</wsdl:output>
<wsdl:fault name="TripPriceServiceException">
<soap:fault name="TripPriceServiceException" use="literal"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="TripPriceServiceFacadeService">
<wsdl:port name="TripPriceServiceFacadePort" binding="tns:TripPriceServiceFacadeServiceSoapBinding">
<soap:address location="http://localhost:8080/trip-price-0.0.1-
SNAPSHOT/webservices/TripPriceService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
HotelProceService.wsdl
13
<soap:address location="http://localhost:8088/external-services-0.0.1-
SNAPSHOT/webservices/HotelPriceService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
FlightPriceService.wsdl
14
</wsdl:service>
</wsdl:definitions>
15