Design by tests. When you have the goal in mind you built a test case. Every functional piece should be able to be tested in isolation.
For instance, validating a login, listing an article, and updating a row in the database all should be testable separate from the whole application.
Accomplishing this goal is helped by using common utilities such as:
– command line parser
– logging
– web browser debugger
Main interfaces will be in most every class. HTML files can be opened on the file system. Firebug in Firefox. Log4j and Java Command Line Option (JCLO) libraries are crucial elements of a successful test driven development strategy. Even JQuery has a log function.
For example, we can create a Log class to encapsulate log4j that will allow us to run without log4j, into stdout and stderr. All our classes can call Log.write(…) with various levels of detail, error, warn, info, debug, etc…
Like this:
package com.doncohoon.util; import java.util.Date; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.Priority; import com.doncohoon.db.Access; public class Log { static boolean v = false; public final int all_int = Level.ALL_INT; public final int trace_int = Level.TRACE_INT; public final int debug_int = Level.DEBUG_INT; public final int error_int = Level.ERROR_INT; public final int fatal_int = Level.FATAL_INT; public final int info_int = Level.INFO_INT; public final int off_int = Level.OFF_INT; public final int stdout_int = 1; // emergency standard log public final int stderr_int = 2; // emergency error log private Logger logger = null; public Log(Logger L) { this.logger = L; } public Log(Logger L, boolean debug) { if (debug) { v = true; } this.logger = L; } public void setVerbose (Logger logger) { logger.setLevel(Level.DEBUG); v = true; } public void all(String msg, Exception e) { write (all_int, msg); logger.info("Exception: ", e); } public void write(int level, String msg, Exception e) { write (level, msg); logger.fatal("Exception: ", e); } public void write(int level, String msg) { // Switch switch (level) { case Level.ALL_INT: logger.info(msg); break; case Level.TRACE_INT: logger.trace(msg); break; case Level.DEBUG_INT: if (logger.isDebugEnabled()) logger.debug(msg); else if (v) write (stdout_int, msg); break; case Level.ERROR_INT: logger.error(msg); break; case Level.FATAL_INT: logger.fatal(msg); break; case Level.INFO_INT: logger.info(msg); break; case Level.OFF_INT: logger.setLevel(Level.OFF); break; case Level.WARN_INT: logger.warn(msg); break; case stdout_int: Date now1 = new Date(); System.out.println(now1.toString() + "-App:" + msg); break; case stderr_int: Date now2 = new Date(); System.err.println(now2.toString() + "-App:" + msg); break; default: Date now = new Date(); System.err.println(now.toString() + "-App:Invalid level." + level); break; } // switch } // write }
The other technique is to be able to call each class from the command line and exercise its methods. For this we will need a command line argument parser for the main(…) method. JCLO works quite well.
Like this, we add a Args class to each class. For class NanoHTTPD we add the following:
/* * Command line arguments */ class NanoHTTPDArgs { private String confdir = "config/"; private String log4j_xml = "LocalServer_log4j.xml"; private String db = "DB.xml"; private String dbdir = null; // private boolean v = false; private String[] additional; }
and add a parse method:
/** * Parse command line inputs */ private static void parseArguments(String[] args) { try { JCLO jclo = new JCLO(new NanoHTTPDArgs(), args); // Allow debug to override log4j on the command line v = jclo.getBoolean("v"); // Allow override APP directory, w/trailing slash confdir = jclo.getString("confdir"); // Allow override of log4j xml configuration file log4j_xml = jclo.getString("log4j_xml"); // Allow override DB directory, w/trailing slash dbdir = jclo.getString("dbdir"); // Allow different database connections // i.e.: Database_derby_client.xml, or Database_oracle_client db = jclo.getString("db"); } catch (IllegalArgumentException e) { String[] arg2 = { "Debug" }; log(stderr,"---> App: Unknown parameter <---"); for (int i = 0; i < args.length; i = i + 1) { // Test and Loop log(stderr,args[i]); } JCLO jclo2 = new JCLO(new NanoHTTPDArgs(), arg2); log(stderr,jclo2.usage()); } } // parseArguments
then finally parse the main input parameters, like this:
public static void main( String[] args ) { /* parse the arguments */ parseArguments(args); . . . // call all the normal methods here // then print some results to System.out.println(...); // and exit with a zero, one, two, etc... so the unit test driver can determine success }
If the class is called from command line, main is called, does some processing and returns results via stdout, or if the class is instantiated via another class, the same processing may be done and returned results sent back via some object result set. See? NanoHTTPD.java
You must be logged in to post a comment.