Monday, June 8, 2009

Learning Java via Simple Tests

On forums dedicated to answering questions for people who are new to Java programming (such as the Sun forum called New to Java), a common frustration vented by many of the "regulars" is when people posting questions have not even bothered to search for something that has already been frequently answered or is easily answered with Google, Bing, or other search engines. This is often considered even more egregious if the very question has been answered in that very forum already. Indeed, as I've written about before, web searches and forums can be invaluable tools for the software developer.

Although the combination of today's powerful online search engines with countless blogs, articles, and forums makes it easier than ever to learn how and why to perform just about any software development task, there are still advantages to simply trying some things out for oneself. When a developer gets used to writing simple tests to find out how something works, the developer can sometimes create and run these tests nearly as quickly as the answer could be found online. Even better, I have found that I often learn more and remember better if I have tested it myself. Another advantage of the simple test approach is that it can help us determine differences on different versions of Java or different JVM implementations in cases where implementation-specific details are allowed. Finally, just the act of writing simple tests and running them simply (often without IDE) helps keeps foundation skills sharp.

Production development can be very different from writing simple tests and demonstration to learn how to use something. In this blog post, I intend to discuss some small things that many experienced developers do all the time to learn via simple testing and demonstrations. Note that I am not talking about tests such as unit tests, functional tests, and integration tests. Rather, I am talking about tests that answer questions that often start with, "What does Java do if..."


The Common Text Editor

For these simple tests to learn how something works, the overhead of starting an IDE, creating an IDE project, and other steps associated with using an IDE are often unnecessary. Although I will almost always use an IDE for production development (other than for quick fixes in which I don't need any of the IDE features), I often find myself using a simple text editor such as JEdit, vim, emacs, or even WordPad.


Template Java Application Class

For most of my simple tests/demonstrations, I need a simple Java class with a main function. I have typed in the minimal skeletal class for this thousands of times, but I know that many developers like to keep a "template" class around for such cases. It will often look like that shown in the next code listing.

ClassName.java - Template Java Class
// TODO - Add package declaration here to avoid unnamed package scope

// TODO - Replace 'ClassName' with appropriate class's name in all locations
//        (search and replace suggested).

/**
 * TODO - Add class description here.
 */
public class ClassName
{
   /**
    * Main exectuable function for this class.
    *
    * @param arguments Command-line arguments for this application.
    */
   public static void main(final String[] arguments)
   {
      final ClassName me = new ClassName();
   }

   /**
    * Provide String representation of me.
    *
    * @return String representation of me.
    */
   public String toString()
   {
      return "String representation not yet provided for " + this.getClass().getName(); 
   }
}


A simple template could be even more minimal than that shown above. For example, the toString() implementation is often unnecessary when writing simple tests.


Building the Simple Test

Thanks to the prevalence of IDEs and highly useful products such as Ant, I have seen that some experienced Java developers have become unfamiliar with using the javac Java compiler and the java Java application launcher. However, when running simple tests to learn a new API or to answer one's own question about "what does Java when ...", these can be quick to use. I typically use javac when I only need to compile a small number of .java files in the same directory. If it gets more complicated than that, I move onto a simple Ant build.xml file. I especially like to move from command-line building directly with javac to using Ant when there are multiple dependencies that need to be explicitly declared on the classpath (though scripting the javac call to use a lengthy classpath can also be a useful tactic).

To make Ant even easier and quicker to use, I often use a template build.xml file similar to the template .java file shown earlier. The next code listing demonstrates what this template Ant build file might look like.

build-template.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Look for XXXXX and replace with appropriate String. -->
<project name="XXXXX" default="all" basedir=".">
<description>XXXXX</description>

<property environment="env"/>

<property name="javac.debug" value="true" />
<property name="src.dir" value="src" />
<property name="dist.dir" value="dist" />
<property name="classes.dir" value="classes" />
<property name="javadoc.dir" value="${dist.dir}/javadoc" />

<property name="jar.name" value="XXXXX.jar" />
<property name="jar.filesonly" value="true" />

<path id="java.example.classpath" />

<target name="-init">
   <mkdir dir="${classes.dir}" />
   <mkdir dir="${dist.dir}" />
</target>

<target name="compile"
        description="Compile the Java code."
        depends="-init">
   <javac srcdir="${src.dir}"
          destdir="${classes.dir}"
          classpathref="java.example.classpath"
          debug="${javac.debug}"
          includeantruntime="false" />
</target>

<target name="jar"
        description="Package compiled classes into JAR file"
        depends="compile">
   <jar destfile="${dist.dir}/${jar.name}"
        basedir="${classes.dir}"
        filesonly="${jar.filesonly}">
   </jar>
</target>

<target name="all"
         description="Compile Java source, assemble JAR, and generate documentation"
         depends="jar, javadoc" />

<target name="javadoc" description="Generate Javadoc-based documentation">
   <mkdir dir="${javadoc.dir}" />
   <javadoc doctitle="XXXXX"
            destdir="${javadoc.dir}"
            sourcepath="${src.dir}"
            classpathref="java.example.classpath"
            private="true"
            author="Dustin" />
</target>

<target name="clean" description="Remove generated artifacts.">
   <delete dir="${classes.dir}" />
   <delete dir="${dist.dir}" />
</target>

</project>


This template file can be renamed build.xml and the XXXXX tokens can be replaced with text specific to the particular test being executed. Often, this is all I need for the simple tests and I can expand the filesets for Java files in multiple directories. Of course, as things become complicated past a certain point, it often indicates to me that it is time to return to the IDE anyway.

UPDATE (1 October 2010): With Ant 1.8 and above, I also like to set the "includeantruntime" attribute of the "javac" task to "false" to NOT include Ant's runtime classes in my compile-time classpath and to avoid the associated warning.


Handling Dynamic Input

Many of the simple "what happens if I..." tests that I write are perfectly useful with static configuration. In these environments, I shameless hard-code values in to demonstrate the desired function's behavior. However, there are times when my tests need values to be provided at runtime. Java offers a plethora of approaches for supplying data dynamically. These include loading properties, reading environment variables, using command-line arguments, reading from files, and reading from standard input with mechanisms such as System.in and Console.

For the simplistic demonstrative tests that I'm talking about in this blog posting, I generally prefer command-line arguments or command-line input/out. The Console class has made it much simpler to read in values from a console since Java SE 6, but it does not work when redirection is used (see section below on output) because there is no applicable console (System.console() returns null). Command-line arguments work particularly well when I do want to use redirection or want to run my demonstrative tests as part of scripts.

One could also write special clients in Swing or Flex or other technology, but that is almost certainly overkill for the simple types of tests I am talking about unless one of the purposes of the test is to understand interaction between clients such as these.


Generating Output of Simple Test

When writing these simple tests to learn or prove how something works in Java, one of the most important steps is to output results. A key point here is that the output should include the particular thing being tested and the result for that particular thing.

The java.util.logging package is very easy to use, especially when one only wants to use its default settings. However, most of the advantages of using a logging framework are not really that important in this environment of simple tests. Therefore, I find myself using the old-fashioned but still highly useful System.out and System.err. I also have come to really like using Console (via System.console()) that was introduced with Java SE 6, especially for getting interactive user input.


Capturing Output

If I have a lot of output in my simple test or if I want to provide the output in an e-mail, blog, on a Wiki page, or in any other form at a later time, it is useful to capture the text output from my simple Java tests. I could adjust the test code itself to write to a file, but it is often easier to simply use the operating system's redirection support to redirect the output from the console to the specified file.

Both Windows and Linux/Unix support output redirection. In fact, both use the greater-than operation (>) to redirect standard output. In both operating systems, the > operator overwrites any file already at the target location (assuming permissions allow this). In Linux and in Windows, one can append to an existing file with the double greater-than operator (>>).

If the test class outputs to standard error rather than or in addition to standard input, then standard error may need to be redirected to a file for later use as well. In Linux, this redirection of standard output and standard error to the same file can be accomplished with the syntax 2>&1 (placed after the filename that the > or >> operator redirects to). For Windows, redirection of both standard output and standard input is also accomplished with the 2>&1 notation placed after the name of the file to which output is redirected via the > or >> operators.

For more details on redirection in the major operating systems, see the following additional resources:
* Unix Power Tools: Using Standard Input and Output
* The Linux Cookbook: Redirecting Input and Output
* Batch Files - Redirection
* Windows: Using Command Operations Redirection

As mentioned above output redirection precludes the use of java.io.Console because there is no applicable Console in such situations.


An Example

I conclude this blog post with an example. The two questions to be answered in this example are "What is the default initial capacity of a StringBuffer and is that different than the default initial capacity of a StringBuilder?" The answers to these questions are undoubtedly available online. However, these are perfect examples of where we can enjoy the advantages of knowing the exact answers to these questions quickly and confidently by writing simple tests that answer the questions.

The following code list for InitialCapacities.java, demonstrates how easy it is to answer these questions. Using the template Java class makes this really easy to generate quickly.

InitialCapacities.java
/**
* Determine initial capacity of StringBuffer and StringBuilder.
*/
public class InitialCapacities
{
/**
* Main exectuable function for this class.
*
* @param arguments Command-line arguments for this application.
*/
public static void main(final String[] arguments)
{
final StringBuffer buffer = new StringBuffer();
System.out.println("Initial StringBUFFER Capacity: " + buffer.capacity());

final StringBuilder builder = new StringBuilder();
System.out.println("Initial StringBUILDER Capacity: " + builder.capacity());
}

/**
* Provide String representation of me.
*
* @return String representation of me.
*/
public String toString()
{
return "Test to indicate initial capacities of StringBuffer and StringBuilder"; 
}
}


This example is only a single class without any third-party class dependencies, so I'll just use trusty old javac directly here:

javac InitialCapacities.java


This generates a InitialCapacities.class file in the same directory. I can easily run that with the Java application launcher:

java InitialCapacities


The output shows that StringBuffer and StringBuilder have the same capacity of 16. The output is shown in the next screen snapshot.



With this demonstrative test written, I can use it later on different JVMs, different operating systems, and in generally different environments to prove the value is always the same or determine when it is not the same. Likewise, the next time someone asks me about this particular behavior, I could simply pass along this test to them as well.



Conclusion

Simple demonstrative tests can be a useful learning tool in the Java developer's toolbox. These simple tests can supplement information available online and in other resources and offer unique advantages not always as easily obtained with other means. This blog post has attempted to show the few simple steps a new Java developer can take to start using these small demonstrative tests when appropriate.

2 comments:

@DustinMarx said...

There are two points I had intended to include in this post, but forgot about until after I had posted.

First, although I typically do not use an IDE for very simple learning tests for reasons discussed in this post, there are times when the IDE's automatic adding of imports, method name completion features, parameter prompting features, and Javadoc comments all integrated together make using a particularly challenging API easier. In such cases, I will use the IDE.

Second, for these simple learning tests, dynamic languages can make them even simpler. I have touched on this before. For example, I could have written the example comparing initial capacities of StringBuffer and StringBuilder in Groovy and tested the very same functionality with very few lines of code.

Rob L said...

Great article! I know this is old but thanks for writing this. It is exactly what I was looking for. I am a newby learning and trying to do my code "by hand". Thanks for the clear examples.

Rob