WS testing

This post recaps some results of my experiments with WS testing. In last few months I have been working on several projects – Spring WS Test, Spring WS 2.0 testing support and finally on Smock. All of them reflect different approaches to WS testing. Today I am going recapitulate my findings focusing on testing producer/server side code. Consumer/client side will be covered in a following post.

Simple service, simple test

You may wonder what’s the problem? Testing web services is easy. We just need to call the endpoint end verify the response. For a simple web service we can write a simple test like this

@Test
public void testPlus()
{
	assertEquals(3, endpoint.plus(1, 2));
}

The test is same as if I was testing a normal method.

Web Services tend to be complex

The trouble is that web services tend to be complex. Normal Java methods usually have few relatively simple parameters. On the other hand, web service request or response could be quite complicated which makes the service endpoint hard to test.

GetFlightsRequest request = new GetFlightsRequest();
request.setFrom("PRG");
request.setTo("DUB");
request.setServiceClass(ServiceClass.BUSINESS);
request.setDepartureDate(DatatypeFactory.newInstance().newXMLGregorianCalendarDate(2011, 02, 23, 0));
//more setter calls could be here
		
GetFlightsResponse response = endpoint.getFlights(request);

List<Flight> flights = response.getFlight();
assertEquals(1, flights.size());
assertEquals(ServiceClass.BUSINESS, flights.get(0).getServiceClass());
//more assertions here

Such tests are bearable only until certain level of complexity. From certain level it’s real pain. I really hate to call twenty setters to construct several levels of nested XML hierarchy and then to call dozens of getters to validate the response.

To define XML use … the XML
The problem is that we want to create or validate an XML structure using wrong language. Java is just not suitable for the task. The best language to describe XML is, surprise, surprise, the XML. Would not it be easier to create XML files containing the request and response and just use them in the test?

GetFlightsRequest request = JAXB.unmarshal(getStream("request1.xml"), GetFlightsRequest.class);
		
GetFlightsResponse response = endpoint.getFlights(request);
		
DOMResult domResponse = new DOMResult();
JAXB.marshal(response, domResponse);
		
XMLUnit.setIgnoreWhitespace(true);
XMLAssert.assertXMLEqual(getDocument("response1.xml"), (Document)domResponse.getNode());

It’s much better. This test will look exactly the same way regardless the complexity of the service. But it still have some shortcomings. The biggest one is that usually testing using only one request is not sufficient. I’d like to test the service using different inputs. In such case I have to create lot of XML files that differ just in few elements. It’s a maintenance nightmare. If you change the service, you also have to change all the XML files.

Smock comes to help
Up until now we managed to test without any special framework except XMLUnit. Now we will start to use my precious Smock library. The test above can be rewritten to this form.

Map<String, Object> params = new HashMap<String, Object>();
params.put("from", "DUB");
params.put("to", "JFK");
params.put("serviceClass", "economy");
GetFlightsRequest request = createRequest(
					withMessage("request-context-groovy.xml")
					.withParameters(params), GetFlightsRequest.class);

GetFlightsResponse response = endpoint.getFlights(request);

validate(response, request)
	.andExpect(message("response-context-groovy.xml")
	.withParameter("serviceClass", "economy"));

The test is quite similar as the previous one. The difference is that it uses statically imported method createRequest to create a request and validate method to validate the response. The main advantage is that Smock methods support templates. So we can set just values we want to, the rest can be driven by the template. Moreover we do not need to deal with the request structure, parameters can be set into a simple flat map.

<ns1:GetFlightsRequest>
	<ns1:from>${from}</ns1:from>
	<ns1:to>${to}</ns1:to>
	<ns1:departureDate>2001-01-01</ns1:departureDate>
	<ns1:serviceClass>${serviceClass}</ns1:serviceClass>
</ns1:GetFlightsRequest>

Templates are even more useful when comparing the response

<ns3:GetFlightsResponse>
	<ns3:flight>
		<ns2:number>OK1324</ns2:number>
		<ns2:departureTime>2011-02-19T10:00:00</ns2:departureTime>
		<ns2:from>
			<ns2:code>${GetFlightsRequest.from}</ns2:code>
			<ns2:name>${IGNORE}</ns2:name>
			<ns2:city>${IGNORE}</ns2:city>
		</ns2:from>
		<ns2:arrivalTime>2011-02-19T12:00:00</ns2:arrivalTime>
		<ns2:to>
			<ns2:code>${GetFlightsRequest.to}</ns2:code>
			<ns2:name>${IGNORE}</ns2:name>
			<ns2:city>${IGNORE}</ns2:city>
		</ns2:to>
		<ns2:serviceClass>${serviceClass}</ns2:serviceClass>
	</ns3:flight>
</ns3:GetFlightsResponse>

We can ignore some elements, we can compare values based on the request elements and so on. Moreover, when we are using Smock library, we can leverage Spring WS 2.0 testing capabilities that Smock builds on. We can for example validate that the response is valid regarding to its schema.

validate(response).andExpect(validPayload(resource("xsd/messages.xsd")));

Cool, isn’t it?

Testing the configuration
Sometimes unit test are not sufficient. Sometimes we want to test the configuration, interceptors, error handling and so on. No problem. If you are using Spring WS 2, you can use the built-in testing library. If you need templates or if you are using different framework like Axis 2, you can use Smock. The test looks similar.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-ws-servlet.xml")
public class GroovyEndpointTest {

	private MockWebServiceClient wsMockClient;
	
	@Autowired
	public void setApplicationContext(ApplicationContext context)
	{
		wsMockClient = createClient(context);
	}
	

	@Test
	public void testResponseTemplate() throws Exception {
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("from", "DUB");
		params.put("to", "JFK");
		params.put("serviceClass", "economy");
		wsMockClient.sendRequest(
			withMessage("request-context-groovy.xml")
			  .withParameters(params))
			.andExpect(message("response-context-groovy.xml")
			  .withParameter("serviceClass", "economy"));
	}

The only difference is that we have to bootstrap the application context. Having done this, we can use the mock web service client to call our services. Even though this kind of tests is more complicated, we can use it to test object-XML mapping, endpoint resolution, error handling and configuration in general.

We have seen several different methods of WS testing. Each of them have advantages and disadvantages. If you keep you services simple, your tests are simple too. If the service is more complex, it makes sense to use XML for the test. Either using your own helper methods, Spring WS 2.0 test support or Smock.

All the samples could be downloaded from here.

Jak je to s tou inovací

Chci jen velmi krátce reagovat na Jírův článek inovace bez legrace. Začnu něčím, co je tak evidentní, že si to mnoho lidí ani neuvědomuje.

Cílem většiny podniků je vydělávání peněz, IT je pro ně jenom nutné zlo. Ano, ušetří dost nákladů, ale obvykle nepřináší naprosto žádnou konkurenční výhodu! Proto nemá pro banku smysl si psát vlastní framework. Bude jí to stát hromadu peněz a co za ně dostane? Více zákazníků? To sotva. Jedině snad mlhavou naději na budoucí úsporu. Pokud se banka pohybuje v konkurenční prostředí, tak musí nějak nalákat klienty. Může to být lepším prodejem, marketingem nebo nedejbože lepšími službami či cenami. Rozhodně to nebude lepším IT. Tady jsem schválně hodně přísný. Ano vím, že IT může dobře podpořit procesy, takže se náklady opravdu sníží a tím se mohou snížit i ceny. Ale to IT je tam jen prostředek, kdyby to šlo bez něj, rádi by si to bez těch ajťáků obstarali. Vím i o úspěchu eBanky, která lákala zákazníky právě na technologii. Ale podívejte se, jak skončila.

Tím se ovšem nesnažím říci, že pokusy o inovaci nemají smysl. Mají. Ale pro IT firmy, konzultanty a podobné. Pro ty je naopak IT prostředkem pro zisk, takže by měli inovovat co to dá. Banka by si ale měla počkat, až si za ni vyláme zuby někdo jiný a pak by měla převzít úspěšné inovace. Neměla by si do baráku pouštět žádný framework, který není vyzkoušen na někom jiném!

Rozloučím se odkazem na článek, který napsal Joel Spolsky In Defense of Not-Invented-Here Syndrome. V tom problém přesně vystihl. Ocituji to nejdůležitější

Pokud je něco jádrem vašeho podnikání, dělejte to sami ať to stojí co to stojí

Nebo ještě lépe

Vyberte si vaše hlavní podnikatelské schopnosti a cíle a ty řešte interně. Pokud jste softwarová firma, psaní skvělého kódu je vaše cesta k úspěchu. … Pokud jste farmaceutická firma, napište si software pro výzkum léků, ale nevytvářejte vlastní účetní software.

Pro zájemce ještě přikládám slavný a kontroverzní článek IT does not matter.

Jak nenapsat framework

Včera jsme v hospodě s kolegy probírali krásy interních frameworků, vzpomněl jsem si na svůj starý příspěvek, který jsem před téměř pěti lety napsal. Tenkrát to bylo pod pseudonymem, abych nenaštval zákazníka, teď už si to troufnu i podepsat. Nevím jestli se za těch pět let něco změnilo, ale myslím že to stále stojí za “přetištění”. Takže tady to je:

Před pár týdny jsem začal pracovat na projektu pro jednu velkou českou finanční společnost. Projekt je vyvíjen za pomocí frameworku, který byl vyvinut speciálně pro tuto instituci. A v tom je právě ten největší problém. Framework místo toho, aby práci programátorům usnadňoval, tak ji komplikuje a zpomaluje. A to několikanásobně. Vyvinutí jedné nepříliš složité obrazovky trvá programátorovi několik dní! A to jde jenom o obrazovku, aplikační logika se do toho nepočítá. Na straně aplikační logiky je to o trochu lepší, práce probíhá rychleji, ale abych pravdu řekl, objektovému programování se moc nepodobá. Spolu s klasiky bych mohl říci, že jsem si pokaždé, když jsem narazil na slovo interface, udělal čárku. Víte kolik výskytů jsem napočítal? Ani jeden přátelé. V tomto článku si nechci stěžovat. Chci se jen zamyslet čím to je. Vím že to není problém jen této instituce a tohoto frameworku, slyšel jsem o několika jiných, mnohem horších případech.

Nejprve se zamysleme jaká je motivace pro napsání frameworku. Odhlédněme od faktu, že to musí být neuvěřitelně zajímavá práce, kterou by si chtěl asi vyzkoušet každý programátor. Hlavní motivací bezpochyby bude, že ve velkých společnostech pracuje mnoho různých týmů, obvykle od externích dodavatelů. Každý tým používá jiné technologie, postupy a jmenné konvence což je pro velkou instituci obvykle problém. Je tu proto snaha vývoj nějakým způsobem sjednotit. Pokusím se nastínit proč si myslím, že vyvinutí vlastního frameworku nemůže tuto snahu nikdy naplnit.

Za prvé, pro napsání frameworku potřebujete špičkové programátory s přehledem v oboru a se znalostmi a zkušenostmi s psaním frameworků. Musí vědět jak má takový framework fungovat a hlavně se musí poučit z chyb svých předchůdců. Je zřejmé, že se lidé takovýchto kvalit hledají těžko.

I kdybychom měli jenom špičkové a zkušené programátory, málokdy se framework podaří napsat správně na první pokus. U takto rozsáhlých projektů zákonitě musí dojít k chybám v návrhu, nedotaženosti na jedné straně a přílišné složitosti na straně druhé (obdobně jako u specifikace EJB, kterou rozhodně nenavrhovali amatéři). Pokud by bylo snadné udělat v návrhu změnu, nebyl by to problém. Na problémy se ale obvykle narazí až při použití někým jiným než autory. To už ovšem bývá framework označen za hotový a management pochopitelně odmítá investovat do nějakých zásadních změn. Jinými slovy, framework se musí podařit hned napoprvé, což je skoro nemožné. Chyby v designu tedy zůstávají a uživatelé frameworku musí nalézat cesty, jak je obcházet.

Dalším velkým problémem podomácku napsaných frameworků bývá špatná dokumentace. Přiznejme si to, snad nikdo nepíše dokumentaci rád. Ale u frameworku je to kritický nedostatek! Pokud máte používat API, které je nekvalitně zdokumentované, je to velký problém.

V neposlední řadě musíme zmínit chybovost. V každém softwaru je chyba. U frameworků je kvůli jejich komplexnosti náchylnost k chybám ještě větší. Navíc, chybu v normální aplikaci mohou odhalit testeři. Framework mohou testovat jenom programátoři, což není právě ideální situace. U testování se můžeme chvíli zdržet. Správně by se u každého programu mělo testovat to, jak odpovídá specifikaci. Musím se přiznat, že si nedokážu představit funkční specifikaci frameworku. Tedy rozhodně ne takovou, která by šla použít jako podklad pro testy. Management se tedy musí spolehnout na programátory a věřit jim, že je framework dobře otestován.

Pro frameworky asi platí základní pravidlo jako pro jiné obecné softwarové komponenty, které zní: „Doma to sami nezkoušejte“. Doufám, že by se v dnešní době všichni dívali jak na blázna na člověka, který by přišel s tím, že si napíše vlastní OR mapovací nástroj. Vždyť existuje několik řešení, z nichž i ty komerční musí vyjít levněji, než vývoj vlastního. Nechápu proč není stejný přístup i k frameworkům. To si opravdu všichni myslí, že jsou jejich potřeby natolik jiné, že pro ně neexistuje již prověřené řešení?

Zamysleme se nad tím, jak tyto podomácku psané frameworky nahradit. Nejpřímočařejší řešení je sepsat dokumentaci o tom, co musí všechny aplikace v instituci splňovat. Tzn. omezit chaos způsobený různými přístupy týmů „administrativně“. Můžeme nadefinovat sadu nástrojů a knihoven, které se mají pro vývoj používat, nadefinovat konvence které se mají dodržovat. Je samozřejmě také nezbytné dělat code review (inspekce kódu) pracovníky instituce, aby se ohlídalo, jestli ten který projekt příliš neujíždí od jednotného standardu. Tyto kroky se ostatně používají i při použití frameworku. Dalším důležitým krokem je poskytnutí základní sady knihoven či služeb například pro SSO. Tyto knihovny by ale rozhodně měly být jenom lehkou nadstavbou nad již existujícími řešeními. Myslím si že tento postup je mnohem účinnější a efektivnější než vynakládat miliony za framework, na který budou v lepším případě všichni jenom nadávat.

Představme si, že by například velká banka investovala čas několika lidí na výzkum existujících řešení problémů, které ji pálí.

Po pár měsících by mohl tento tým přijít s tím, že jejich potřebám nejlépe vyhovuje např. použití JSF pro UI, Hibernate pro perzistenci a Spring pro propojení a konfiguraci. Nadefinovala by se pravidla, jak tyto nástroje používat (tady pozor na ohýbání technologií pro účely na které nejsou určeny). Nadefinovaly by se jmenné konvence, kam ukládat jaké zdroje atp. A to je vše. Tento postup přináší několik nesporných výhod. Zaprvé je mnohem levnější. Pokud dobře zvolíte open sourcové projekty, máte často zdarma velmi kvalitní dokumentaci, přístup ke zdrojovému kódu, upgrade a opravy chyb. V neposlední řadě, programátoři z externích firem tyto technologie budou pravděpodobně znát, takže nebudou muset trávit spoustu času studováním dokumentace a lamentováním proč mají sakra používat ten …. framework.

Na závěr bych chtěl případné čtenáře požádat o jejich zkušenosti s podomácku psanými frameworky. Doufám, že tento článek odradí alespoň jednoho člověka od napsání si vlastního frameworku a tím zachrání pár dalších programátorů od osudu psát v Javě procedurálně stejně jako ve Fortranu.