XNSIO
  About   Slides   Home  

 
Managed Chaos
Naresh Jain's Random Thoughts on Software Development and Adventure Sports
     
`
 
RSS Feed
Recent Thoughts
Tags
Recent Comments

Archive for the ‘Java’ Category

Mocking only Abstract Methods using Mockito (Partial Mocking)

Thursday, December 5th, 2013

I remember back in the days, before any mocking frameworks existed in Java, we used to create an anonymous-inner class of an abstract class to fake-out the abstract method’s behaviour and use the real logic of the concrete method.

This worked fine, except in cases where we had a lot of abstract methods and overriding each of those methods to do-nothing or return dummy value seemed like a complete waste of time.

With the mocking frameworks like Mockito, we have a better way to deal with such situations, esp. in legacy code. But there is a catch. Let me explain it to you via a code example.

public abstract class AbstractClazz {
	public String sayHello() {
		return "Hello";
	}
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethods() {
		AbstractClazz clazz = mock(AbstractClazz.class);
		assertEquals("Hello", clazz.sayHello());
	}
}

This test fails, with the following error:

java.lang.AssertionError: expected:<Hello> but was:<null>
	at org.junit.Assert.fail(Assert.java:91)
	at org.junit.Assert.failNotEquals(Assert.java:645)
	at org.junit.Assert.assertEquals(Assert.java:126)
	at org.junit.Assert.assertEquals(Assert.java:145)
	at com.agilefaqs.mocking.AbstractClazzTest.
	shouldNotFakeRealMethods(AbstractClazzTest.java:20)

To make this work, We need to pass the following Answer parameter while creating the Mock:

AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS);

Now let’s say our requirements have evolved. Our sayHello() method should also add the person’s name and greet. Different implementations will figure out different ways to fetch the person’s name.

public abstract class AbstractClazz {
	public String sayHello() {
		return "Hello " + fetchName() + "!";
	}
 
	protected abstract String fetchName();
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethodsAndFakeAbstractMethod() {
		AbstractClazz clazz = mock(AbstractClazz.class, CALLS_REAL_METHODS);
		when(clazz.fetchName()).thenReturn("Naresh");
		assertEquals("Hello Naresh!", clazz.sayHello());
	}
}

The moment we run this test, we get the following error:

java.lang.AbstractMethodError: 
	com.agilefaqs.mocking.AbstractClazz.fetchName()Ljava/lang/String;
	at com.agilefaqs.mocking.AbstractClazzTest.
	shouldCallRealMethodsAndFakeAbstractMethod(AbstractClazzTest.java:22)

Basically, we need our mocking framework to give us a mock which allows partial mocking. Which means, for some methods we want the real methods to be invoked and for some, we want to use the fake implementation.

One way to implement this is by creating a default mock and then explicitly setting expectation on real methods.

@Test
public void shouldCallRealMethodsAndFakeAbstractMethod() {
	AbstractClazz clazz = mock(AbstractClazz.class);
	when(clazz.sayHello()).thenCallRealMethod();
	when(clazz.fetchName()).thenReturn("Naresh");
	assertEquals("Hello Naresh!", clazz.sayHello());
}

But the main problem with this approach is that we need to ensure we set explicit expectations on all public and protected methods which might be internally called by the main method (sayHello in this case.) To make matters worse, private methods can’t be mocked and hence we can’t set expectations on them. But let’s say at a later point, if someone makes a private method protected/public, the test will fail, as it will now get mocked. Overall this strategy can make your tests extremely fragile.

For example the following works:

public abstract class AbstractClazz {
	public String sayHello() {
		return "Hello " + fetchName() + closingSymbol();
	}
 
	private String closingSymbol() {
		return "!";
	}
 
	protected abstract String fetchName();
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethodsAndFakeAbstractMethod() {
		AbstractClazz clazz = mock(AbstractClazz.class);
		when(clazz.sayHello()).thenCallRealMethod();
		when(clazz.fetchName()).thenReturn("Naresh");
		assertEquals("Hello Naresh!", clazz.sayHello());
	}
}

However if we change closingSymbol() method to protected/public, the test will fail with the following error:

org.junit.ComparisonFailure: expected:<Hello Naresh[!]> but was:<Hello Naresh[null]>

A better approach in Mockito is to pass a Custom Answer parameter while creating the mock. Following is the Answer implementation which can do partial mocking:

public class AbstractMethodMocker implements Answer<Object> {
	@Override
	public Object answer(InvocationOnMock invocation) throws Throwable {
		Answer<Object> answer;
		if (isAbstract(invocation.getMethod().getModifiers()))
			answer = RETURNS_DEFAULTS;
		else
			answer = CALLS_REAL_METHODS;
		return answer.answer(invocation);
	}
}
 
public class AbstractClazzTest {
	@Test
	public void shouldCallRealMethodsAndFakeAbstractMethod() {
		AbstractClazz clazz = mock(AbstractClazz.class, new AbstractMethodMocker());
		when(clazz.fetchName()).thenReturn("Naresh");
		assertEquals("Hello Naresh!", clazz.sayHello());
	}
}

If you are forced to use this technique in brand new code you are building, may I suggest the Delete button…

Duplicate Code and Ceremony in Java

Thursday, July 21st, 2011

How would you kill this duplication in a strongly typed, static language like Java?

private int calculateAveragePreviousPercentageComplete() {
    int result = 0;
    for (StudentActivityByAlbum activity : activities)
        result += activity.getPreviousPercentageCompleted();
    return result / activities.size();
}
 
private int calculateAverageCurrentPercentageComplete() {
    int result = 0;
    for (StudentActivityByAlbum activity : activities)
        result += activity.getPercentageCompleted();
    return result / activities.size();
}
 
private int calculateAverageProgressPercentage() {
    int result = 0;
    for (StudentActivityByAlbum activity : activities)
        result += activity.getProgressPercentage();
    return result / activities.size();
}

Here is my horrible solution:

private int calculateAveragePreviousPercentageComplete() {
    return new Average(activities) {
        public int value(StudentActivityByAlbum activity) {
            return activity.getPreviousPercentageCompleted();
        }
    }.result;
}
 
private int calculateAverageCurrentPercentageComplete() {
    return new Average(activities) {
        public int value(StudentActivityByAlbum activity) {
            return activity.getPercentageCompleted();
        }
    }.result;
}
 
private int calculateAverageProgressPercentage() {
    return new Average(activities) {
        public int value(StudentActivityByAlbum activity) {
            return activity.getProgressPercentage();
        }
    }.result;
}
 
private static abstract class Average {
    public int result;
 
    public Average(List<StudentActivityByAlbum> activities) {
        int total = 0;
        for (StudentActivityByAlbum activity : activities)
            total += value(activity);
        result = total / activities.size();
    }
 
    protected abstract int value(StudentActivityByAlbum activity);
}

if this were Ruby

@activities.inject(0.0){ |total, activity| total + activity.previous_percentage_completed? } / @activities.size
@activities.inject(0.0){ |total, activity| total + activity.percentage_completed? } / @activities.size
@activities.inject(0.0){ |total, activity| total + activity.progress_percentage? } / @activities.size

or even something more kewler

average_of :previous_percentage_completed?
average_of :percentage_completed?
average_of :progress_percentage?
 
def average_of(message)
	@activities.inject(0.0){ |total, activity| total + activity.send message } / @activities.size
end

ActionScript 3 v/s Java 6 Summary

Tuesday, May 4th, 2010

Here is a quick list of differences I found between AS3 and Java 6 (mostly language syntax and concepts)

  • AS3 places classes inside package blocks. Which allows AS3 to have multiple package definitions per file
  • In AS3 the variable have function scope not block scope
  • AS3 is a mostly type safe language with Dynamic behavior. This means you can add new capabilities (functions and properties) to existing objects at run time.
  • AS3 has metadata while Java has annotations
  • In AS3 semicolons are options if you have one statement per line
  • In AS3 casts look more like a function call through the type being cast or we can use the as operator
  • AS3 does not support generics and typed collections, except it has typed arrays through the Vector class
  • AS3 has E4X integrated into the language to query/manipulation XML
  • In AS3, constructors are always public
  • AS3 does not allow properties on interfaces. Only functions can be declared on interfaces
  • There is no concept of “abstract” in AS3
  • In AS3, because functions are Objects, they can be passed around (like Function Pointers)
  • AS3 has the concept of anonymous functions useful for declaring callbacks or event handlers inline
  • In AS3, the override keyword is required if the subclass declares a function with the same name
  • AS3 does not support Function overloading. Default parameter values might help you achieve it to some extent
  • AS3 has properties as a first-class citizen of the language. get and set keywords are defined as special getters/setters for properties
  • AS3 allows Property access via strings
  • AS3 supports Untyped property access – allows accessing property through the String accessor, even if the property is not present at compile time (you’ll get run-time error)
  • All AS3 objects can act like String maps
  • AS3 supports Untyped variables (mostly type safe language). This allows you to told references without declaring its type
  • AS3 allows Initializing dynamic properties using JSON-like syntax. Create and Initializes the object in one statement

Thanks to Chet Haase for his insightful article on ActionScript for Java developers.

Setting up Tomcat Cluster for Session Replication

Monday, November 9th, 2009

If you have your web application running on one tomcat instance and want to add another tomcat instance (ideally on a different machine), following steps will guide you.

Step 1: Independently deploy your web application (WAR file) on each instance and make sure they can work independently.

Step 2: Stop tomcat

Step 3: Update the <Cluster> element under the <Engine> element in the Server.xml file (under the conf dir in tomcat installation dir) on both your servers with:

<Engine name="<meaningful_unique_name>" defaultHost="localhost">      
     <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
              channelSendOptions="8">
          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
               <Membership className="org.apache.catalina.tribes.membership.McastService"
                           address="228.0.0.4"
                           port="45564"
                           frequency="500"
                           dropTime="3000"/>
               <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                         address="auto"
                         port="4000"
                         autoBind="100"
                         selectorTimeout="5000"
                         maxThreads="6"/>
               <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
                   <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
               </Sender>
               <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
               <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>
          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.css;.*\.txt;"/>
          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
     </Cluster>
     ...
</Engine>

For more details on these parameters, check https://sec1.woopra.com/docs/cluster-howto.html

Step 4: Start tomcat and make sure it starts up correctly. You should be able to access http://locahost:8080. Most default tomcat installations come with an examples web app. Try access http://localhost:8080/examples/jsp/ You should see a list of JSP files.

Step 4.a: Also if you see catalina.out log file, you should see:

INFO: Initializing Coyote HTTP/1.1 on http-8080
Nov 9, 2009 9:29:43 AM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 762 ms
Nov 9, 2009 9:29:43 AM org.apache.catalina.core.StandardService start
INFO: Starting service <server_name>
Nov 9, 2009 9:29:43 AM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.16
Nov 9, 2009 9:29:43 AM org.apache.catalina.ha.tcp.SimpleTcpCluster start
INFO: Cluster is about to start
Nov 9, 2009 9:29:43 AM org.apache.catalina.tribes.transport.ReceiverBase bind
INFO: Receiver Server Socket bound to:/<server_ip>:4000
Nov 9, 2009 9:29:43 AM org.apache.catalina.tribes.membership.McastServiceImpl setupSocket
INFO: Setting cluster mcast soTimeout to 500
Nov 9, 2009 9:29:43 AM org.apache.catalina.tribes.membership.McastServiceImpl waitForMembers
INFO: Sleeping for 1000 milliseconds to establish cluster membership, start level:4

Step 5: Stop tomcat.

Step 6: We’ll use the examples web app to test if our session replication is working as expected.

Step 6.a: Open the Web.xml file of the “examples” web app in your webapps. Mark this web app distributable, by adding a <distributable/> element at the end of the Web.xml file (just before the </web-app> element)

Step 6.b: Add the session JSP file. This JSP prints the contents of the session and also adds/increments a counter stored in the session.

Step 6.c: Start tomcat on both machines

Step 6.d: You should see the following log in catalina.out

Nov 9, 2009 9:29:44 AM org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{-64, -88, 0, 101}:4000,{-64, -88, 0, 101},4000, alive=10035,id={68 106 92 39 -110 -8 73 124 -116 -122 -15 -3 11 117 56 105 }, payload={}, command={}, domain={}, ] 
 
Nov 9, 2009 9:29:49 AM org.apache.catalina.ha.session.DeltaManager start
INFO: Register manager /examples to cluster element Engine with name <server_name>
Nov 9, 2009 9:29:49 AM org.apache.catalina.ha.session.DeltaManager start
INFO: Starting clustering manager at /examples
Nov 9, 2009 9:29:49 AM org.apache.catalina.ha.session.DeltaManager getAllClusterSessions
WARNING: Manager [localhost#/examples], requesting session state from org.apache.catalina.tribes.membership.MemberImpl[tcp://{-64, -88, 0, 101}:4000,{-64, -88, 0, 101},4000, alive=15538,id={68 106 92 39 -110 -8 73 124 -116 -122 -15 -3 11 117 56 105 }, payload={}, command={}, domain={}, ]. This operation will timeout if no session state has been received within 60 seconds.
Nov 9, 2009 9:29:49 AM org.apache.catalina.ha.session.DeltaManager waitForSendAllSessions
INFO: Manager [localhost#/examples]; session state send at 11/9/09 9:29 AM received in 101 ms.
 
Nov 9, 2009 9:29:49 AM org.apache.catalina.core.ApplicationContext log
INFO: ContextListener: contextInitialized()
Nov 9, 2009 9:29:49 AM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: contextInitialized()
Nov 9, 2009 9:29:50 AM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
Nov 9, 2009 9:29:50 AM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Nov 9, 2009 9:29:50 AM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/49  config=null
Nov 9, 2009 9:29:50 AM org.apache.catalina.startup.Catalina start
INFO: Server startup in 6331 ms

Step 6.e: Try to access http://localhost:8080/examples/jsp/session.jsp Try refreshing the page a few times, you should see the counter getting updated.

Step 6.f: You should see the same behavior when you try to access the other tomcat server. Open another tab in your browser and hit http://<other_server_ip>:8080/examples/jsp/session.jsp

Step 6.g: At this point we know the app works fine and the session is working correctly. Now we want to check if the tomcat cluster is replicating the session info. To check this, we want to pass the session from server 1 to session 2 and see if it increments the counter from where we left.

Step 6.h: Before accessing the page, make sure you copy the j_session_id from server 1 (displayed on the http://localhost:8080/examples/jsp/session.jsp). Also make sure to clear all cookies from server 2. (All browsers give you a facility to clear cookies from a specific host/ip).

Step 6.i: Now hit http://<server_2_ip>:8080/examples/jsp/session.jsp;jsessionid=<jsession_id_from_server1>

Step 6.j: If you see the counter incrementing from where ever you had left, congrats! You have session replication working.

Step 6.k: Also catalina.out log file should have:

Nov 9, 2009 9:42:03 AM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: sessionCreated('CDC57B8C5CFDFDDC2C8572E7D14C0D28')
Nov 9, 2009 9:42:03 AM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: attributeAdded('CDC57B8C5CFDFDDC2C8572E7D14C0D28', 'counter', '1')
Nov 9, 2009 9:42:05 AM org.apache.catalina.core.ApplicationContext log
INFO: SessionListener: attributeReplaced('CDC57B8C5CFDFDDC2C8572E7D14C0D28', 'counter', '2')

While this might like smooth, I ran into lot of issues when getting to this point. Following are some trap routes I ran into:

1) java.sql.SQLException: No suitable driver tomcat cluster
Make sure your DB Driver jar (in our case mysql-connector-java-x.x.xx-bin.jar) is in tomcat/lib folder

2) In catalina.org if you see the following exception:

Nov 7, 2009 3:48:53 PM org.apache.catalina.ha.session.DeltaManager requestCompleted
SEVERE: Unable to serialize delta request for sessionid [1F43C3926FF3CC231574EF248896DCA6]
java.io.NotSerializableException: com.company.product.Class
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1156)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326)
	at java.util.ArrayList.writeObject(ArrayList.java:570)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)

This means that you are storing com.company.product.Class object (or some other object that holds a reference to this Object) in your session. And you’ll need to make com.company.product.Class implement Serializable interface.

3) In your catalina.out log if you see

INFO: Register manager /<your_app_name> to cluster element Engine with name <tomcat_engine_name>
Nov 7, 2009 11:56:20 AM org.apache.catalina.ha.session.DeltaManager start
INFO: Starting clustering manager at /<your_app_name>
Nov 7, 2009 11:56:20 AM org.apache.catalina.ha.session.DeltaManager getAllClusterSessions
INFO: Manager [localhost#/<your_app_name>]: <strong>skipping state transfer. No members active in cluster group</strong>.

If both your tomcat instance are up and running, then check if your tomcat servers can communicate with each other using Multicast with the following commands:

$ ping -t 1 -c 2 228.0.0.4
PING 228.0.0.4 (228.0.0.4): 56 data bytes
64 bytes from <server_1_ip>: icmp_seq=0 ttl=64 time=0.076 ms
64 bytes from <server_2_ip>: icmp_seq=0 ttl=64 time=0.645 ms

— 228.0.0.4 ping statistics —
1 packets transmitted, 1 packets received, +1 duplicates, 0.0% packet loss

or

$ sudo tcpdump -ni en0 host 228.0.0.4
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on en0, link-type EN10MB (Ethernet), capture size 65535 bytes
22:11:50.016147 IP <server_1_ip>.45564 > 228.0.0.4.45564: UDP, length 69
22:11:50.033336 IP <server_2_ip>.45564 > 228.0.0.4.45564: UDP, length 69
22:11:50.516746 IP <server_1_ip>.45564 > 228.0.0.4.45564: UDP, length 69
22:11:50.533613 IP <server_2_ip>.45564 > 228.0.0.4.45564: UDP, length 69

If you don’t see the results as described above, you might want to read my blog on Enabling Multicast.

Unable to initialize TldLocationsCache

Thursday, July 9th, 2009

On one of the projects we are using Cargo Maven Plugin to run an embedded Jetty server for our builds. Out of the blue, today, I started getting the following error when I was running my Selenium Tests after deploying the application.

1
2
3
4
5
6
7
WARN:  Nested in org.apache.jasper.JasperException: org.apache.jasper.JasperException: Unable to initialize TldLocationsCache: null:
org.apache.jasper.JasperException: Unable to initialize TldLocationsCache: null
at org.apache.jasper.compiler.TldLocationsCache.init (TldLocationsCache.java:253)
at org.apache.jasper.compiler.TldLocationsCache.getLocation (TldLocationsCache.java:224)
at org.apache.jasper.JspCompilationContext.getTldLocation (JspCompilationContext.java:526)
at org.apache.jasper.compiler.Parser.parseTaglibDirective (Parser.java:422)
...

No clue why this is happening. Surprising this is, this issue cannot be reproduced on a Windows box. Only on my Mac with JDK 1.6 and Maven 2.0, I’m getting this issue.

On goolging for this issue, I make across this bug report which kind of indicated that this might be an issue with the Cargo Maven Plugin. On upgrading the plugin to version 1.0, the issue was solved. 🙂

Need to find out what caused the problem in the first place.

Brett’s Refactoring Exercise Solution Take 1

Saturday, June 13th, 2009

Recently Brett Schuchert from Object Mentor has started posting code snippets on his blog and inviting people to refactor it. Similar to the Daily Refactoring Teaser that I’m conducting at Directi. (I’m planning to make it public soon).

Following is my refactored solution (take 1) to the poorly written code.

Wrote some Acceptance Test to understand how the RpnCalculator works:

Then I updated the perform method to

@Deprecated
public void perform(final String operatorName) {
  perform(operators.get(operatorName));
}
 
public void perform(final Operator operator) {
  operator.eval(stack);
  currentMode = Mode.inserting;
}

Notice I’ve deprecated the old method which takes String. I want to kill primitive obsession at its root.

Had to temporarily add the following map (this should go away once our deprecated method is knocked off).

private static Map operators = new HashMap() {
  {
    put("+", Operator.ADD);
    put("-", Operator.SUBTRACT);
    put("!", Operator.FACTORIAL);
  }
 
  @Override
  public Operator get(final Object key) {
    if (!super.containsKey(key)) {
      throw new MathOperatorNotFoundException();
    }
    return super.get(key);
  }
};

Defined various Operators

private static abstract class Operator {
  private static final Operator ADD = new BinaryOperator() {
    @Override
    protected int eval(final int op1, final int op2) {
      return op1 + op2;
    }
  };
 
  private static final Operator SUBTRACT = new BinaryOperator() {
    @Override
    protected int eval(final int op1, final int op2) {
      return op2 - op1;
    }
  };
 
  private static final Operator FACTORIAL = new UnaryOperator() {
    @Override
    protected int eval(final int op1) {
      int result = 1;
      int currentOperandValue = op1;
      while (currentOperandValue &gt; 1) {
        result *= currentOperandValue;
        --currentOperandValue;
      }
      return result;
    }
  };
 
  public abstract void eval(final OperandStack stack);
}

Declared two types of Operators (BinaryOperator and UnaryOperator) to avoid duplication and to make it easy for adding new operators.

public static abstract class BinaryOperator extends Operator {
  @Override
  public void eval(final OperandStack s) {
    s.push(eval(s.pop(), s.pop()));
  }
 
  protected abstract int eval(int op1, int op2);
}

and

public static abstract class UnaryOperator extends Operator {
  @Override
  public void eval(final OperandStack s) {
    s.push(eval(s.pop()));
  }
 
  protected abstract int eval(int op1);
}

Cobertura Gottachs

Friday, October 3rd, 2008

Today I spent a good 3 hours trouble shooting issues with code coverage on a legacy project. I’ve been using Cobertura for a good 3 years now. Hence I decided to use Cobertura to help a team working on a legacy project. What looked like a simple 10 mins job, ended up taking forever.

To start off, the team did not have an automated build. So I quickly put an Ant script together. Last 2 days I had helped them write some acceptance tests using FitNesse. I set up the build file to run the FitNesse tests as part of the build. Once I had the testing in place, I wanted to see what kind of code coverage we had. So we started adding Cobertura Ant tasks to the build. This was a fairly trivial task. But surprisingly cobertura-report kept displaying N/A for Line and Branch coverage. Clearly there was something wrong.

After spending a good amount of time, I realized that we need to compile the source code with debug=true option, else Cobertura does not generate any coverage numbers. What’s amazing is, it does not complain (in the normal mode) about this nor does cobertura’s documentation talks about this. When I ran the ant build in verbose mode, it showed the following warning

[cobertura-instrument] WARN visitEnd, No line number information found for class

That’s when it occurred to me that I need to compile my source code with debug=true option.

Once I solved this problem I hit the next roadblock. Even thought I fixed the debug=true option issue, cobertura-report was still displaying N/A for Line and Branch coverage. I quickly wrote a dummy unit tests and that seemed to work. Cobertura report started showing some numbers. But I was not able to generate any coverage numbers from the main Java application (server) that I was trying to test.

I knew there was some problem with the Shutdown hook not been executed correctly to flush the coverage numbers to the ser file. It turned out that if you have a server which is started as a daemon process in a forked JVM in the build, it needs to have a way to shutdown the server gracefully. So I ended up writing a shutdown command for the server which would basically do a System.exit(0). One need to explicitly call the shutdown script before calling the cobertura report target.

Finally after 3 hrs, there was some ray of hope in my life when I saw a good 86% code coverage. This meant I could now go and refactor the code till I dropped dead.

toString(), equals() and hashCode(): To Override or Not to?

Sunday, August 31st, 2008
During a pairing session with Michael Feathers, he told me why he does not like to override toString(), equals() and hashCode() methods in Java.
If you look around, different people use toString() method to return random pieces of text. The guidelines says that the toString() method should
Returns a string representation of the object. In general, the 

1
toString

 method returns a string that “textually represents” this object. The result should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method.

What does string representation of the object really mean? Does it mean all the values of all the instance variables? Does it mean just a selected values? Usually developers decide on some selected value which uniquely identifies the object. But there is no clear guideline. 
Similarly when we talk about the equals() method, different developers look at object equality differently. Depending on the state of the object and the context, one might have to think about equality differently. So instead of overriding equals() method, Michael prefers to create methods like matchesId() or matchesName() which are very explicit in terms of what they are matching to. 
So the next time you override one of these methods think about them.

@Override annotation, I’m looking at you

Monday, August 18th, 2008

Being out of touch with Java coding and moving between Java 5 and 6 can be a great way to assume yourself. Couple of days back I stumbled upon a fairly well know Java 5 feature, @Override annotation. Annotations were introduced in Java 5. According to the JavaDoc

public @interface Override

Indicates that a method declaration is intended to override a method declaration in a superclass. If a method is annotated with this annotation type but does not override a superclass method, compilers are required to generate an error message.

One would read this and think the following code should work perfectly fine:

1
2
3
interface I {
  void m1();
}
1
class C implements I {
1
2
3
4
  @Override
  public void m1() {
  }
}

But guess what? In Java 5, the compiler gives the following error: “The method m1() of type C must override a superclass method”

The same exact code works perfectly fine on Java 6. You go back and read the Javadocs again and you can’t seem to find a fault. But it turns out that in Java 5, @Override annotation only applied to methods overridden from a superclass and not from Interfaces. (So if you implement a method from an Interface, @Override annotation cannot be applied in Java 5).
This was apparently a bug in Java 5, which was fixed in Java 6. Here is the bug report : http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6399361
This is great, but there is no mention on public JDK 6 JavaDocs nor in the release notes.
Eclipse IDE 3.3.1.1 recognizes the new feature and add the @Override annotation auto-magically every time I override a method or implement an interface method. Pretty cool! Unfortunately for all you Intelij fans, there is an open bug still waiting to be fixed.

svn: Unable to open an ra_local session to URL

Monday, August 4th, 2008

Currently I’m working on adding Revision Control support to FitNesse. In the process, I’m creating a SVN adapter using svnkit library.

Once I’ve added a file, if I try to commit the file using

1
2
3
4
5
6
protected void commit(File file) throws SVNException {
final SVNClientManager manager = SVNClientManager.newInstance();
final SVNCommitClient commitClient = manager.getCommitClient();
final File[] filesToCommit = new File[] { file };
commitClient.doCommit(filesToCommit, false, "Auto Commit", false, false);
}

I get the following exception:

1
2
3
4
5
svn: Unable to open an ra_local session to URL
at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.error(SVNErrorManager.java:55)
at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.error(SVNErrorManager.java:40)
at org.tmatesoft.svn.core.wc.SVNCommitClient.doCommit(SVNCommitClient.java:582)
at org.tmatesoft.svn.core.wc.SVNCommitClient.doCommit(SVNCommitClient.java:549)

Trying to Google for this, did not take me anywhere. Finally after debugging thru svnkit’s code, I stumbled upon the following line which throws the exception:

1
SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(url));

Basically, they have SVNRepositoryFactory class which expects the client to register an appropriate driver to handle the given protocol. Their javadocs says:

Depending on what protocol a user exactly would like to use to access the repository he should first of all set up an appropriate extension of this factory. So, if the user is going to work with the repository via the custom svn-protocol (or svn+xxx) he initially calls

1
SVNRepositoryFactoryImpl.setup();

More details: http://svnkit.com/kb/javadoc/org/tmatesoft/svn/core/io/SVNRepositoryFactory.html

Solution: Since I’m using File System (file://) protocol, I had to add the following line in a static block of my adapter class:

1
FSRepositoryFactory.setup();
    Licensed under
Creative Commons License