tifyty

pure Java, what else ?

Havi archívumok: augusztus 2012

Még mindig az Integer cache, de utoljára

A miért billeg cikkben körbejártuk egy kicsit, hogy mi a helyzet az Integer cache-sel. A cikk végén, pedig a szavazásba beleírtam, hogy

A szavazásba bele sem mertem írni, hogy reflection-nel megoldható, hogy az alsó határ se -128 legyen. Még valaki képes lett volna arra szavazni.

Ebben a cikkben azért mégiscsak megnézzük, hogy ha valaki ezt megpróbálná, akkor mire kell felkészülnie, és milyen trükköket kell alkalmaznia. A cikk végén pedig lesz csattanó is.

Mit is szeretnénk?

Azt szeretnénk elérni, hogy az Integer autoboxing ne csak -128 és 127 közötti értékekre adjon konstand Integer objektumokat, hanem egy ennél nagyobb intervallumra. Ebben az intervallumban a Java runtime két azonos értékű int értéket ugyanazon Integer objektummá konvertál autoboxing esetén. Azaz

Integer a = 13;
Integer b = 13;
if( a == b ){
   System.out.println("kakukk");
}

az bizony kakukk. 13 helyett állhat itt bármilyen szám, -128 és 127 között (beleértve a határokat is), de ha -128-nál kisebb, vagy 127-nél nagyobb számot írunk, akkor bizony a != b.

A fentieket egyébiránt egészen pontosan úgy teszi a JVM, hogy amikor autoboxingot akar csinálni, akkor az Integer osztály statikus Integer.valueOf(int) metódusát hívja meg, ami kimásolva az rt.jar-ból (persze a forrás, de az is benne lehet a jar-ban):

public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

Az IntegerCache az Integer osztály egy belső privát statikus osztálya, mi osztálybetöltési időben beállítja a low és high int értékeket, valamint allokálja a cache tömböt, és feltölti a -128 és 127 közötti egész értékekkel. Ennél egy kicsit bonyolultabba dolog, mert lehet definiálni a java.lang.Integer.IntegerCache.high system property-t, és ha ez definiálva van, akkor a high értéke nem 127, hanem a megadott lesz. A low értékének megváltoztatására azonban, még osztálybetöltési időben sincs a Java által támogatott lehetőség.

Ezen kívül fontos megjegyezni, hogy a low, high és a cache változók az IntegerCache osztály statikus és final változói (ez még fontos lesz).

Mit tegyünk

Essünk neki ennek az osztálynak reflection-nel! Valójában persze nem, csak a tanulság kevéért, és az itt bemutatott példát ha partner cégnél éles kódban meglátom, vagy akár csak hasonlót, akkor soha többet szóba nem állok, szerződést nem kötök.

Készítsünk egy olyan kis metódust (legyen ez egy egyetlen statikus metódus egy utility osztályban), amelyik átírja a low, high és cache mezőket a kedvünk szerint. Valahogy így:

	public static void setIntegerCacheLimits(int low, int high)
			throws NoSuchFieldException, SecurityException,
			IllegalArgumentException, IllegalAccessException {
		Class<?>[] classes = Integer.class.getDeclaredClasses();
		Class<?> cacheClass = null;
		for (Class<?> klass : classes) {
			if (klass.getSimpleName().equals("IntegerCache")) {
				cacheClass = klass;
			}
		}
		Field lowField = cacheClass.getDeclaredField("low");
		lowField.setAccessible(true);
		Field highField = cacheClass.getDeclaredField("high");
		highField.setAccessible(true);
		Field cacheField = cacheClass.getDeclaredField("cache");
		cacheField.setAccessible(true);
		Integer[] cache;
		cache = new Integer[(high - low) + 1];
		int j = low;
		for (int k = 0; k < cache.length; k++)
			cache[k] = Integer.valueOf(j++);
		cacheField.set(null, cache);
		highField.set(null, high);
		lowField.set(null, low);
	}

Működik? Nem! Bummer. Mit mond?

java.lang.IllegalAccessException: Can not set static final [Ljava.lang.Integer; field java.lang.Integer$IntegerCache.cache to [Ljava.lang.Integer;
	at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:73)
	at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:77)
	at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
	at java.lang.reflect.Field.set(Field.java:680)
... itt még folytatódik a stack trace, nem érdekes

Miért nem megy? Hát megadtuk, hogy setAccessible(true), hozzá kellene engednie!

Mit mond erről a setAccessible() dokumentációja?


Set the accessible flag for this object to the indicated boolean value. A value of true indicates that the reflected object should suppress Java language access checking when it is used. A value of false indicates that the reflected object should enforce Java language access checks.

Hát bizony ennek engednie kellene, és pár perc guglizás után ki is derül, hogy nem a private-tal van baja, hanem a final-lal. Az ugyanis nem hozzáférési jogosultság kérés, hanem egyszerűen a final az végleges, azt nem lehet átállítani. Persze, ami ott van a memóriában, ahhoz hozzá lehet férni, és aki elszánt, azt meg nem lehet állítani. Azért, mert a reflection Field osztályának set metódusa nem akarja átírni a final mezőket azért még ne adjuk fel. Verjük át a reflection osztályt, hitessük el vele, hogy ez a mező nem final.

Hogyan? Hát reflection-nel!

	public static void setIntegerCacheLimits(int low, int high)
			throws NoSuchFieldException, SecurityException,
			IllegalArgumentException, IllegalAccessException {
		Class<?>[] classes = Integer.class.getDeclaredClasses();
		Class<?> cacheClass = null;
		for (Class<?> klass : classes) {
			if (klass.getSimpleName().equals("IntegerCache")) {
				cacheClass = klass;
			}
		}
		Field lowField = cacheClass.getDeclaredField("low");
		lowField.setAccessible(true);
		Field highField = cacheClass.getDeclaredField("high");
		highField.setAccessible(true);
		Field cacheField = cacheClass.getDeclaredField("cache");
		cacheField.setAccessible(true);
		Integer[] cache;
		cache = new Integer[(high - low) + 1];
		int j = low;
		for (int k = 0; k < cache.length; k++)
			cache[k] = Integer.valueOf(j++);
		Field modifiersField = Field.class.getDeclaredField("modifiers");
		modifiersField.setAccessible(true);
		modifiersField.setInt(cacheField, cacheField.getModifiers()
				& ~Modifier.FINAL);
		cacheField.set(null, cache);
		modifiersField.setInt(highField, highField.getModifiers()
				& ~Modifier.FINAL);
		highField.set(null, high);
		modifiersField.setInt(lowField, lowField.getModifiers()
				& ~Modifier.FINAL);
		lowField.set(null, low);
	}

Mit is csinálunk? Elkérjük reflection-nel a java.lang.reflect.Field osztály modifiers mezőjét, és az átállítani kívánt három mező esetében átállítjuk a FINAL bitet. (Sajnos a java.lang.reflect.Field osztályban a modifiers mező nem final, pedig lehetne az, és ezért ezt a trükköt meg lehet lépni.)

Innen kezdve a reflection azt hiszi, hogy ez a három mező az IntegerCache osztályban nem final és hajlandó asszisztálni abban, hogy tűkön lőjük magunkat. A metódus hiba nélkül lefut, öröm és boldogság…

… egészen addig, míg meg nem látjuk a kis teszt programunknak

package tifyty;

import java.lang.reflect.Field;

import org.junit.Test;

public class TestSetIntegerCache {
	@Test
	public void testSetIntegerCache() throws NoSuchFieldException,
			SecurityException, IllegalArgumentException, IllegalAccessException {
		SetIntegerCache.setIntegerCacheLimits(-3000, 3000);
		Class<?>[] classes = Integer.class.getDeclaredClasses();
		Class<?> cacheClass = null;
		for (Class<?> klass : classes) {
			if (klass.getSimpleName().equals("IntegerCache")) {
				cacheClass = klass;
			}
		}
		Field lowField = cacheClass.getDeclaredField("low");
		lowField.setAccessible(true);
		Field highField = cacheClass.getDeclaredField("high");
		highField.setAccessible(true);
		System.out.println("low =" + lowField.getInt(null));
		System.out.println("high=" + highField.getInt(null));
		for (int i = -3001; i < 3002; i += 1000) {
			Integer a = i;
			Integer b = i;
			System.out.println((a == b ? "==" : "!=") + i + "\n");
		}
	}
}

a kimenetét:

low =-3000
high=3000
!=-3001

!=-2001

!=-1001

==-1

==999

==1999

==2999

Na erre kellene gombot varrni!

Mi történik?

Avagy: csalatkoznék szemeim által?

Kinek higgyek? Az output-nak, vagy a debuggernek?

Ott van a

if (i >= IntegerCache.low && i <= IntegerCache.high)

sor, debuggolom, a low át van írva, a high át van írva, i a kettő között van, és mégis, mégis (sírógörcs, csodálkozás, és ámulás) az egész kifejezés értéke false, új Integer objektum jön létre.

Ilyenkor jön az, hogy elő a bájt kóddal. Csak oroszlánszívűeknek.

$ mkdir tmp
$ cd tmp
$ jar xvf /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre/lib/rt.jar
$ javap  -c java/lang/Integer.class >Integer.txt
$ less Integer.txt

Ebben azt láthatjuk, hogy

  public static java.lang.Integer valueOf(int);
    Code:
       0: getstatic     #34                 // Field $assertionsDisabled:Z
       3: ifne          22
       6: getstatic     #35                 // Field java/lang/Integer$IntegerCache.high:I
       9: bipush        127
      11: if_icmpge     22
      14: new           #36                 // class java/lang/AssertionError
      17: dup
      18: invokespecial #37                 // Method java/lang/AssertionError."<init>":()V
      21: athrow
      22: iload_0
      23: bipush        -128
      25: if_icmplt     45
      28: iload_0
      29: getstatic     #35                 // Field java/lang/Integer$IntegerCache.high:I
      32: if_icmpgt     45
      35: getstatic     #38                 // Field java/lang/Integer$IntegerCache.cache:[Ljava/lang/Integer;
      38: iload_0      
      39: sipush        128
      42: iadd
      43: aaload
      44: areturn      
      45: new           #39                 // class java/lang/Integer
      48: dup
      49: iload_0      
      50: invokespecial #40                 // Method "<init>":(I)V
      53: areturn      

Mit látunk a 23. sorban? Azt, hogy bipush -128. Ezt a final értéket bizony a javac befordította a kódba. Hiába létezik az IntegerCache osztályban a statikus low változó (mert létezik, aki nem hiszi, fejtse vissza azt is, és látni fogja, hogy statikus initializer a

      50: bipush        -128
      52: istore_2

sorokkal tárolja ezt az értéket). Hiába, mert a valueOf(int) NEM olvassa azt a változót. Minek is olvasná? Az konstans 127, nem is lehet más, hiszen final. És ha bármilyen final változót átállítunk reflection-nel, akkor fel lehetünk rá készülve, hogy bárhol a kódban, ha valamelyik programrészlet, JIT által generált kód darab, szál, vagy akár egy tündér, vagy törp tartogat magánál egy másolatot, eszébe nem fog jutni megnézni soha, hogy esetleg megváltozott-e a megváltoztathatatlan.

Ezért kellene, hogy a Field osztályban a modifiers mező is final legyen. És nem csak azért nem írtam bele az előző szavazásba, hogy oldjuk meg reflection-nel, mert féltem, hogy valaki arra szavaz, hanem, mert nem lehet. Meg nem is kell.

Integer cache konfigurálható. De miért billeg?

A korábbi Ez már tényleg … posztban megnéztük, hogy az Integer osztály betöltődéskor létrehoz 256 darab konstans Integer objektumot, és az autoboxing során ezt használja a -128 és 127 közötti int értékek konvertálására. Azt is megnéztük, hogy ez bizonyos esetekben lényeges sebességnövekedést hozhat. Mi a helyzet akkor, ha azt gondoljuk, hogy ez a sebességnövekedés még nagyobb is lehetne, ha ez a cache még nagyobb lenne? Mondjuk nem csak 127-ig tartalmazná a számokat, hanem akár 1000-ig.

A rövid válasz, hogy felejts el. You are doing it wrong. Olvasgasd a napi mi a f*sz cikkeit. Annyira hülyeség, ez a gondolat, hogy a kommentek egy részéről még azt sem lehet eldönteni, hogy komolyan gondolják-e.

Ennek azonban némileg ellent mond, hogy a Java fejlesztői nyitva hagytak egy lehetőséget, hogy ne csak 127-ig legyenek konstansok az Integer-ek. Ha a java.lang.Integer.IntegerCache.high értékét átállítjuk, hogy ne 127 legyen a felső limit a cache-elt Integer-ekre.

De miért csak a felső értéket lehet átállítani? Miért nem lehet az Integer osztályban egy static cache, ami -128 alatt is tárolja (cache-eli, pool-olja) az objektumokat?

A szavazásba bele sem mertem írni, hogy reflection-nel megoldható, hogy az alsó határ se -128 legyen. Még valaki képes lett volna arra szavazni.

[E] How to mock the System.class

In this article I will show you a way to test code that uses static methods from a final class and how to mock that class. The example to mock is the System class. (We are not playing in the sand, we are real warriors.) We will use mockito, powermock, maven, eclipse and lots of brain of yours to follow. (You are also a java warrior after all!)

The Problem

When you unit test your application you usually use some mocking framework. Some of the most known are EasyMock and Mockito. There are others as well but for now I am not going to talk about these. They are light weight tools when you are really heavily into testing. Especially when you are creating test code that was created without caring about testability. Consider for example the following code fragment:

    public String getConfigValue(final String key) {
        String configValue = null;
        final String envKey = "sb4j." + key;
        if (configProperties != null && configProperties.containsKey(key)) {
            configValue = configProperties.getProperty(key);
        }
        String sysValue = null;
        if ((sysValue = System.getenv(envKey)) != null) {
            configValue = sysValue;
        }
        if ((sysValue = System.getProperty(envKey)) != null) {
            configValue = sysValue;
        }
        return configValue;
    }

A very simple code that tries to look up some configuration key in a Properties type variable (not defined here in the fragment, it is defined as a field in the class) but the configuration value for a certain key can be redefined in the environment variables and in the system properties (those defined with the java option -D on the command line). The strongest is the system property. If that is defined everything else is irrelevant. The second strongest is the environment variable, and the final choice is the configuration properties variable that are read from a .properties file (reading also not listed here to save space).

You may notice that the system property and the environment variable names are prefixed using the string “sb4j.” You can guess that this code fragment is from ScriptBasic for Java while it was under development.

The code is simple and seems to be OK, but trust me (not because I am engineer [as a matter of fact I am], but because of my experience): no code can be so simple that it can not contain a bug. I have learnt it modifying a method once, simpler than this above and since I had ten more minutes before heading towards home not to miss my movie for the evening I wrote a unit test. The movie was long time over when I finished with the fifth unit test that I created that night: every new bug you find deserves its own unit test.

We have to write unit test for the code. We need to mock the external classes that are used by the code. Some of them at least. We have the classes java.util.Properties, java.lang.String and java.lang.System. Obviously we need not mock String. Even though this is a class, it is almost like a primitive type. Similarly we need not mock Properties. Whatever the mock could provide us a stub instance of the Properties class can provide. We will not be able to check that the properties were really read using containsKey and getProperty method calls but if we get back the value we inserted into the stub properties variable we should be ok.

What we need to mock however is System and we have to mock the static methods getenv and getProperty.

The Solution

To do that we have to use Powermock. This is an extension to EasyMock and to Mockito (my fav is the second over the first one) and gives methods that let us mock static methods. To do so it needs to craft some hefty things into the Java byte code that I would not ever like to have in a production code except for testing and mocking. I am not knowledgeable how power mock really works, but I have the feeling that they are poking some areas that are beyond the official Java contract. I never mind this at this moment. Lets go and prepare the test.

To do so we need a unit test and we have to tell the test framework that we use Powermock so that a modified runner provided by Powermock is used instead of the standard JUnit runner. This is very simple to tell it actually, all we have to do is to annotate the class using the annotations:

@RunWith(PowerMockRunner.class)
@PrepareForTest({ System.class })
public class TestBasicConfiguration {

RunWith is org.junit.runner.RunWith and it is processed by the JUnit framework. PrepareForTest is org.powermock.core.classloader.annotations.PrepareForTest and informs PowerMock that it has to prepare the list of classes (in our case a single class, called System) to be mocked.

For your reference I include here the full source code of the test with all test methods as they were in the test class:

package com.scriptbasic.configuration;

import java.util.Properties;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import com.bdd.Business;
import com.scriptbasic.interfaces.Configuration;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ System.class })
public class TestBasicConfiguration {

    private static final Configuration config = new BasicConfiguration();
    private static final String key = "key";
    private static final String value = "value";
    private static final String badValue = "badValue";

    @Test
    public void testGetConfigValueWhenConfigKeyExistsShallReturnValue() {
        new Business() {
            private String actual;
            private Properties props;

            @Override
            public void given() {
                props = Mockito.mock(Properties.class);
                config.setConfigProperties(props);
                Mockito.when(props.containsKey(key)).thenReturn(true);
                Mockito.when(props.getProperty(key)).thenReturn(value);
            }

            @Override
            public void when() {
                actual = config.getConfigValue(key);
            }

            @Override
            public void then() {
                Mockito.verify(props).containsKey(key);
                Mockito.verify(props).getProperty(key);
                Assert.assertEquals(value, actual);
            }

        }.execute();
    }

    @Test
    public void testGetConfigValueWhenEnvironmentKeyExist() {
        String actual;
        Properties props;
        // GIVEN
        props = PowerMockito.mock(Properties.class);
        PowerMockito.mockStatic(System.class);
        config.setConfigProperties(props);
        Mockito.when(props.containsKey(key)).thenReturn(true);
        Mockito.when(props.getProperty(key)).thenReturn(badValue);
        Mockito.when(System.getenv("sb4j." + key)).thenReturn(value);
        // WHEN
        actual = config.getConfigValue(key);
        // THEN
        Mockito.verify(props).containsKey(key);
        Mockito.verify(props).getProperty(key);
        PowerMockito.verifyStatic();
        System.getenv("sb4j." + key);
        Assert.assertEquals(value, actual);
    }

    @Test
    public void testGetConfigValueWhenEnvironmentKeyExists() {
        new Business() {
            private String actual;
            private Properties props;

            @Override
            public void given() {
                props = PowerMockito.mock(Properties.class);
                PowerMockito.mockStatic(System.class);
                config.setConfigProperties(props);
                Mockito.when(props.containsKey(key)).thenReturn(true);
                Mockito.when(props.getProperty(key)).thenReturn(badValue);
                Mockito.when(System.getenv("sb4j." + key)).thenReturn(value);
            }

            @Override
            public void when() {
                actual = config.getConfigValue(key);
            }

            @Override
            public void then() {
                Mockito.verify(props).containsKey(key);
                Mockito.verify(props).getProperty(key);
                PowerMockito.verifyStatic();
                System.getenv("sb4j." + key);
                Assert.assertEquals(value, actual);
            }

        }.execute();
    }

    @Test
    public void testGetConfigValueWhenSystemPropertyExists(){
        new Business() {
            private String actual;
            private Properties props;
            
            @Override
            public void given() {
                props = PowerMockito.mock(Properties.class);
                PowerMockito.mockStatic(System.class);
                config.setConfigProperties(props);
                Mockito.when(props.containsKey(key)).thenReturn(true);
                Mockito.when(props.getProperty(key)).thenReturn(badValue);
                Mockito.when(System.getenv("sb4j." + key)).thenReturn(badValue);
                Mockito.when(System.getProperty("sb4j." + key)).thenReturn(value);
            }
            
            @Override
            public void when() {
                actual = config.getConfigValue(key);
            }
            
            @Override
            public void then() {
                Mockito.verify(props).containsKey(key);
                Mockito.verify(props).getProperty(key);
                PowerMockito.verifyStatic();
                System.getenv("sb4j." + key);
                PowerMockito.verifyStatic();
                System.getProperty("sb4j." + key);
                Assert.assertEquals(value, actual);
            }
        }.execute();
    }
}

For demonstration purposes some comments were deleted, but other than those, the code above is complete.
PowerMock is powerful and makes life easy when testing static method, but not that powerful. If we execute maven, we get the following error:

Results :

Failed tests: 
  testGetConfigValueWhenEnvironmentKeyExist(com.scriptbasic.configuration.TestBasicConfiguration): 

Tests in error: 
  testGetConfigValueWhenEnvironmentKeyExists(com.scriptbasic.configuration.TestBasicConfiguration): 
  testGetConfigValueWhenSystemPropertyExists(com.scriptbasic.configuration.TestBasicConfiguration): 

Tests run: 56, Failures: 1, Errors: 2, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 20.666s

What is the problem? Let us have a look at the surefire reports file:

$ cat target/surefire-reports/com.scriptbasic.configuration.TestBasicConfiguration.txt 
-------------------------------------------------------------------------------
Test set: com.scriptbasic.configuration.TestBasicConfiguration
-------------------------------------------------------------------------------
Tests run: 4, Failures: 1, Errors: 2, Skipped: 0, Time elapsed: 3.455 sec <<< FAILURE!
testGetConfigValueWhenEnvironmentKeyExist(com.scriptbasic.configuration.TestBasicConfiguration)  Time elapsed: 0.83 sec  <<< FAILURE!
Wanted but not invoked java.lang.System.getenv("sb4j.key");
Actually, there were zero interactions with this mock.
	at org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl.performIntercept(MockitoMethodInvocationControl.java:292)
	at org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl.invoke(MockitoMethodInvocationControl.java:194)
	...

testGetConfigValueWhenEnvironmentKeyExists(com.scriptbasic.configuration.TestBasicConfiguration)  Time elapsed: 0.083 sec  <<< ERROR!
org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
2. inside when() you don't call method on mock but on some other object.

	at com.scriptbasic.configuration.TestBasicConfiguration$2.given(TestBasicConfiguration.java:109)
	at com.bdd.Business.execute(Business.java:17)

You can see on the highlighted code the relevant error messages. System was NOT mocked, and not because it is a system class but rather because this is final. Looking at the code in RT you can see that it really is, just as you can also have a look at the documentation: http://docs.oracle.com/javase/7/docs/api/java/lang/System.html

We are doomed? We can not test this code?

The good approach, generally is, to move all these external dependencies to a utility class, having static methods proxying the call to the System class. In that case we can mock the utility class and life is beautiful again. To be honest, I could do that in the example case. The developed code of sb4j was in my hands and I could modify it any way I wanted. But the idea to overcome this issue just did not leave my mind. There is a problem that I can have a workaround for and not a solution. The solution is to test the code as it is now!

If I only had a System class that was not final. Hey!! Wait!! I can have a class named System that is not final:

public class System {
    public static String getenv(String key) {
        return java.lang.System.getenv(key);
    }

    public static String getProperty(String key) {
        return java.lang.System.getProperty(key);
    }
}

If I place this class in the same package as the tested code then the compiler will compile the tested class again this System stub instead of the java.lang.System and this class can be mocked. But this alter the code base. Does not change the code itself but the class file at the end calls this stub, which calls system and this affects performance. After all this is all about reading configuration, so performance should not be a big issue, but even though: I just do not like it. And if the production code contains the stub System then it also needs testing even if it as simple as cold water. And to test it I have to mock java.lang.System and the circle of hell just closed.

What if this code would only be compiled against the code for the testing and not for the compilation of the production code? In that case I test the original code without modification and the production .class is not influenced by the test needs. Even more: if I use my stub System class only for testing, it does not need to proxy the methods getenv and getProperty to java.lang.System since it will never been used in production code. And we do not need testing it since this is not production code.

Interesting is it? Let’s give it a try. Let me create a class under src/test/java in the same class as the above code containing a simpler version of the above class:

public class System {
    public static String getenv(String key) {
        return null;
    }

    public static String getProperty(String key) {
        return null;
    }
}

And executing the code in Eclipse: tadam! It works. Problem solved.

Almost. Running mvn clean install from the command line I get exactly the same error as the above. The version of the class used for testing is still compiled against the java.lang.System. On second thought this is fine. When we compile using maven we get one set of classes from our java sources. If you look at the maven log on the screen, you see:

[INFO] --- maven-compiler-plugin:2.5.1:compile (default-compile) @ jscriptbasic ---
[INFO] Compiling 243 source files to ... target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ jscriptbasic ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 31 resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.5.1:testCompile (default-testCompile) @ jscriptbasic ---
[INFO] Compiling 38 source files to ... target/test-classes

that the test compilation compiles only the test classes, which are few in this case compared to the production classes.
Note that I deleted the full path from the printout.
When the tests run the directories target/test-classes and targed/classes are on the classpath in this order. It means that test classes are found first and production classes are found the second. If I want to have a version of the tested class for testing then I have to compile it twice: once for the production code and once for the test. There will be two versions of the class on the classpath: one in the target/test-classes directory and one in the targed/classes directory, and the test framework will perform the tests on the first one, “linked” against the stub System.

The only issue that remained is how to tell maven to compile the source code again for testing. There is no easy solution as maven, by default is handling sources from a single directory. I also do not want to have the test classes in the production JAR file and even more I do not want to have the production code compiled against my stub.

The help comes with the maven helper plugin that can be used to configure extra directories for the test compilation. Actually this is a bit of hack: we tell the compiler that there are “generated” sources (ha ha 😀 ) for the test:

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>build-helper-maven-plugin</artifactId>
	<version>1.4</version>
	<executions>
		<execution>
			<id>add-test-source</id>
			<phase>generate-sources</phase>
			<goals>
				<goal>add-test-source</goal>
			</goals>
			<configuration>
				<sources>
					<source>${basedir}/src/main</source>
				</sources>
			</configuration>
		</execution>
	</executions>
</plugin>

And finally we are there. What we gained is that

  • we tested the source code
  • we did mock the System.class
  • there is no production code left untested

The drawbacks are

  • we tested the code, but a different compilation, not the same .class
  • all production classes are compiled twice

The Conclusion

There was a code that used System static methods directly and needed testing. Usually those dependencies should be “moved far away” from the code and the code should be crafted bearing in mind the testability. I did not discuss how to make the code testable. I only focused on the technical issue: how to test the given class without modifying the source code.

What we test actually is the source code. Usually we do not make any distinction between testing the source code or testing the compiled class file. In this case there is difference and the approach found does test the source code compiled to a test class file. Does it matter?

When we execute unit tests we use mocks. The test class uses a to-be-mocked stub implementation of the class that we need to mock instead of the original. If we could mock the original the test would execute exactly the same way. For this reason I see no real risk in this approach. We never measure what we are interested in. In this case, how the code behaves in operation. You can not test that. Not only in practice, but also theory supports that: you can not tell if Schrödinger’s cat is dead or alive. Instead we measure something that is close to the thing we really are interested in. In this case we test the compiled code in a test environment. Now using this approach we test the source code compiled to a test class in a test environment. We moved a bit from the usual measuring point, however I believe that the move was tangential, and the distance did not change.

My final note below this article: if you need to mock static methods, and especially methods from the system class: start to consider refactoring the code.

Dobni, vagy nem dobni, az itt a kérdés

akkor nemesb-e a lélek, ha tűri balsorsa minden nyűgét, s nyilait;
Vagy ha kiszáll tenger fájdalma ellen, s hibát ragadva kivételt dob neki?

Elnézést kérek.

Szóval egy metódus dobjon-e inkább kivételt, vagy inkább adjon vissza olyan értéket, amelyik speciális? Jelezzen null érték visszaadásával, vagy legyen egy olyan metódusa az osztálynak, amelyik segít eldönteni, hogy a másik metódus meghívható-e, és ha nem hívható meg, de mégis meghívjuk, akkor viszont kapjunk kivételt? Sok ilyen kérdést lehet feltenni, és sokáig el lehet gondolkodni azon, hogy melyik API az, amelyik a hívó oldalon olvashatóbb kódot eredményez. Ez azonban nagyrészt pszichológia, hiszen nem azt akarjuk megmondani, hogy egy program működik-e, vagy sem, hanem azt, hogy majd az “átlag programozó”, ha ugyan létezik ilyen, milyen API-val fog jó kódot készíteni.

Csak így magunk közt szólva: semmilyennel, maximum működő kódot fog készíteni, és már az is a szerencsésebb esetek közé tartozik. Láttatok már távoli keleten készült kódot?

Az Effective Java könyv ad tanácsokat, de talán nem árt, ha egy kicsit jobban körbejárjuk ezt a témát. Nem biztos, hogy mindenben teljesen igaza van annak a könyvnek, illetve sok mondása olyan, hogy igaznak ugyan igaz, de amikor “szakemberek” hivatkoznak a könyvre, akkor már nem feltétlenül helyes, amit mondanak a könyv alapján. Pedig csak azt mondják, amit a könyv is mond, csak a környezet más. Ebben a cikkben megnézzük, hogy ez a könyv miket mond a kivételekről, és utána méréseket végzünk, hogy mibe kerül kivételt kezelni.

Effektív Jáva Kivételek

Item 57: Használjunk kivételt kivételes esetben. Ez teljesen igaz. Csak úgy, szíre-szóra nem szabad kivételt dobni, vagy olyan módon használni szerkezeteket, hogy nem ellenőrizzük például a null értéket, mert majd elkapjuk az NPE-t. Ha valahol egy kódban NPE van elkapva: az bűzlik. Az a kód nem optimális, sem sebességben, sem olvashatóságban, és így karbantarthatóságban sem.

Item 58: használjunk ellenőrzött (checked) kivételt minden olyan esetben, amiből ki lehet jönni, és futási kivételt (runtime exception), programozási hibára. Nem akarok ezzel vitába szállni, hallottam már olyan véleményt is, hogy az egész ellenőrzött kivétel, mint fogalom elhibázott. Nekem hiányozna.

Item 59: Ne használjunk feleslegesen ellenőrzött kivételeket. Erre hivatkozva hallottam a fenti állítást. Valójában azt mondja a könyv, hogy ha van rá mód, akkor elgondolkodhatunk azon, hogy a

try{
  obj.action(args);
  }catch(TheCheckedException e){
 ...
  }

szerkezet helyett használhatunk inkább

if( obj.actionPermitted(args) ){
  obj.action(args);
  }else{
 ...
  }

ha lehet. És ha az jó.

Item 60: Ha lehet, inkább használjunk szabványos kivételt és ne készítsünk sajátot. Ez elég evidens: nem csak a kivételeknél van ez így. Ha van egy osztály, amelyik megfelel a célunknak, akkor ne készítsünk magunknak egy másikat, csak azért, hogy legyen egy sajátunk. Egy Java osztály nem státusszimbólum.

Item 61: Az absztrakciós szintnek megfelelő kivételt dobjunk. Ez azt mondja, hogy a kivétel feleljen meg az osztály/metódus szintjének. Például egy kamatszámítási metódus ne dobjon IOException-t, akkor sem, ha nem tudja olvasni a számításhoz szükséges aktuális napi kamatkonfigurációs fájlt. Ilyen esetben az exception-t át kell csomagolni, hogy a metódus szignatúrája egységesen magas (vagy éppen alacsony) szintű legyen. Bár általában alacsony absztrakciós szintről nem szoktunk magas absztrackciós szintű osztályokat hívni.

Item 62: dokumentálni kell a kivételeket, amiket dobunk. Ez evidens, nem csak azt kell dokumentálni, hanem jószerével mindet, amit maga a kód nem dokumentál. Mondjuk nem mindenki szeret annyit gépelni, mint én.

Item 63: Tegyük bele a kivételbe a hibáról szóló információkat a részletes üzenetbe. Ez olyankor fontos, ha a hibát ember (is) kezelni fogja. Bekerül a naplófájlba, és valaki majd el fogja olvasni. Anno még a FORTRAN korban hozták oda hozzám az építész hallgatók a teljes oldal FORTRAN printout-ot, amire csak annyi volt a program alá nyomtatva: SYNTAX ERROR. Aztán keresd meg és holnap add le újra a lyukkártyákat. Szerencsére már nem itt tartunk, és ez a programozók érdeme. Maradjon is így.

Item 64: törekedjünk az atomi hiba kezelésre azaz, ha hibát dobunk, akkor az objektum még maradjon használható állapotban. Ez is elég evidens, de a mostani vizsgálat szempontjából annyira nem fontos.

Item 65: Ne hagyjuk figyelmen kívül a kivételeket. Ismerős ugye:

try{
  obj.action(args);
  }catch(Throwable t){
  }

?

Persze nem ennyire durván, csak ahogy az Eclipse vagy a NetBeans (vagy tetszőleges más IDE) előadja valamelyik kód template-je alapján. Akár még lehet is benne egy e.printStackTrace() ami egy alkalmazás szervere alatt aztán kiköt egy sose nézett napló fájlban, vagy a /dev/null-ban. Hát ne!

Mérjünk!

Mit mérjünk? Mérjünk, hogy merjünk kivételt dobni. Hívjunk meg mondjuk 10milliószor egy metódust, ami csak visszatér egy null értékkel, és mérjük meg, hogy mi történik, ha kivételt dob és azt el kell kapnunk.

Az első kód:

                        final long loopSize = 10_000_000;
...
			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				@SuppressWarnings("unused")
				Object z = nullReturner();
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with null");

a második pedig:

			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				try {
					@SuppressWarnings("unused")
					Object z = exceptionell();
				} catch (Exception e) {

				}
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with exception");

(Bevallom őszintén, és bűnbánóan, hogy ezek a benchmark kódok nem mennének át a sonar copy-paste szűrőjén. És bizony, már itt is van a 65. tétel megsértése: a kivétel ignorálása ebben az esetben kivételes eset, tisztelt bíróság …)

A két metódus pedig:

	private static Object exceptionell() throws Exception {
		throw new Exception();
	}

	private static Object nullReturner() {
		return null;
	}

Ha pedig futtatjuk, akkor a következő eredményt kapjuk:

13ms with null
12221ms with exception

Ez azért lényeges különbség! Kivételt dobni és elkapni ezerszer többe kerül, mint csak egy értékkel visszatérni. De igazából mi tart ilyen sokáig? Az kivétel létrehozása, az eldobása, vagy az elkapása?
Dobjunk egy olyan kivételt, amelyik előre el van készítve:

			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				try {
					@SuppressWarnings("unused")
					Object z = exceptionelle();
				} catch (Exception e) {

				}
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with constant exception");

és a metódus amit hívunk:

	final static private Exception e = new Exception();

	private static Object exceptionelle() throws Exception {
		throw e;
	}

Ennek eredménye:

22ms with constant exception

ami már nem annyira rossz! Persze ebben az esetben ne is álmodjunk arról, hogy az exception meg fogja mondani a stack trace-ben, hogy honnan dobták. Azt tudja csak, hogy hol készült, a stack milyen mélységében:

This is what exceptionelle throws:
java.lang.Exception
	at XceptionTest.<clinit>(XceptionTest.java:133)

Éppen ez a lényeg, hogy nem kell összegyűjtenie a stack tarce-t, szemmel láthatólag az tart sokáig. De ha olyan ellenőrzött kivételt dobunk, amelyiket el fognak kapni, akkor általában nincs szükségünk a stack trace-re. Persze ha egy static final kivételt dobunk, akkor abban nem tudunk átadni paramétereket. Azért ezt néha szeretnénk. (Már aki…)

Java 1.7 óta lehetőségünk van rá, hogy olyan kivételt hozzunk létre, amelyik nem gyűjti össze a stack trace-t magába. Ehhez egy saját kivétel osztályt kell létrehozni, és a megfelelő (protected) super konstruktort meghívni:

	private static Object exceptionella() {
		return new MyException();
	}

	private static class MyException extends Exception {
		private static final long serialVersionUID = 1L;

		public MyException() {
			super("nomessage", null/* cause */, false/* enableSupression */,
					false/* writableStackTrace */);
		}
	}

És a kód ami mér:

			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				try {
					@SuppressWarnings("unused")
					Object z = exceptionella();
				} catch (Exception e) {

				}
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with exception w/o stack trace");

(Milyen változatos ez a program…)

A futási eredmény

260ms with exception w/o stack trace

No, azért ez már nem olyan villám, mintha csak egy konstans kivétellel térnénk vissza, de mégis csak hússzoros és nem ezerszeres idő a kivétel nélküli esethez képest. De itt mi viszi el az időt? Az új kivétel objektum létrehozása, vagy magát a kivételt drága még így is létrehozni? Vagy a kivétel elkapása a drága? Nézzük meg, hogy mi a helyzet ha csak egy sima Object-t hozok létre:

	private static Object exceptionello() {
		return new Object();
	}

(Ennél egyszerűbb mért kód talán már nem is lehet.) És a mérőkód:

			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				@SuppressWarnings("unused")
				Object z = exceptionello();
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with new Object");

Az eredmény pedig:

15ms with new Object

Nem kerül túl sokba egy új objektum létrehozása. Majdnem semmi. Ebben a teszt környezetben biztonsággal nem is mérhető a különbség, de azért feltehető, hogy nem nulla és nem negatív idő alatt jön létre 🙂

Konklúzió

Kivételt létrehozni drága. Ha a nem elég gyors a programod, és tuningolni kell rajta, és azt mutatja profiler, hogy sok időt visz el a kivételek létrehozása és dobálása, akkor

  • Gondolkozz el azon, hogy lehet-e előre gyártott kivételeket dobni, ez lehet egy gyors tuning.
  • Ha az időt felhasználó kivételek saját kivételek, amelyek hurcolnak információt, akkor Java 1.7 alatt lehet használni a stack trace nélküli konstruktort. Ez egy picit több idő, de még mindig a gyors tuning.
  • Gondolkozz el azon, hogy miért dob ilyen gyakran kivételt a program? (Feltehetőleg nem tiszta a kód.)

Nem szabad, hogy ebbe a problémába ütközz. Ha tényleg a kivételek dobása viszi el az időt, akkor nézd meg, hogy van-e elegendő unit teszted, és ha van, akkor refactor, refactor, refactor. Ha nincs, akkor meg úgyis mindegy…

Prolog

Mint arra bölcs kollégánk az előző cikk kapcsán rámutatott ezek a mérések nem precíz benchmarkok. Nagyságrendileg lehet rájuk támaszkodni, azaz azt lehet mondani, hogy null értékkel visszatérni gyorsabb, mint kivételt dobni, de körülbelül ennyi. Az előző cikkekhez képest áttértem a nanoTime használatára, de nem gondolom, hogy az előző cikkek eredményei s a következtetések ne állnának meg. A teljes mérőprogram egy db. .java fájl, ha valakit érdekel itt van copy/paste:

public class XceptionTest {

	public static void main(String[] args) {
		Long timeStart, timeEnd, dTime;
		final long loopSize = 10_000_000;
		switch (args[0]) {
		case "1":
			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				@SuppressWarnings("unused")
				Object z = nullReturner();
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with null");
			break;
		case "2":
			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				try {
					@SuppressWarnings("unused")
					Object z = exceptionell();
				} catch (Exception e) {

				}
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with exception");
			break;
		case "3":
			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				try {
					@SuppressWarnings("unused")
					Object z = exceptionelle();
				} catch (Exception e) {

				}
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with constant exception");
			break;
		case "4":
			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				try {
					@SuppressWarnings("unused")
					Object z = exceptionella();
				} catch (Exception e) {

				}
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with exception w/o stack trace");
			break;
		case "5":
			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				@SuppressWarnings("unused")
				Object z = exceptionello();
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime + "ms with new Object");
			break;
		case "6":
			try {
				@SuppressWarnings("unused")
				Object z = exceptionelle();
			} catch (Exception e) {
				System.out.println("This is what exceptionelle throws:");
				e.printStackTrace();
			}
			break;
		case "7":
			timeStart = System.nanoTime();
			for (int i = 0; i < loopSize; i++) {
				try {
					@SuppressWarnings("unused")
					Object z = exceptionelln();
				} catch (Exception e) {
				}
			}
			timeEnd = System.nanoTime();
			dTime = (timeEnd - timeStart) / 1_000_000;
			System.out.println(dTime
					+ "ms null return but could throw exception");
			break;
		case "8":
			try {
				@SuppressWarnings("unused")
				Object z = exceptionell();
			} catch (Exception e) {
				System.out.println("This is what exceptionell throws:");
				e.printStackTrace();
			}
			break;
		case "9":
			try {
				@SuppressWarnings("unused")
				Object z = exceptionella();
			} catch (Exception e) {
				e.printStackTrace();
			}
			break;
		}
	}

	private static Object exceptionelln() throws Exception {
		return null;
	}

	private static Object exceptionello() {
		return new Object();
	}

	private static Object exceptionella() {
		return new MyException();
	}

	private static class MyException extends Exception {
		private static final long serialVersionUID = 1L;

		public MyException() {
			super("nomessage", null/* cause */, false/* enableSupression */,
					false/* writableStackTrace */);
		}
	}

	final static private Exception e = new Exception();

	private static Object exceptionelle() throws Exception {
		throw e;
	}

	private static Object exceptionell() throws Exception {
		throw new Exception();
	}

	private static Object nullReturner() {
		return null;
	}

}

A környezet, amin a mérést végeztem, ugyanaz, mint a múltkor:


A tesztek egy 8GB memóriával felszerelt MacBook Pro7,1 gépen futottak OS X 10.7.4, 7-es Java-val (ez utóbbit értő szem több helyen is észrevehette), de azért itt van egy ‘java -version’ kimenete:

verhasp:java verhasp$ java -version
java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)

[E] Does javac do optimization? Does not seem…

We usually say that java programmers have to write code that looks good and all other issues are solved by the compiler. For example having a complex boolean expression is better moved to a separate method with a good name and with a single return statement containing the expression. The original if or while will be much easier to understand. The java compiler is clever enough to see that the code is only called from a single place and will move the code inline.

Is it really true? I have heard that the JIT compiler does the optimization and the javac compiler does not. Let us have a look at some simple class:

public class OptimizeThis {
	private int a(int x, int y) {
		return x + y;
	}

	public int add(int x, int y, int z) {
		return a(a(x, y), z);
	}
}

There is a lot of space for optimization. The method a() could be left out from all the fun. The code could be included in the method add() and the code would be much faster.
Something like this:

public class Optimized {
	public int add(int x, int y, int z) {
		return x + y + z;
	}
}

Let us compile the class OptimizeThis and disassemble using javap:

verhasp:java verhasp$ javac OptimizeThis.java
$ javap -v -p OptimizeThis.class
Classfile /Users/verhasp/.../src/main/java/OptimizeThis.class
  Last modified 2012.07.08.; size 327 bytes
  MD5 checksum 9ba33fe0979ff0948a683fab2dc32d12
  Compiled from "OptimizeThis.java"
public class OptimizeThis
  SourceFile: "OptimizeThis.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         //  java/lang/Object."<init>":()V
   #2 = Methodref          #3.#16         //  OptimizeThis.a:(II)I
   #3 = Class              #17            //  OptimizeThis
   #4 = Class              #18            //  java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               a
  #10 = Utf8               (II)I
  #11 = Utf8               add
  #12 = Utf8               (III)I
  #13 = Utf8               SourceFile
  #14 = Utf8               OptimizeThis.java
  #15 = NameAndType        #5:#6          //  "<init>":()V
  #16 = NameAndType        #9:#10         //  a:(II)I
  #17 = Utf8               OptimizeThis
  #18 = Utf8               java/lang/Object
{
  public OptimizeThis();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  private int a(int, int);
    flags: ACC_PRIVATE
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LineNumberTable:
        line 3: 0

  public int add(int, int, int);
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=4, args_size=4
         0: aload_0
         1: aload_0
         2: iload_1
         3: iload_2
         4: invokespecial #2                  // Method a:(II)I
         7: iload_3
         8: invokespecial #2                  // Method a:(II)I
        11: ireturn
      LineNumberTable:
        line 7: 0
}
verhasp:java verhasp$

You can see we have both of the methods. The code

  private int a(int, int);
    flags: ACC_PRIVATE
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn

is the private method a() and the code

  public int add(int, int, int);
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=4, args_size=4
         0: aload_0
         1: aload_0
         2: iload_1
         3: iload_2
         4: invokespecial #2                  // Method a:(II)I
         7: iload_3
         8: invokespecial #2                  // Method a:(II)I
        11: ireturn

is the public method add(). The code itself is simple. The method a() loads on the operand stack the first local variable (iload_1), then it does the same with the second (iload_2), and then adds the two (iadd). What is left on the operand stack is used to return (ireturn).

  1. the local variable number zero is this in case of non-static methods
  2. the arguments are also treated as local variables
  3. for the first few local variables there are shorthand java byte codes, because the generated code accesses these the most and this saves some space and speed
  4. we are using int only and thus we need not care about more complex issues, like a double occupying two slots.

Them method add() is almost as simple. Loads the value of this on the operand stack two times. It is needed to call the non-static method a(). After that it loads the first and the second local variable on the stack (the first two method arguments) and in the command #4 (line 61.) calls the method a(). After this it loads the third local variable on the stack. This time the stack contains the variable this, the result of the previous call to method a() and then the third local variable, which is the third argument to the method add(). Then it calls the method a().

Let us have a look at the code generated from the class Optimized:

$ javap -v -p Optimized.class
Classfile /Users/verhasp/.../src/main/java/Optimized.class
  Last modified 2012.07.08.; size 251 bytes
  MD5 checksum 2765acd1d55048184e9632c1a14a8e21
  Compiled from "Optimized.java"
public class Optimized
  SourceFile: "Optimized.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#12         //  java/lang/Object."<init>":()V
   #2 = Class              #13            //  Optimized
   #3 = Class              #14            //  java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               add
   #9 = Utf8               (III)I
  #10 = Utf8               SourceFile
  #11 = Utf8               Optimized.java
  #12 = NameAndType        #4:#5          //  "<init>":()V
  #13 = Utf8               Optimized
  #14 = Utf8               java/lang/Object
{
  public Optimized();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public int add(int, int, int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: iload_1
         1: iload_2
         2: iadd
         3: iload_3
         4: iadd
         5: ireturn
      LineNumberTable:
        line 3: 0
}

Much simpler. Is it also faster? The proof of the pudding is in the eating. If it is not tasty then the dog will eat it. However…

Here we have again the two classes extended with some main methods (one for each).

public class OptimizeThis {
	private int a(int x, int y) {
		return x + y;
	}

	public int add(int x, int y, int z) {
		return a(a(x, y), z);
	}

	public static void main(String[] args) {
		OptimizeThis adder = new OptimizeThis();
		final int outer = 100_0000_000;
		final int loop = 100_0000_000;
		Long tStart = System.currentTimeMillis();
		for (int j = 0; j < outer; j++) {
			for (int i = 0; i < loop; i++) {
				int x = 1;
				int y = 2;
				int z = 3;
				adder.add(x, y, z);
			}
		}
		Long tEnd = System.currentTimeMillis();
		System.out.println(tEnd - tStart);
	}
}

and

public class Optimized {
	public int add(int x, int y, int z) {
		return x + y + z;
	}

	public static void main(String[] args) {
		Optimized adder = new Optimized();
		final int outer = 100_0000_000;
		final int loop = 100_0000_000;
		Long tStart = System.currentTimeMillis();
		for (int j = 0; j < outer; j++) {
			for (int i = 0; i < loop; i++) {
				int x = 1;
				int y = 2;
				int z = 3;
				adder.add(x, y, z);
			}
		}
		Long tEnd = System.currentTimeMillis();
		System.out.println(tEnd - tStart);
	}
}

In addition to this we also created an empty class, named Empty that returns constant zero.

public class Empty {
	public int add(int x, int y, int z) {
		return 0;
	}

	public static void main(String[] args) {
		Empty adder = new Empty();
		final int outer = 100_0000_000;
		final int loop = 100_0000_000;
		Long tStart = System.currentTimeMillis();
		for (int j = 0; j < outer; j++) {
			for (int i = 0; i < loop; i++) {
				int x = 1;
				int y = 2;
				int z = 3;
				adder.add(x, y, z);
			}
		}
		Long tEnd = System.currentTimeMillis();
		System.out.println(tEnd - tStart);
	}
}

Here we have an executing script that can be called after executing the command javac *.java:

#! /bin/sh
echo "Empty"
java Empty
echo "Optimized"
java Optimized
echo "OptimizeThis"
java OptimizeThis

And the result:
STOP!!!! Before you open it try to estimate the ration between the optimized and non-optimized version and also how much faster the class Empty is. If you have your estimation you can open the result:

verhasp:java verhasp$ ./testrun.sh
Empty
1970
Optimized
1987
OptimizeThis
1970
verhasp:java verhasp$ ./testrun.sh
Empty
1986
Optimized
2026
OptimizeThis
2001
verhasp:java verhasp$ ./testrun.sh
Empty
1917
Optimized
1892
OptimizeThis
1899
verhasp:java verhasp$ ./testrun.sh
Empty
1908
Optimized
1903
OptimizeThis
1899
verhasp:java verhasp$ ./testrun.sh
Empty
1898
Optimized
1891
OptimizeThis
1892
verhasp:java verhasp$ ./testrun.sh
Empty
1896
Optimized
1896
OptimizeThis
1897
verhasp:java verhasp$ ./testrun.sh
Empty
1897
Optimized
1903
OptimizeThis
1897
verhasp:java verhasp$ ./testrun.sh
Empty
1908
Optimized
1892
OptimizeThis
1900
verhasp:java verhasp$ ./testrun.sh
Empty
1899
Optimized
1905
OptimizeThis
1904
verhasp:java verhasp$ ./testrun.sh
Empty
1891
Optimized
1896
OptimizeThis
1896
verhasp:java verhasp$ ./testrun.sh
Empty
1895
Optimized
1891
OptimizeThis
1904
verhasp:java verhasp$ ./testrun.sh
Empty
1898
Optimized
1889
OptimizeThis
1894
verhasp:java verhasp$ ./testrun.sh
Empty
1917
Optimized
1894
OptimizeThis
1898
verhasp:java verhasp$

Conclusion? Before you vote on the first choice read all the possible answers!


The tests were executed on a 8GB memory MacBook Pro7,1 with OS X 10.7.4, 7-es Java (you could notice it that it was already java7) Still here you can have the output of ‘java -version’:

verhasp:java verhasp$ java -version
java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)


Next time something more interesting.

[E] A little bit of testing

Behaviour Driven Development usually says that the test structure consists of

  • GIVEN
  • WHEN
  • THEN

Even Mockito has an DBBMockito façade implementation that fits to this habit. If you want a big shot you can have JBehave. However for now let us starts with some simple unit test:

    private static final Configuration config = new BasicConfiguration();
    private static final String key = "key";
    private static final String value = "value";
    private static final String badValue = "badValue";
...
    public void testGetConfigValueWhenEnvironmentKeyExists() {
        String actual;
        Properties props;
        // GIVEN
        props = PowerMockito.mock(Properties.class);
        PowerMockito.mockStatic(System.class);
        config.setConfigProperties(props);
        Mockito.when(props.containsKey(key)).thenReturn(true);
        Mockito.when(props.getProperty(key)).thenReturn(badValue);
        Mockito.when(System.getenv("sb4j." + key)).thenReturn(value);
        // WHEN
        actual = config.getConfigValue(key);
        // THEN
        Mockito.verify(props).containsKey(key);
        Mockito.verify(props).getProperty(key);
        PowerMockito.verifyStatic();
        System.getenv("sb4j." + key);
        Assert.assertEquals(value, actual);
    }

Do you feel something under you skin that this is not the real one? What are those comments? Whenever I feel the need to write comments into a method I start to think about better variable and method names and refactoring. Why is this method so complex that this is hard to understand without the comments? Yes, sure: I can remove those three comment lines, but even then it is not really an improvement.

What if we could create a simple Business class and we could book our ticked from the economy class to business class? We are not even forced to finish the class, it is OK half way ready:

public abstract class Business {

    abstract public void given();
    abstract public void when();
    abstract public void then();
    public void execute(){
        given();
        when();
        then();
    }

}

It is so small that it just fits any project. Whenever we have it the test code looks much better:

    @Test
    public void testGetConfigValueWhenEnvironmentKeyExists() {
        new Business() {
            private String actual;
            private Properties props;

            @Override
            public void given() {
                props = PowerMockito.mock(Properties.class);
                PowerMockito.mockStatic(System.class);
                config.setConfigProperties(props);
                Mockito.when(props.containsKey(key)).thenReturn(true);
                Mockito.when(props.getProperty(key)).thenReturn(badValue);
                Mockito.when(System.getenv("sb4j." + key)).thenReturn(value);
            }

            @Override
            public void when() {
                actual = config.getConfigValue(key);
            }

            @Override
            public void then() {
                Mockito.verify(props).containsKey(key);
                Mockito.verify(props).getProperty(key);
                PowerMockito.verifyStatic();
                System.getenv("sb4j." + key);
                Assert.assertEquals(value, actual);
            }

        }.execute();
    }

Much better, is it. The only question remaining: who in the hell could we mock the System class? Believe me: the code above is copy/paste without modification from a real live test and it works. Stay tuned for some coming posts where I detail how it was made.

Egy kis tesztelés

A Behaviour Driven Development módszertanban szokás, hogy a teszt kód alapja

  • GIVEN
  • WHEN
  • THEN

alakú. Még a Mockitónak is van DBBMockito façade implementációja, ami ehhez a szokáshoz igazodik. Sőt ha valaki akar egy nagyágyút, nos ott a JBehave. De most csak egy sima kis unit teszt:

    private static final Configuration config = new BasicConfiguration();
    private static final String key = "key";
    private static final String value = "value";
    private static final String badValue = "badValue";
...
    public void testGetConfigValueWhenEnvironmentKeyExists() {
        String actual;
        Properties props;
        // GIVEN
        props = PowerMockito.mock(Properties.class);
        PowerMockito.mockStatic(System.class);
        config.setConfigProperties(props);
        Mockito.when(props.containsKey(key)).thenReturn(true);
        Mockito.when(props.getProperty(key)).thenReturn(badValue);
        Mockito.when(System.getenv("sb4j." + key)).thenReturn(value);
        // WHEN
        actual = config.getConfigValue(key);
        // THEN
        Mockito.verify(props).containsKey(key);
        Mockito.verify(props).getProperty(key);
        PowerMockito.verifyStatic();
        System.getenv("sb4j." + key);
        Assert.assertEquals(value, actual);
    }

Nem érzel valami bizsergést, hogy ez így nem az igazi? Mi az a három komment? Ha egy metódusba kommentet akarok írni, bele a belsejébe, akkor mindig elgondolkodom rajta, hogy miért is? Mi történt? Miért olyan bonyolult ez a metódus, hogy fel kell kommentezni? Persze, ja, hogyne… ki is vehetem azt a három sort, de attól nem lesz jobb, maximum átmenetileg tisztább, szárazabb az érzés, de a görcs megmarad.

Mi lenne, ha egy baromi egyszerű, kis Business class-t készítenénk, és átszállnánk a turista osztályról. Nem is kell teljesen elkészíteni, félbe is hagyhatjuk:

public abstract class Business {

    abstract public void given();
    abstract public void when();
    abstract public void then();
    public void execute(){
        given();
        when();
        then();
    }
    
}

Ez igazán nem sok, minden projektbe belefér. És ha ez megvan, akkor a teszt már sokkal szebb:

    @Test
    public void testGetConfigValueWhenEnvironmentKeyExists() {
        new Business() {
            private String actual;
            private Properties props;

            @Override
            public void given() {
                props = PowerMockito.mock(Properties.class);
                PowerMockito.mockStatic(System.class);
                config.setConfigProperties(props);
                Mockito.when(props.containsKey(key)).thenReturn(true);
                Mockito.when(props.getProperty(key)).thenReturn(badValue);
                Mockito.when(System.getenv("sb4j." + key)).thenReturn(value);
            }

            @Override
            public void when() {
                actual = config.getConfigValue(key);
            }

            @Override
            public void then() {
                Mockito.verify(props).containsKey(key);
                Mockito.verify(props).getProperty(key);
                PowerMockito.verifyStatic();
                System.getenv("sb4j." + key);
                Assert.assertEquals(value, actual);
            }

        }.execute();
    }

Ugye mennyivel szebb? Már csak egy kérdés maradt: hogy sikerült a System final osztályt statikusan mockolni a powermock-kal? De ez már egy másik történet, ráment négy óra. Most hétvégére legyen csak egy ilyen könnyű cikk, jövő hétre a csőben van egy kis kivételkezelési sebesség, de utána elmondom, hogy hogyan is tud működni ez a teszt. Lesz benne classloader, maven, eclipse … “és kakast is!”.