Following a "there's no reason not to test" motto, here it is a simple way to perform unit testing on classes with a main() method. This idea is easily extensible and can be used to test methods whose outputs are sent to the standard and error streams.

After the last edition of an Object Oriented Java Programming course that I have given frequently over the last few years, I was able to confirm the finding that I almost always make on these types of courses. This is that students, when they first encounter Java and OO find it much easier to start developing simple classes with main() methods and System.out outputs than to try to take on board more advanced concepts such as the creation of objects or the use of logging libraries. By the end of the course, however, the majority of the students are more comfortable with these concepts and we have moved on to more commonly used test setups.

While not part of the standard libraries, I have always included a section on unit testing with JUnit. The course concludes with the delivery of solved exercises and, of course, I include tests for all of them.

The class to be unit tested:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println(args[0]+"! This is a simple two lines");
        System.out.println("Hello World!!");
    }
}

The JUnit test case looks like this:

import org.junit.Test;
import static org.junit.Assert.*;
public class HelloWorldTest {
    @Test
    public void testMain() {
        String[] expected = new String[]{
            "Yeah! This is a simple", "Hello World!!" }; 
        String[] results = 
            AbstractMainTests.executeMain("HelloWorld", new String[]{"Yeah"});

        assertArrayEquals(expected, results);
    }
}

Note that, for brevity, in this example we are using only System.out as when both System.out and System.err are used the assertArrayEquals method will be found wanting since the output order of the streams is not guaranteed. A simple ordering of the arrays prior to their comparison would suffice to solve this. Additionally, for brevity, we are passing the name of the class to be tested to executeMain when a more elegant and reusable way to do it would be to use reflection to work it out.

The Helper class: AbstractMainTests

AbstractMainTests redirects the output and uses reflection to execute the main method. Results are collected in an array.

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

public abstract class AbstractMainTests {
    private static final PrintStream OUT = System.out;
    private static final PrintStream ERR = System.err;

    private static void recoverOriginalOutput() {
        System.err.flush();
        System.out.flush();
        System.setOut(AbstractMainTests.OUT);
        System.setErr(AbstractMainTests.ERR);
    }
    public static String[] executeMain(String className, String[] args) {
        // First, change the standard and error output streams
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        PrintStream tempOutput = new PrintStream(bos, true);
        System.setOut(tempOutput); 
        System.setErr(tempOutput);

        List<String> result = new ArrayList<String>();
        try {
            AbstractMainTests.invokeMain(className, args); // Call main!!
            // Collect main() execution output
            BufferedReader reader = 
                new BufferedReader(new StringReader(bos.toString()));
            String line = reader.readLine();
            while (line != null) {
                result.add(line);
                line = reader.readLine();
            }
        } catch(Throwable e) {
            throw new RuntimeException(
                "Error obtaining output for ["+className+"]", e
            );
        } finally {
            recoverOriginalOutput();  // Return output to its original form
            try {
                bos.close();
                tempOutput.close();  // Close streams
            } catch (IOException e) { }
        }
        return result.toArray(new String[0]); // Convert from list to an array
    }
    public static void invokeMain(String test, String[] args) {
       try {
           Class clazz = Class.forName(test);
           Object app = clazz.newInstance();
           Method m = app.getClass().
               getMethod("main", (new String[0]).getClass());

           // Make sure it is the static void main(String[]) method
           if ((m.getReturnType() != Void.TYPE) || 
               (!Modifier.isStatic(m.getModifiers()))) {
                   throw new RuntimeException(
                       "Not executable found: static main(String[])"
                   );
           } 
           Object[] param = { args };
           m.invoke(app, param);
       } catch(Throwable e) {
          throw new RuntimeException("Error executing main", e);
       }  
    }
}
Filed under: languages, methodology

comments

There are no comments.