Archive for the ‘Tests’ Category

Test coverage – interesting only when low

Monday, September 28th, 2009

What a nice and controversial title. But I really mean it. Level of test coverage is really interesting only when it's low. Of course, we should aim for highest coverage possible, but the higher the coverage is, the less useful information it brings. Let take the following example and let's assume there are no tests for this method.

	/**
	 Adds positive numbers. If one of the parameters is negative,
	 IllegalArgumentException is thrown.
	 * @param a
	 * @param b
	 * @return
	 */
	public int addPositive(int a, int b)
	{
		if ((a<0) || (b<0))
		{
			throw new IllegalArgumentException("Argument is negative");
		}
		return a+a;
	}

No coverage - wow, that's interesting

The fact that some piece of code is not covered at all is really interesting. It either means, that we have to start writing tests at once or that the piece of code has to be deleted. Either way we know that we should do something. Moreover, we know exactly what to do. For example, we can start with the following test.

	@Test
	public void testAddition()
	{
		assertEquals(2, math.addPositive(1, 1));
	}

It will get us partial coverage like this
Partial coverage

Partial coverage - cool, I know what to test next

If I have partial coverage, I can see what other tests I am supposed to write. In our example I see that I have to test what happens if I pass in a negative numbers.

	@Test(expected=IllegalArgumentException.class)
	public void testNegative1()
	{
		math.addPositive(-1, 1);
	}
	@Test(expected=IllegalArgumentException.class)
	public void testNegative2()
	{
		math.addPositive(1, -1);
	}

Total coverage - I have no clue

Now we have 100% coverage. Well done! Unfortunately, I have no clue what to do next. I have no idea if I need to write more tests, I do not know if I have complete functional coverage. I have to stop looking at the coverage and I have to start thinking. You see, when the coverage was low it gave me lot of useful information, when it's high I am left on my own. For example I can think hard and come up with following complex test scenario.

	@Test
	public void testComplicatedAddition()
	{
		assertEquals(3, math.addPositive(1, 2));
	}

Please note, that the coverage was already 100% before I have written the test. So one more test could not make it higher. Nevertheless the test actually detected a bug that was there from the beginning. It means that 100% code coverage is not enough, we have to aim higher. We should care about functional coverage and not about code coverage!

That's also one of many reasons why we should do test driven development. Or at least test first development. If we write the tests before the code, we are forced to think from the beginning. In TDD we do not care about the coverage. We just think about the functionality, write corresponding tests and then add the code. Total coverage is automatically achieved.

In the opposite situation, when we write the code first and then add the tests as an afterthought, we are not forced to think. We can just write some tests, achieve the target coverage and be happy. Test coverage can be really deceitful. It can give us false sense of security. So please remember, test coverage is a good servant but a bad master.

Spring WS Test

Wednesday, September 2nd, 2009

Last few weeks I have been working on one of my pet projects. Its name is Spring WS Test. As the name implies, its main purpose is to simplify Spring WS tests.

Again, I am scratching my own itch. I am quite test infected and I have needed something that allows me to write functional tests of my application without having to depend on an external server. Until now, you basically had two options. This first one is to test WS client application using plain old JUnit together with a library like EasyMock. But usually this test are quite ugly and hard to read. Moreover this type of tests does not test your configuration. The second option is to create a functional test that calls an external mock service. But this solution requires you to have two JVM, its configuration is complicated and error prone.

Classical WS test

I have been looking for something in between, for something that would allow me to write functional tests using JUnit and would be able to run in the same JVM as the test. Unfortunately I have not been able to find anything similar.

Spring WS Test test

That's the reason why I have created Spring WS Test project. It's quite simple and easy even though I had to spent lot of my evenings getting it into a publishable state.

Basic configuration looks like this

<beans ...>
  <!-- Creates mock message sender -->
  <bean id="messageSender" class="net.javacrumbs.springws.test.MockWebServiceMessageSender"/>

  <!-- Injects mock message sender into WebServiceTemplate -->
  <bean class="net.javacrumbs.springws.test.util.MockMessageSenderInjector"/>

  <!-- Looks for responses on the disc based on the provided XPath -->
  <bean class="net.javacrumbs.springws.test.generator.DefaultResponseGeneratorFactoryBean">
     <property name="namespaceMap">
         <map>
            <entry key="soapenv"
                value="http://schemas.xmlsoap.org/soap/envelope/"/>
            <entry key="ns"
                value="http://www.springframework.org/spring-ws/samples/airline/schemas/messages"/>
         </map>
     </property>
     <property name="XPathExpressions">
         <list>
             <value>
                 concat(local-name(//soapenv:Body/*[1]),'/default-response.xml')
             </value>
         </list>
     </property>
 </bean>
</beans>

Here we have MockWebServiceMessageSender that replaces standard Spring WebServiceMessageSender. The replacement is done by MockMessageSenderInjector. The only other thing you have to do is to define ResponseGenerator. It's main purpose is to look for files in you test classpath and return them as mock responses.

Of course it has to decide, which file to use. By default a XPath expression is used to determine the resource name. In our example it is concat(local-name(//soapenv:Body/*[1]),'/default-response.xml'). It takes name of the payload (first soap:Body child) and uses it as a directory name. File “default-response.xml” from this directory is used as the mock response. Simple isn't it?

Of course you can define more complicated XPaths, you can use XSLT templates to generate your responses, you can validate your requests etc. More details can be found in the documentation.

Now I am looking for some end-user feedback. So please, if you are using Spring WS on the client side do not hesitate and test it. It should be stable enough to be used although there might be a bug here and there.

Stoprocentní mantra

Tuesday, August 25th, 2009

Programátoři a jejich manažeři občas řeší, jaká míra pokrytí unit testy je dostatečná. Můžeme zaslechnout, že cokoliv pod 60% nestojí za zmínku, občas zahlédneme, že to magické číslo je 95% a někteří fundamentalisté dokonce tvrdí, že cesta k nirváně vede jen a pouze přes stoprocentní pokrytí.

Dnes jsem si řekl, že je ideální počasí na to, vyzkoušet jestli se dá sta procent dosáhnout. Povedlo se mi to během dopoledne a nyní bych se chtěl podělit o své zkušenosti.

Pracuji na jednom menším projektu, který v podstatě jen zpracovává REST volání a přeposílá je pomocí SOAP na další systémy. Není to nic složitého, ale není to ani žádná trivialita. Prostě ideální kandidát na pokusy. I před tím, než jsem se rozhodl dosáhnout vytoužené mety, jsem se snažil unit testy dělat poctivě. Jen občas jsem něco neotestoval s tím, že to je příliš triviální na to, abych se patlal s testem. Většinou jsem ale svoji lenost ovládl. Proto mě překvapilo, že moje pokrytí testy bylo něco kolem 60%.

Začal jsem pátrat po příčinách a zjistil jsem, že nejsem tak svědomitý, jak jsem si myslel. Narazil jsem například na neotestovanout třídu, kterou jsem jen odněkud zkopíroval Fůůůůůj. Ale občas je to potřeba. Znáte to, potřebujete něco podobného co umí nějaká metoda ve Springu, jenom je to potřeba trochu ohnout, má to implementovat jiné rozhraní a navíc je to v tom Springu private. Tak to vezmete, zkopírujete a trochu přiohnete. Pokud se pokoušíte o stoprocentní pokrytí, musíte to i otestovat. Pokud se spokojíte s pokrytím nižším, je velice lákavé si říci, že to už je vlastně otestovaný kód, tudíž není potřeba testovat. Ještě jednou fůůůj. Ještě že se stydím jenom před děvčaty. Nakonec jsem byl rád, že jsem tu zkopírovanou metodu musel otestovat. Díky tomu jsem se nad ní pořádně zamyslel a zkrátil ji na polovinu.

Pak jsem ještě napsal pár testů na metody, které jsem nepovažoval za hodné testu. Potěšilo mě, že jsem narazil jenom na jednu metodu, kterou jsem evidentně netestoval, protože by to bylo moc práce. Díky své nové stoprocentní mantře jsem se donutil otestovat i ji. Nakonec to ani moc nebolelo, jenom jsem se musel ponořit do vnitřností Spring-WS abych zjistil jak nejlépe vytvořit instanci jednoho rozhraní (SoapMessage). Alespoň jsem se něco naučil. Pak už zbývaly jen zapeklitější věci.

Get a Set metody
Jedním ze zádrhelů byly get metody. Poté co jsem otestoval všechen kód, který něco dělá, zbyla mi jedna skupina metod, které prostě v žádném testu nebyly potřeba. Byly to get metody u Spring bean. Když totiž potřebuji pomocí Springu něco injektnout, automaticky vygeneruji set a get metody. A to i když používám autowiring přímo do polí. Set metody využívám právě v unit testech. Ukázalo se, že pokud není v testu set metoda použita, je v tom testu něco špatně. A to bez výjimky. Buď to, co nastavuji, není vůbec potřeba nebo netestuji funkcionalitu, která danou hodnotu využívá. Takže si prosím zapamatujte:

Pokud není otestována set metoda, je něco špatně.

Ale zpátky ke get metodám u Spring bean. Ty pro test potřeba nejsou. Ony totiž nejsou potřeba vůbec. Jenom jsem měl hrozný psychický problém s tím je smazat. Pořád jsem si říkal, že je tam nechám, co kdyby byly potřeba. Co kdyby někdo potřeboval vědět, jak je daná třída nakonfigurována atp. Ale pak mě stoprocentní mantra donutila je smazat a myslím, že si na ně nikdo nevzpomene. Jenom je to divné mít set bez getu. Kdybych ty get metody opravdu někdy potřeboval, třeba kvůli JMX, tak je prostě přidám a otestuji.

Equals
Dalším špekem je equals metoda. Pokud byste náhodou nevěděli jak vypadá, tak vám ukáži, to co mi normálně generuje Eclipse

	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Person))
			return false;
		Person other = (Person) obj;
		if (firstname == null) {
			if (other.firstname != null)
				return false;
		} else if (!firstname.equals(other.firstname))
			return false;
		if (secondname == null) {
			if (other.secondname != null)
				return false;
		} else if (!secondname.equals(other.secondname))
			return false;
		return true;
	}

A teď si představte jak otestovat všechny možnosti takového krasavce. Abyste dosáhli plného pokrytí, museli byste napsat cca devět testů. To vše kvůli vygenerovanému kódu. K tomu mě nikdo nedonutí. Zkusil jsem použít EqualsBuilder, ale moc jsem si nepomohl. I s jeho pomocí jsou tam asi čtyři kombinace, které je potřeba otestovat. Mumlaje si pod vousy „sto procent, sto procent“ jsem se zeptal pana Googla a ten mě navedl na EqualsTester. Problém vyřešen, jsem zas o krok blíže k vytouženému cíli.

Nemožná výjimka
Zbývala mi jen jedna úplně poslední past nastražená na mě pány ze Sunu:

	private byte[] toByteArray(String data)
	{
		try {
			return data.getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			LOG.error("Can not happen",e);
			throw new RuntimeException(e);
		}
	}

Abych dosáhl kompletního pokrytí, musím otestovat i catch blok. Ten je bohužel dosažitelný jen pokud vaše JVM nepodporuje UTF-8. Vzhledem k tomu, že by pak JVM nesplňovalo specifikaci není tato situace moc pravděpodobná. Bohužel, tím pádem ani otestování není moc snadné. Nakonec jsem musel kód změnit takto:

	private static final Charset UTF8 = (Charset)Charset.availableCharsets().get("UTF-8");

	private byte[] toByteArray(String data)
	{
		return data.getBytes(UTF8);
	}

Není to moc elegantní, ale alespoň se mohu utěšovat tím, že je to výkonově efektivnější. Pokud znáte lepší cestu, podělte se o ni, rád se něco přiučím.

A to je vše, dosáhl jsem stoprocentního pokrytí. Zde mi samozřejmě hrálo do karet, že jsem nemusel řešit připojení k databázi. To bych se musel uchýlit k mockování JDBC template nebo k mé oblíbené in-memory databázi ve spojení s JPA. To už by sice nebyl unit test, ale třeba by to nikdo nepoznal.

Takže mám stoprocentní pokrytí, co mi to dalo? Donutilo mě to otestovat věci, na které bych se pravděpodobně jinak vykašlal. U některých z nich jsem byl rád, že jsem je nakonec otestoval. Přivedlo mě to k lepšímu kódu. Potěšilo mě, že jsem nemusel psát žádné nesmyslně testy, jenom kvůli pokrytí. Toho jsem se ze začátku trochu bál. Samozřejmě nesmím usnout na vavřínech. To že mám kód zcela pokryt neznamená, že v něm není chyba. Těch tam naprosto jistě ještě hodně zůstalo. Nicméně mám z toho docela dobrý pocit, myslím si, že 100% je docela dobrá laťka. Na jednu stranu vám nedovolí vymýšlet si výmluvy, proč něco netestujete, na stranu druhou se ukázala jako dosažitelná. Proto se příště jako správný úderník pokusím pokrýt na 105%.