Getting Started with Java Development and Testing: Netbeans IDE, Ant & JUnit 1. Introduction These tools are all available free over the internet as is Java itself. A brief description of each follows. They are demonstrated in this example. Java 1.4.x Netbeans Ant latest version of Java. Online documentation and tutorials are also available. http://java.sun.com/ http://java.sun.com/j2se/1.4/download.html http://java.sun.com/docs/books/tutorial/ an integrated development environment. Includes Ant. http://www.netbeans.org/ http://www.netbeans.org/ide/download.html tool/classes for automated building and other tasks of software development. http://jakarta.apache.org/ant/ http://jakarta.apache.org/ant/manual/index.html JUnit tool/classes for unit testing and reporting of code during development http://junit.org/index.htm To illustrate the usage of these tools as we might want to use them in extreme Programming (tests are written first) we will introduce a simple example application. 2. Example Application The application supports the following stories. a. There shall be 4 states. Start, Finish and 2 intermediate states. b. The user may navigate between states by using 2 buttons, Next and Back. c. The interface will visually provide feedback about the current system state. A sketch of what the application s Graphical User Interface (GUI) might look like follows: Figure 1: Sketch of application s proposed user interface (GUI). > Start State 1 State 2 Finish Back Next Tools H.O. CS435, M. Wainer page 1
Notice that the stories are small and lack many details. The idea is that a customer is readily available for the development team to meet with to work out the details. In this example we will assume all the stories should be implemented for this iteration. The first code that should be written (aside from a spike if necessary) are the tests which help to define what your objects should do. 3. Readying the basic classes Sometimes it is hard to think about the basic objects when all you can imagine is the sketch of the GUI. However, it is commonly recognized, that the GUI and the underlying functionality are most often better implemented as separate but cooperating objects. The underlying functionality (often referred to as the model) is independent of the particular GUI used to represent and manipulate it. We can develop the model separate from the interface. This better allows porting to different window systems, development of GUI s by trained GUI designers, better used of specialized hardware, etc. There are 2 immediate advantages for us: 1. Development can proceed without needing to know GUI programming; 2. Unit tests are easily automated if you don t need to worry about the GUI. Thus we focus first on the model component. This object should be able to represent the states required. It should support state transition functions and be able to respond to queries about what state it is currently in. We call the object type, StateModel. Figure 2: The class which represents the model for the application. attributes methods StateModel curstate: keeps current state getstate(): returns current state nextstate(): go to next state backstate(): go back one state Now we can begin to write tests which confirm the behavior of the model. The tests are to be written in Java using JUnit. Writing the tests within the Netbeans environment will allow Ant to automatically, compile, execute and generate reports about the tests. To start, bring up Netbeans and start a new project.(if this is the first time that you are using Netbeans you may also be asked to specify a file to store your settings. On the Windows machines in the lab use H:NetBInfo. The H drive is networked so you will have access to your settings even when you use a different PC.) a. Use the Project menu to bring up the Project Manager. b. Create a New project (the example name used is toolex) c. Have a directory ready for your new project. Under the Netbeans Explorer you will need to mount a file system for your project. Make sure the Netbeans Explorer is in Filesystems mode. To mount your directory, use the File --> Mount Filesystem menu and specify a Filesystem which is a Local Directory. Select your project directory. d. To create Java code, you will need to be ready to define what package that the code will be in. Java relates packages to directory structure so a package name will (likely) use a subdirectory of Tools H.O. CS435, M. Wainer page 2
your project directory. The subdirectory is set up for you if you use File --> New and select the Template for a Java Package. We use the name cs435ex1 as the package name. e. Finally to start creating your class source code: Select File --> New using the Java class template with the cs435ex1 package and the class name StateModel. A Java source file is created to define the class StateModel. Of course, the details remain to be filled in. f. In addition to the basic class needed for your application, you also need to define a class which will carry out tests. Select File --> New again and this time use the cs435ex1 package and the class name StateModelTester. 4. Preparing To Test The purist approach to Test-First software development writes tests which will always fail at first. They fail because the code for the objects being tested hasn t been written yet so the test cannot possibly pass. Our tests will use the JUnit testing framework. Here s a start /* * StateModelTester.java * * Created on August 10, 2002, 3:58 PM */ package cs435ex1; import junit.framework.*; File name also gives class name We use the JUnit framework /** * * @author wainer */ public class StateModelTester extends TestCase { /** Creates a new instance of StateModelTester */ public StateModelTester(String aname) { super(aname); extending the JUnit class TestCase Using reflection, tests are collected and run public static Test suite() { return new TestSuite(StateModelTester.class); public static void main(string[ ] args) { junit.textui.testrunner.run(suite()); Main will run a GUI application which performs and displays the tests Our test methods all begin with test public void testinitialstate() { StateModel model = new StateModel(); assertequals("check initial state", StateModel.START, model.getstate() ); JUnit provides assert methods for testing Figure 3: A start on the StateModelTester class which uses the JUnit framework to test the StateModel class. Tools H.O. CS435, M. Wainer page 3
Of course, this test will fail to even compile since the method getstate() and constant START are not even defined. A purist approach would be to try the test and let the compiler tell you what you need to fix. The advantage of this method is that you won t be implementing things unless you absolutely need them. We will go ahead and add some code to the StateModel class so at least things will compile. Our bare-bones implementation follows. /* * StateModel.java * * Created on August 10, 2002, 11:26 AM */ package cs435ex1; /** * * @author wainer */ public class StateModel { static final int START = 1; added to allow compilation /** Creates a new instance of StateModel */ public StateModel() { public int getstate() { return 0; Figure 4: Adding just enough code to StateModel so that the first test can compile. 5. Using Ant to Compile Netbeans, like most IDEs, can automatically compile and run the software you write. If you ve developed software on UNIX systems you are probably familiar with make, an application which reads a file of dependencies and instructions to automate development tasks. You can think of Ant as a Java based make replacement. Ant isincluded with Netbeans. By default, Ant sinstructions arein afilenamedbuild.xml. Indeed, the build file is in plain text with xml formatting. It may be created by Netbeans and it is recognized with a special icon in the Netbeans Explorer window. From that window you can run any target (you specify each task of interest as a target) by double-clicking on its name. From the File --> Open window you may select an Ant build file template. If you select the empty one you will get a very small file. Replace its contents with the following. Tools H.O. CS435, M. Wainer page 4
Note: E:\netbeans\modules\patches\org-apache-tools-ant-module\junit.jar shown as a pathelement location in the build.xml file must give the path location of the JUnit jar file. You will need to obtain this jar file. Read the installation instructions or use the path given as an indication of where it should be placed. If you do not set this up correctly, the example will fail to compile. Create your build file within your project directory not inside your package directory (cs435ex1). <?xml version="1.0"?> <!-- build.xml, for Ant to compile Java sources, cs435ex1, Wainer --> <project basedir="." default="compile"> <target name="compile"> <javac debug="true" deprecation="true" destdir="." srcdir="." verbose="true" > <classpath> <!-- gives location of junit framework objects to java compiler --> <pathelement location="e:\netbeans\modules\patches\org-apache-tools-ant-module\junit.jar"/> </classpath> </javac> </target> </project> Assuming your build.xml file is as above (with a proper pathelement location to the junit jar file) you should see the build.xml icon in the Netbeans Explorer window. Click the show inside lever icon to its left, and the target compile should appear. Double-click on the compile target to make Ant execute that task. If all goes well your code should successfully compile. The IDE will switch to the Running window work-set. To get back to the Editing work-set click its tab or use the View --> Workspaces menu selection. 6. Using Ant to Run Tests Replace with the proper path location for your system Figure 5: An Ant build.xml file to compile the StateModel and its tester class. To run the JUnit tests the code first has to be compiled and then run. The build file will need another target; we ll call it utest. The additional text to be added is shown below. Double-click on the utest target in the Netbeans Explorer to run the unit tests. <target depends="compile" description="execute Unit Tests" name="utest"> <java classname="junit.swingui.testrunner" failonerror="true" fork="true"> <arg value = "cs435ex1/statemodeltester" /> <classpath> <pathelement location="e:\netbeans\modules\patches\org-apache-tools-ant-module\junit.jar"/> <pathelement location="."/> </classpath> </java> </target> Figure 6: Additional target to add to the build.xml file to enable running the Junit tests. Insert between the <project> </project> tags. Tools H.O. CS435, M. Wainer page 5
If you were successful, you will see the JUnit testing application pop-up (Figure 7). If you had problems, consult Figure 8 to make sure that your files are in the proper places. Project Directory Figure 8: The directory structure of the files used in this example as shown by the Netbeans Explorer. Double-clicking on utest should launch the testing application after making sure that the code is compiled. Figure 7: The Unit Test should compile and run but as expected will fail during execution. 7. Coding to Pass the Test Production coding (and testing) with extreme Programming is to be done with Pair Programming. That aside, the tests should drive the code. We have a test that failed so we must code what is needed to pass it. In this case, we need to maintain the state as a variable and set it properly when initialized. The state also needs to be accurately retrieved. See the modifications in Figure 9 below. Running the utest target should now result in the test passing. package cs435ex1; // make sure to include the package statement public class StateModel { static final int START = 1; private int curstate; /** Creates a new instance of StateModel */ public StateModel() { curstate = START; public int getstate() { return curstate; Figure 9: Adding code to so that the StateModel passes the test. Just enough code was added to pass the test. Tools H.O. CS435, M. Wainer page 6
8. Repeating the Cycle: Test Coding, Testing, Code Corrections to Pass Tests Obviously we aren t finished yet. Many cycles of writing tests, running tests and coding are needed. A cycle takes on the order of 15 minutes. Figure 10 shows tests for the back andnext navigation methods. Figure 11 shows the modified source for StateModel so that it passes the tests. Use the code in Figures 10 and 11 to further advance your implementation: testing with the code in Figure 10 before applying the code in Figure 11. public void testbasicnavigation() { StateModel model = new StateModel(); model.nextstate(); assertequals("next to State1", StateModel.STATE1, model.getstate() ); model.nextstate(); assertequals("next to State2", StateModel.STATE2, model.getstate() ); model.backstate(); assertequals("back to State1", StateModel.STATE1, model.getstate() ); model.nextstate(); // move to state2 model.nextstate(); // move to finish assertequals("next to Finish", StateModel.FINISH, model.getstate() ); model.backstate(); assertequals("back to State2", StateModel.STATE2, model.getstate() ); Figure 10: Tests for typical navigation behavior using backstate and nextstate methods. public class StateModel { static final int START = 1; static final int STATE1 = 2; static final int STATE2 = 3; static final int FINISH = 4; private int curstate; /** Creates a new instance of StateModel */ public StateModel() { curstate = START; public int getstate() { return curstate; Figure 11: Code to support typical navigation behavior using backstate and nextstate methods and a current state variable. public void nextstate() { curstate++; public void backstate() { curstate--; We should make sure to test problematic areas. For example, what should happen if we try to move back from the start state or forward from the finish state? The customer should determine Tools H.O. CS435, M. Wainer page 7
what sort of behavior makes sense. Part of the customer responsibility is to write tests for the stories. In this case, our rules will be that a back move from state start does nothing and a forward move from state finish does nothing. Figure 12 shows the test code for these exception cases. Your test cases should make sure to cover boundary conditions and other unusual cases which may be encountered. As Figure 13 illustrates, the exception case tests fail. public void testbndrynavigation() { StateModel model = new StateModel(); model.backstate(); assertequals("back from Start", StateModel.START, model.getstate() ); model.nextstate(); model.backstate(); model.backstate(); model.backstate(); assertequals("back again from Start", StateModel.START, model.getstate() ); // move from start to S1, S2, Finish model.nextstate(); model.nextstate(); model.nextstate(); assertequals("next to Finish", StateModel.FINISH, model.getstate() ); model.nextstate(); model.nextstate(); model.nextstate(); // and try to move further assertequals("nexts at Finish", StateModel.FINISH, model.getstate() ); Figure 12: Tests for navigation behavior at border conditions. Figure 13: Earlier tests pass, but the test for navigation at boundary conditions fails. The detail gives the assert statement which caused the failure. Figure 14: Revising the nextstate and backstate methods as shown can fix the problem. All tests now pass. public void nextstate() { if (curstate < FINISH) curstate++; public void backstate() { if (curstate > START) curstate--; Extreme Programming promotes the idea You Ain t Gonna Need It (YAGNI). Meaning that we shouldn t be spending a lot of effort designing for all future possibilities. The YAGNI principle often applies since many future plans are frequently changed or cancelled. We used that principle here to design a very simple mechanism for supporting state change. Tools H.O. CS435, M. Wainer page 8
9. Creating and Compiling the Application and Interface So far we have created the underlying object model and tests for that object. We need to create the actual user application which calls upon the model and supports interaction with the user. While progress is being made in automatic testing tools for GUI code, we will not consider that here. We construct a simple Swing interface for a Java application, a screen shot is shown in Figure 15. You may also use visual tools to design an interface. (Netbeans has a GUI editor. The CS Department also has Visual Cafe). In Xp you might use a spike to explore and experiment to quickly test out ideas. Any code developed as part of a spike is not consider production code (It wasn t developed test-first with pair programming). It should be discarded. Figure 15: A java application which uses Swing to provide a GUI for the statemodel. As yet it is not hooked up to the StateModel code. It also fails to provide the user with sufficient feedback since the current state is not indicated. /* * StateApp.java * * Created on August 11, 2002, 2:54 PM */ package cs435ex1; import javax.swing.*; import java.awt.*; import java.awt.event.*; Figure 16: StateApp.java, source code to create the application and interface shown in Figure 15. /** * * @author wainer */ public class StateApp { JLabel startlabel; JLabel state1label; JLabel state2label; JLabel finishlabel; JButton nextbut; JButton backbut; public StateApp() { Tools H.O. CS435, M. Wainer page 9
Figure 16 continued. public static void main(string[] args) { JFrame frame = new JFrame("Example State Application GUI"); StateApp app = new StateApp(); // Create Buttons app.nextbut = new JButton("Next"); app.nextbut.addactionlistener(new ActionListener() { public void actionperformed(actionevent e) { // make the button click do something ); app.backbut = new JButton("Back"); app.backbut.addactionlistener(new ActionListener() { public void actionperformed(actionevent e) { // make the button click do something ); // group the buttons in a JPanel JPanel buttonpane = new JPanel(); // uses default flow layout buttonpane.add(app.nextbut); buttonpane.add(app.backbut); // Create the labels and have them align at their horizontal centers app.startlabel = new JLabel("Start"); app.startlabel.sethorizontalalignment(jlabel.center); app.state1label= new JLabel("State 1"); app.state1label.sethorizontalalignment(jlabel.center); app.state2label= new JLabel("State 2"); app.state2label.sethorizontalalignment(jlabel.center); app.finishlabel= new JLabel("Finish"); app.finishlabel.sethorizontalalignment(jlabel.center); JPanel labelpane = new JPanel(); // group the labels in a JPanel // Use a GridLayout to align the labels in a single column labelpane.setlayout(new GridLayout(4,1)); labelpane.add(app.startlabel); labelpane.add(app.state1label); labelpane.add(app.state2label); labelpane.add(app.finishlabel); // Place the buttons and labels into the frame frame.getcontentpane().add(buttonpane,borderlayout.south); frame.getcontentpane().add(labelpane,borderlayout.center); frame.addwindowlistener(new WindowAdapter() { // so app exits upon window closing public void windowclosing(windowevent e) { System.exit(0); ); //Finish setting up the frame, and show it. frame.pack(); frame.setsize(340,300); frame.setvisible(true); Tools H.O. CS435, M. Wainer page 10
The source code for StateApp.java must be added to the project directory. To compile, build.xml should be modified to add another target for Ant. Insert the following to create a run target within your build.xml file. <target depends="compile" description="run the Application" name="run"> <java classname="cs435ex1.stateapp" failonerror="true" fork="true"> <classpath> <pathelement location="."/> </classpath> </java> </target> Figure 17: Ant target to compile and run the application. 10. Connecting the Application to the Model The basic idea is to make the application act as a wrapper around the functionality of the model. The application should instantiate a model object. It queries the object and reflects the object s state in its interface. User interaction should be passed from the application to the model. In a more sophisticated application we would probably use a Model-View-Controller pattern. Following the YAGNI idea we just keep it simple here. The following code fragments can be added to the StateApp source to produce the connected working application. public void updatelabels(statemodel m) { // set all to unselected - black startlabel.setforeground(color.black); state1label.setforeground(color.black); state2label.setforeground(color.black); finishlabel.setforeground(color.black); // query the model to find and show the current state int thestate = m.getstate(); switch (thestate) { case StateModel.START: startlabel.setforeground(color.red); break; case StateModel.STATE1: state1label.setforeground(color.red); break; case StateModel.STATE2: state2label.setforeground(color.red); break; case StateModel.FINISH: finishlabel.setforeground(color.red); break; model.backstate(); // for back button only model.nextstate(); // for next button only app.updatelabels(model); //Make sure the true initial state is shown app.updatelabels(model); Define another method for StateApp. updatelabels is responsible for making sure that the interface reflects the state of the model. Red letters indicate the current state. Put final in front of the declaration for app andaddalinetodeclarea model object. final StateModel model = new StateModel(); The button handlers act on the model and then query the model s state. (add to both button s actionperformed methods) To make sure the initial state is visualized correctly, query the model s state. (add before frame.pack() ) Figure 18: Code to add to StateApp.java to create the application and interface which utilizes the model. Tools H.O. CS435, M. Wainer page 11
11. What Next? If automatic tests aren t used to check the application/interface, manual tests are used. These tests may be associated with the stories and are provided by the customer (or by closely working with the customer to establish the tests). Developers should also get frequent feedback from the customer about the interface design and appearance. At some point, suggestions must be expressed as new stories and the customer must make the business decisions about which stories have higher priorities. (Fixing bugs/glitches are necessarily compared with the value of adding new features.) 12. Refactoring After a while, changes in code build up and you often realize that things are more complicated than they should be. The code is said to start to smell and it starts to become more error prone and difficult to maintain. Refactoring is the process of reorganizing the code. One way to tell if refactoring is appropriate is to notice if you have nearly identical code which repeats itself several times. In our example, creating JLabels uses several calls which are nearly identical for each label. A refactoring might create a new method which consolidates those calls. Figure 19 shows such a method and how it might be used. // add this method in StateApp public JLabel initlabel(string text) { JLabel lab = new JLabel(text); lab.sethorizontalalignment(jlabel.center); return lab; // Creating Labels is now much simpler labelpane.add(app.startlabel = app.initlabel("start")); labelpane.add(app.state1label = app.initlabel("state 1")); labelpane.add(app.state2label = app.initlabel("state 2")); labelpane.add(app.finishlabel = app.initlabel("finish")); Figure 19: An example of refactoring that applies to StateApp. The code which creates new JLabels has been simplified by introducing a new method. 13. Deployment Since it is important for your customer to run your application, you must consider how they will be able to do this with a minimum amount of fuss. This is how to make a java application doubleclickable on a Windows machine. It assumes that Java and any necessary class libraries are already loaded. It also assumes that the PATH variable includes the java binaries directory. Italics below show my values for the example application. The javaw.exe executable runs a java class file (with a main entry point) without opening a command window. a. Create a shortcut for the StateApp file. Place it in your project directory b. Edit the properties of the shortcut. Change the Target: to run javaw C:\WINNT\SYSTEM32\javaw.exe -classpath "." cs435ex1.stateapp Change the Start in: field to your project directory. E:\AANewJava\MyCodeTests\ToolsHO c. Change the name of the modified shortcut to something sensible like Run StateApp Tools H.O. CS435, M. Wainer page 12
Common Problems Extra subdirectories appear Make sure you have included the package statement in your source files. Check the relative positions of your files (see Figure 8) Can t compile test code Make sure the JUnit files are installed and the path element in your build file is given for its location. Annoying glyphs appear in the source editor The editor is scanning the files and if it can t find a particular package (like JUnit) all the functions from that file will be flagged with a red boxed x. You can tell the editor where the package is located, the scan will succeed and the glyphs will go away. From the main window open the Tools Menu and select Options (see Figure below). You ll want to pick the Expert tab of the Java Sources section and set the Parser Class Path so it gives the location of the package which the IDE can t find. You may have to exit and restart the IDE before it notices that the statements can be parsed and removes the glyph. Update this path Tools H.O. CS435, M. Wainer page 13