Discussion Utilisateur:Stephane

Un article de Agile-Swiss.

Jump to: navigation, search

Sommaire

JUnitX: "A Brief tutorial..."

     The purpose of this little tutorial is to fill the lack (as modestly as possible) of documentation concerning JUnitX ( http://www.extreme-java.de ). For my part, I discovered it in 2001. It is an extension to JUnit allowing to foil the access restrictions and so to be able to test private methods (or protected) as well as methods inside inner private classes (or protected of course). All this without polluting classes to test. It is, of course, a bad practice to abuse of this kind of tests, but sometimes it happens that is embarrassing to turn a method public to test it only to reassure oneself of its efficiency. (even if we unit test the public method using it).

     OK, you are about to say : "If a private method needs unit tests, this method could probably be better implemented and be public" I used to think that way and I haven't choose my camp yet, but it's not the purpose of this tutorial : we are going to details the ways to use JUnitX if you need it, not the "why" of its use. Ready ? ...


Principle

    This API heavily relies on reflection. Why ? Simply because on objects of Class type, methods such as getDeclaredMethods, getDeclaredClasses and getDeclaredFields offer a lot of possibilities. getDeclaredMethods for instance returns the names of ALL methods of a class, NOT ONLY public ones. This method returns a nice array Method[] with all usual capacities available on such objects. In our case, the one that interests us is invocation. getDeclaredClasses on the other side, gives the names of ALL inner classes/interfaces declared in the class this method is called on. They can be public, protected, default (package) access or private.


    You guess the usage of getDeclaredFields and imagine the possibilities that come by mixing calls to these 3 methods. One can ask of the reason of this weakness of reflection that permits things usually "forbidden" by language (done on purpose by language creators ?, Side effect ?), but we must admit that for our concern this is of a great help.

    To avoid the need of modifying the code of tested classes, we need to generalize this kind of calls. JUnitX authors had the idea of having a proxy in the same package as these classes to access their private parts. The necessary constructions/invocations in the test-cases are done via this proxy.

Let's use a schema to better visualize the JUnitX architecture (by the way, it contains very few classes)


Image:JUnitXArch.png


After the internals of JUnitx, let's see its usage....

Simple case : noargs private method

     Let's assume we have the following case:


Image:JunitX_ex1.png


    The isElvisAlive method must return the boolean 'true' (!!), and for reasons known only by its creators, this method will be private to hide the truth from those who would be tempted to discover it. Even if it seems that these authors know a lot of things on many topics, they remain developers subject to mistakes : they write immediately the test. But how test this ... secret ?

First thing : they create a TestProxy class (Listing 1.1) in the same package. (too bad for them they haven't read next sections yet... and do not know JUnitX 5.2)


Listing 1.1

   package com.mib.xfiles;
   
   import junit.framework.*;
   import junitx.framework.*;
   
   public class TestProxy extends junitx.framework.TestProxy{
   
     public Object newInstance (Object[] anArgList) throws TestAccessException {
       try {
         return getProxiedClass().getConstructor (anArgList).newInstance (anArgList);
       }catch (Exception e){
         throw new TestAccessException ("could not instantiate " + getTestedClassName(), e);
       }
     }
   
     public Object newInstanceWithKey (String aConstructorKey, Object[] anArgList)throws TestAccessException{
       try{
         return getProxiedClass().getConstructor (aConstructorKey).newInstance (anArgList);
       }catch (Exception e){
         throw new TestAccessException ("could not instantiate " + getTestedClassName(), e);
       }
     }
   }

The code of this class is the same for all TestProxy classes MiB need, in any package. Of course, they take care not to deploy these classes ! Otherwise, secrets would not be safely kept.

Listing 1.2 details MusicWorldTest test class :

Listing 1.2

   package com.mib.xfilestests;
   
   import junit.framework.*;
   import junitx.framework.*;
   
   import com.mib.xfiles.MusicWorld;
   
   public class MusicWorldTest extends PrivateTestCase {
       
       public MusicWorldTest(java.lang.String testName) {
           super(testName);
       }
       
       public void testElvisAlive()throws TestAccessException{
           MusicWorld musicWorld = new MusicWorld();
           boolean veritas = asBoolean(invoke(musicWorld, "isElvisAlive", NOARGS));
           assertTrue(veritas);
       }
       
   }


Let's detail line :

boolean veritas = asBoolean(invoke(musicWorld, "isElvisAlive", NOARGS));

    invoke takes as first argument the object (musicWorld) on which we call the method, the second is the name of the method "isElvisAlive". Would this method have parameters they would appear as the third argument of invoke. Here, as NOARGS constant suggests, no argument. Nothing too complex for the moment ? OK there remains the asBoolean. This methods wraps a primitive type and its equivalent object. Invoke alway returns an Object. If the method to test returns an object (and not a primitive type) a simple cast will do the trick. If it returns a primitive type, you must use the corresponding wrapper.

Our Mib development team is now reassured : after they code and test isElvisAlive method, check that the previous test passed successfully, the truth and its secret are now guaranteed.

Private method with arguments

Object argument

No time for a rest !, a new test (at least as important as Elvis health) needs to be added:


Image:JunitX_ex2.png



    This method getAlienThatLiveIn (which is about to disappoint many fans...) takes a String as a parameter. The test (Listing 2.1) is a little different from previous one. It is just about passing an array of objects (String) as the last argument of invoke method. Note also that this time, the dev team gets the result by casting to String the returned value because the returned type is an object. No need here to use asSomeThing to wrap this value.


Listing 2.1

   public void testAliens()throws TestAccessException{
       MusicWorld musicWorld = new MusicWorld();
       String alienName = (String) invoke(musicWorld, "getAlienThatLiveIn", new Object[]{"Elvis"});
       assertEquals("Ublitonum Frividous (from Planet Astra V12)", alienName);
   }


Please note that to use invoke with arguments, they all must be objects. In the case where at least one argument is a primitive type, another method must be used. Fortunately, our developers from MiB have more tricks to tell us...

Primitive Argument

For each planet there is an alien ambassador on earth disguised as an artist. Mib have a method that takes a planet code and return the name of the alien-artist ambassador.


Image:JunitX_ex3.png


    The test (Listing 2.2) must now take into account the primitive argument (int). First, the method to use is invokeWithKey et not invoke. Second, a primitive type is wrapped by its corresponding class, that is why the team passes as an argument an array of Objects with only one element : an Integer. Wait !, that's not enough. Look close to the code from our collegues : the name of the method passed as the second argument to invokeWithKey is different from what you have seen so far : the primitive type is specified just after. Do not forget it !


Listing 2.2

   public void testAmbassadorFromPlanetCode()throws TestAccessException{
       MusicWorld musicWorld = new MusicWorld();
       String peopleName = (String) invokeWithKey(musicWorld, "getAmbassadorComingFromPlanetCode_int", new Object[]{new Integer(12)});
       assertEquals("Elvis", peopleName);
       peopleName = (String) invokeWithKey(musicWorld, "getAmbassadorComingFromPlanetCode_int", new Object[]{new Integer(10)});
       assertEquals("We don't know anyone coming from this planet!", peopleName);        
   }


Note that if the getAmbassadorComingFromPlanetCode method would have had 2 arguments, one primitive and one String (just as an example), the team would have needed to specify both types after the name of the method leading to following call :

String peopleName = (String) invokeWithKey(musicWorld, "getAmbassadorComingFromPlanetCode_int_java.lang.String", new Object[]{new Integer(12), "GloubiWay"});

One last remark : if the previous methods would have had been protected, our Mib team would have tested the same way. But paranoiac as they are, private is preferred; these methods are probably only used for statistics and the only public things to be published will be hard to understand for the vast majority of us, simple earthmen ;)

Access to protected inner classes and to their private methods

    This team is stranger that you would dream of : they would have liked to test private methods inside private inner classes. But this turns out to be impossible with JUnitX (or with reflection) : An inner private class is one of the most locked things in Java (perhaps the only thing not accessible, at least MiB team still hasn't found, if you figured out how to do this, transmit me the message, and I will forward inside an anonymous envelop)

They resigned to change their inner-classes to protected ones with private methods.

You may have noticed, in previous examples, that the object to test was instanciated in the classic way because this object was accessible from everywhere (public class):

MusicWorld musicWorld = new MusicWorld();

But when it comes to instantiate a protected object encapsulated in a public object, it's another story. For example, the MusicWorld class contains a specific object from a world appart : Rapworld (Listing 3.1); an inner protected class. A private method of this class is in charge of breaking the 2pac code.

Listing 3.1

   public class MusicWorld {
       
       protected class RapWorld{
           
           public RapWorld(){
           }
       
           public RapWorld(String side){
               System.out.println("side is " + side);
           }
       
           private boolean isTupacAlive(){
              return true;
           }       
       
       }
   
       /** Creates a new instance of MusicWorld */
       public MusicWorld() {
       }

       //les autres méthodes de MusicWorld vues précedemment... 
      
   }


At first glance, access this method is a sort of extra-terrestrian pirouette but the hardest part is RapWorld instantiation (Listing 3.2), invoke methods remain the same.


Listing 3.2

   public void testTupacAlive()throws TestAccessException{
       Object[] outer = {new MusicWorld()};
       Object   inner = newInstance("com.mib.xfiles.MusicWorld$RapWorld", outer);        
       assertTrue(asBoolean (invoke (inner, "isTupacAlive", NOARGS)));        
       
       outer = new Object[]{new MusicWorld(), "West side"};
       inner = newInstanceWithKey("com.mib.xfiles.MusicWorld$RapWorld", "_com.mib.xfiles.MusicWorld_java.lang.String", outer);
       assertTrue(asBoolean (invoke (inner, "isTupacAlive", NOARGS)));       
   }

Let's detail the arguments of newInstance and newInstanceWithKey to play on the same level than MiB.

Concerning newInstance:

  • The first argument represents the full name of the inner class to test (written under the form package.ClasseParente$ClasseFille).
  • You also need to know that compiler adds as first argument to constructor of inner class the instance of parent class.

That's why we use an array containing only an instance of MusicWorld as second argument of newInstance.

Concerning newInstanceWithKey:

  • The first argument represents the fully qualified names of the inner class to be tested (under the form package.ClasseParente$ClasseFille).
  • The second argument represents the key of the constructor to use (JUnitX key formed by parameters types prefixed by '_', including parent class type, type of first argument).
  • The last argument represents the list of paramters values for this constructor, including the famous first parameter added to constructor at compilation time, ie parent class instance.


The lab obtains the following result :


Image:junitguijunitx.png

Further investigation

    The daily use of JUnitX can lead to complex cases, and without the help of our friends from xp-swiss, it can become tricky. In this section, we will attempt to detail some cases, not all, but the ones we have aleady encountered ...


Interface or Superclass as method argument

    We have seen (Listing 2.2) that invokeWithKey allowed to call an inaccessible method with at least one primitive argument. There is another case this kind of call may be useful for. Let's consider this :

Image:Interfaceorsuperclassjunitx.png

    To test getMostFamousForWorld, the main concern is that using invoke is not enough. Indeed, reflection works by using the deepest possible class in inheritance hierarchy. If we pass new Object[]{new MusicWorld()} to invoke method, reflection will look for method whose signature is getMostFamousForWorld(com.mib.xfiles.MusicWorld world) instead of getMostFamousForWorld(com.mib.xfiles.MediaWorld world).

One way to overcome this problem is to force the wanted signature by using invokeWithKey with signature getMostFamousForWorld_com.mib.xfiles.MediaWorld as an argument : it's done ! (cf. Listing 4.1). It turned out that relying on your reflection proved more efficient, congratulations ! ;-)

'Listing 4.1'

   public void testGetMostFamousForWorld() throws TestAccessException{
       MibHq mibHQ = new MibHq();
       MusicWorld musicWorld = new MusicWorld();
       //the below line doesn't work because the reflexion expects a MusicWorld object as argument
       //assertEquals("Elvis", (String) invoke(mediaWorld, 
       //                                      "getMostFamousForWorld", 
       //                                      new Object[]{musicWorld}));
       assertEquals("Elvis", (String) invokeWithKey(mibHQ,  
                                                    "getMostFamousForWorld_com.mib.xfiles.MediaWorld", 
                                                    new Object[]{musicWorld}));
   }

Test a private field without accessors

    Do not ask me when this can happen, it happens. Suppose you want to check the background color of a JButton (no reason to make it accessible outside its class). e.g., MiB, after they have coded a MMI for a form downloadable by aliens, want to verify that the initial state of their MMI contains the following : a JLabel with the question "Upon my conscience(s), I affirm to be ...", a green button labeled "A nice alien" and a red one "a dangerous and vicious psychopath"

Acces to these private fields is achieved via get method available in all classes that override PrivateTestCase.

For an instance variable :

   get(Object anInstance, String fieldName) : the first argument is the instance for which 
                                                  we want to get a reference on field called 'fieldName'


For a static class variable

   get(String aClassName, String fieldName) : first argument is the fully qualified name(package included) of class 
                                                  for which we want to get a reference on field called 'fieldName'

In our case, if green button is declared as this in WhatAlienAmIDialogBox class :

   JButton btGoodAlien;

we can test its background color with :

   assertEquals(Color.GREEN, 
                ((JButton) get(whatAlienAmIDialogBoxInstance, "btGoodAlien")).getBackground());

At this point, and with your level of expertise on JUnitX, you have probably guessed that if we access a primitive private field, we use asXXX() to obtain its value.

   boolean myPrivateBooleanValue = asBoolean( get(whatAlienAmIDialogBoxInstance, "myPrivateBoolean")) );

Test a static private method

    static, therefore no need for an instance. With JUnitX <= 5.1, it is necessary to create an instance to invoke this kind of methods. The problem happens when the tested class doesn't exhibit any constructor neither public factory method.


     A new version of JUnitX JUnitX 5.2 (jar alone available here) makes static methods invocation possible without instanciating tested class.

You just need to call invokeStatic or invokeStaticWithKey like invoke and invokeWithKey. The only difference is that the first argument is now the class name (an object of Class type) and no longer the instance.

To test that getRandomNote of MusicWorld returns a value comprised in a list of well known values (e.g {"A", "B", "C", "D", "E", "F", "G") :

   assertTrue( notesList.contains(invokeStatic(MusicWorld.class, "getRandomNote", NOARGS)) );

Rethrow exceptions

    Private methods we want to test might throw RuntimeException. If one of these is thrown as JUnit runs (Listing 4.2 et 4.3), the exception will be a TestAccessException and the window showing failures will only give few info, e.g :

   junitx.framework.TestAccessException: could not invoke com.mib.xfiles.MusicWorld.throwARuntimeException , 
                                         reason: java.lang.reflect.InvocationTargetException


If you want to get info on what really happened, a good practice of JUnitX is described in listing Listing 4.4. You must catch exceptions specific to JUnitX (TestAccessException), extract reason and cause and propagate the last one.


Listing 4.2 (method to test)

   public boolean throwARuntimeException(){
       if(true){
           throw new RuntimeException("you should read this RuntimeException message");
       }
       return true;
   }


Listing 4.3 (wrong propagation of exception)

   public void testRuntimeException() throws TestAccessException{
       MusicWorld musicWorld = new MusicWorld();
       assertTrue(asBoolean(invoke(musicWorld, "throwARuntimeException", NOARGS)));
   }


Listing 4.4 (good practice)

   public void testRuntimeException() throws Throwable{
       try{
           MusicWorld musicWorld = new MusicWorld();
           assertTrue(asBoolean(invoke(musicWorld, "throwARuntimeException", NOARGS)));            
       }catch(TestAccessException tae){
           throw tae.getReason().getCause();
       }      
   }    

And you can admire the nice trace in your logs window :

   java.lang.RuntimeException: you should read this RuntimeException message

with the full stacktrace of course. Everything you need !

JUnitX 5.2 -> No need for TestProxy !

    Yes, we have waited until now to unveil the good news...... JUnitX 5.2 (jar alone available here) allows to do without TestProxy classes that pollute your packages ! I have patched the last version of JUnitX sources (available on www.extreme-java.de).

    JUnitX version 5.2 uses javassist to avoid the creation of such classes : they are automatically created, compiled and placed in tests classpath at runtime. No file is created on disk, even temporarily, all is done in JVM.

    This new version is backward compatible, if some TestProxy classes remain in your packages, they will be used. But you may now delete them. ;)

Warning : Do not forget to include jar from javassist in your classpath. If not, JUnitX behavior will be unchanged, looking for a TestProxy class in each package to test, and if none are found, won't be able to create it at runtime.

Note that JUnitX project seemed abandoned. We have tried numerous times to contact Andreas Heilwagen unsuccessfully (to send him the patch for static methods and automatic TestProxy creations), so we have decided to publish it on this site.


Conclusion

    In this article, you have seen the main thing to unleash the test driven developer in you. If you really need to test a private method, and turning it public gives you headache, you now have another option. Not all cases have been treated here, and you will find other kind of parameters, other methods for which you will need to dig into the code. The rest is up to you, JUnitX and reflection. Do not forget that the best documentation is the source code itself and the associated test cases. Another example where tests become the documentation of an API !


( Some source from this article )