Tuesday October 23, 2007 Bryan Weber
Writing testable Java code with stateless collaborators
Writing testable Java code is an often discussed topic that has been covered ad nauseam. So what could another blog post possibly contribute? Well, I've been following a simple pattern recently and it makes for some very simple, clean (imho) and testable code. I never call a method that resides within the calling class. Ok, so maybe never is too strong, but every time you call a method within the same class you violate the principle of isolation, which is key to unit testing. Why does calling a method from within the same class violate the principle of isolation? Because the methods are tightly coupled and it is no longer possible to test the calling method without also testing the called method. So what is the answer? Well, one way is to use collaborators. The objects can be used, stubbed or mocked for unit tests and the calling and called objects can be tested separately.
So what are some arguments against this type of pattern? Well, many people complain about the following points: (1) the code is no longer as readable, (2) the visibility of the code is changed, (3) the potential explosion of classes, and (4) the logic of an "Object" is no longer contained within the class. Let's discuss these points one at a time. Code samples are included below for a side-by-side comparison so read the code and think about it as you go through the four points.
1) The code is no longer as readable. This may be the point with the most merit. Instead of reading the code and simple scrolling up or down to continue reading the reader must now find another class. Having a method name on the called class that is descriptive often makes the code just as readable, it simply requires the reader to go to another place to read the details of the implementation. IDE's alleviate this by allowing users to jump to other classes/methods.
2) The visibility of the code is changed. Instead of having a private or protected method inside of the class there is now an often times public method on another interface/class. However, the scope of the collaborator can be controlled inside the class so in my opinion the benefit of testability outweighs the changing of scope from method to field.
3) The potential explosion of classes. Methods can still be grouped together on collaborators by related functionality, but having a few extra classes is an acceptable trade-off for more testable code. Classes often tend to be simpler with this methodology. This can become an argument of preference between fewer, more complex classes or more classes that are simpler.
4) The logic of an object is no longer contained with the class. But it can be contained within a small number of classes in the same package and the classes tend to be simpler and classes tend to be even more well defined in terms of what they do.
There probably is no right or wrong answer here, to a degree it is a matter of preference, however, I feel pretty strongly that having more testable code without any major drawbacks is worth adopting this pattern. This pattern works particularly well when the called code is a stateless service that can be injected by an IOC container, such as Spring.
Let's look at samples side by side.
# All the logic in one class (NOT easily testable because bar must be tested with foo)
So what are some arguments against this type of pattern? Well, many people complain about the following points: (1) the code is no longer as readable, (2) the visibility of the code is changed, (3) the potential explosion of classes, and (4) the logic of an "Object" is no longer contained within the class. Let's discuss these points one at a time. Code samples are included below for a side-by-side comparison so read the code and think about it as you go through the four points.
1) The code is no longer as readable. This may be the point with the most merit. Instead of reading the code and simple scrolling up or down to continue reading the reader must now find another class. Having a method name on the called class that is descriptive often makes the code just as readable, it simply requires the reader to go to another place to read the details of the implementation. IDE's alleviate this by allowing users to jump to other classes/methods.
2) The visibility of the code is changed. Instead of having a private or protected method inside of the class there is now an often times public method on another interface/class. However, the scope of the collaborator can be controlled inside the class so in my opinion the benefit of testability outweighs the changing of scope from method to field.
3) The potential explosion of classes. Methods can still be grouped together on collaborators by related functionality, but having a few extra classes is an acceptable trade-off for more testable code. Classes often tend to be simpler with this methodology. This can become an argument of preference between fewer, more complex classes or more classes that are simpler.
4) The logic of an object is no longer contained with the class. But it can be contained within a small number of classes in the same package and the classes tend to be simpler and classes tend to be even more well defined in terms of what they do.
There probably is no right or wrong answer here, to a degree it is a matter of preference, however, I feel pretty strongly that having more testable code without any major drawbacks is worth adopting this pattern. This pattern works particularly well when the called code is a stateless service that can be injected by an IOC container, such as Spring.
Let's look at samples side by side.
# All the logic in one class (NOT easily testable because bar must be tested with foo)
public class Foobar {
public void foo() {
for ( int i = 0; i < 10; i++ ) {
bar();
}
}
public void bar() {
// do bar
}
}
# Using collaborator (foo and bar can be tested completely independent of each other)
public class Foo {
private Bar bar;
public void setBar(Bar bar) {
this.bar = bar;
}
public void foo() {
for ( int i = 0; i < 10; i++ ) {
bar.bar();
}
}
}
public class Bar {
public void bar() {
// do bar
}
}
Posted by bweber
Oct 23 2007, 12:03:38 AM EDT
JRuby script in a signed jar
OK, let's dive into the world of JRuby a little further, specifically let's touch on the boundary between Java and JRuby again. One excellent way to run JRuby for certain situations is to put the JRuby code (along with some Java code) in a signed, executable jar. A fairly common reason to do this would be the following: you want to run some ruby code (in the JVM) and you want to make sure that no one modifies the contents of the JRuby file (thus you sign the jar). Of course this does not offer you perfect protection, but it adds another layer making it more difficult to subvert your efforts. For the time being, I will assume that you have JDK 1.5 and not JDK 1.6... which means that the scripting framework is not available to you. So, simply include the JRuby jar file in your classpath and jar up the following class along with your ruby files. You should be sure to sign the jar when you create it and you should add pkg.JRubyRunner as your main class so the jar file is executable. Then executing "java -cp jruby.jar -jar MyJar.jar" will run your jruby code AND since the jar file is signed no one will be able to modify your ruby files! (Again, with some effort of course a hacker could modify the ruby file if they had access to the jar.)
package pkg;
import org.jruby.Main;
public class JRubyRunner {
public static void main(String[] args) throws Exception {
runJRubyScript("ipc.rb", args);
}
public static void runJRubyScript(String name, String... args) {
// modify the args for jruby
String[] args2 = new String[2 + args.length];
if (args.length > 0) {
System.arraycopy(args, 0, args2, 2, args.length);
}
args2[0] = "-e";
args2[1] = "require '" + name + "'";
// execute the ruby script
Main.main(args2);
}
}
Now, for a few subtle points that you may have missed, especially considering the simplicity of the code. For starters, why you may ask, not just pass in the name of the ruby file? Why use the -e option at all? Well, the reason is because the ruby file is located in the jar file so it is not accessible via java.io.File which JRuby uses to load the file. This makes perfect sense. And since it defeats the purpose to move the ruby file outside of the signed jar, we must find another way to get this to work. You could read in the entire contents of the file from the classpath and pass it into the -e option, but this is ugly and extremely prone to error, so we take advantage of an excellent feature of 'require' in JRuby. Require loads its files from the classpath. This is excellent, because it means that if we require just the file we want to run, it will be loaded from the classpath and and subsequent requires will be loaded from the classpath as well.
Posted by bweber
Jul 30 2007, 09:28:15 PM EDT
Configuring JRuby in IntelliJ IDEA 6.0.5
So you know Java, you've played around with Ruby and now you are interested in trying out JRuby in your favorite IDE (which naturally is IntelliJ IDEA) or maybe you just want to try out Ruby/JRuby... Unfortunately, JetBrains is still a little behind with their Ruby, and especially JRuby, support for IntelliJ. But have no fear, just follow the steps below and you'll be up and running without too much trouble.
[Legal disclaimer: This was tested on Mac OS X with Java 1.5, JRuby 1.0 and IntelliJ 6.0.5, but should work on any Windows or *nix based system with IntelliJ 6.0.x and the JRuby 0.1 plugin.]
[Legal disclaimer: The screen shots in these instructions are not actually perfect or exactly what you will see at all stages of the process, however they should contain all the information required.]
- Install Ruby Plugin for IntelliJ Instructions (Leaves this page) [Don't look for a JRuby plugin, install the Ruby plugin.]
- Download and Install JRuby (unzip to the desired installation directory) Instructions (Leaves this page)
-
[UPDATE: I PUT IN A FEATURE REQUEST AND JETBRAINS INCLUDED IT SO NOW THERE IS SUCH A THING AS A JRUBY SDK SO THIS STEP IS NO LONGER REQUIRED! YEAH JETBRAINS!!!!]
Create a Ruby link (*nix) or bat file (Windows) [UPDATE: Creating a bat file named ruby.bat does NOT work. IntelliJ looks for ruby.exe which makes this trick more of a pain... On Windows it is probably simpler to create an External Tool for JRuby right now. That way you can right click on a file and execute it and it doesn't require any fancy hacks. Instructions for creating/configuring the "external tool" are below.] that points to the JRuby executable [The IntelliJ plugin requires the executable file be called "ruby" and not "jruby" so we simply trick it by creating a file that points to the real JRuby executable. Hopefully JetBrains will change this for future releases.]
- *nix soft link: ln -s jruby ruby
-
Create a project with a Ruby module (note: create a Ruby module NOT a JRuby module as you won't find any such thing as a JRuby module. Nor do you need one for that matter, as the Ruby module will do just fine thank you very much. I suppose that JRuby wouldn't be very good if it couldn't just replace Ruby as that is what it is designed to do after all! Well, sort of anyway.)

-
Point IntelliJ to your JRuby SDK


-
Create java_cp file and enter classpath values [NOTE: Classpath values must end with a trailing slash due to the implementation of the java module in JRuby.]
-
Include java_cp after java and before including any classes
- Run files by right clicking on the file and choosing Run or by selecting the file and hitting the Run shortcut key sequence.
-
Change the ruby project SDK to Java (Otherwise your Ruby module will be fine but your Java modules will not work due to a bug in the Plugin.)
See, I told you, you get an irrelevant, nasty error message!
-
Create a JRuby External Tool (Carefully note the parameters and working directory used. Notice that I made the working directory the lib directory where I put my source file(s). This screen shot is inconsistent with the other screen shots taken because it came from another project, so just take my word for it. :))
- Run your Ruby files by selecting the file and choosing Tools -> JRuby.
Posted by bweber
Jul 04 2007, 10:20:46 PM EDT

