I have JUnit plugged into my JBuilder environment. Here are the goals driving my setup:
1) The customer jar file must be as small as possible, since I plan to follow XP's rules of making many small releases rather than fewer huge ones. I plan on making updates available on our ftp/web site, and (as of today) we only have an ISDN link, so I must minimize bandwidth required.
2) The code being tested should not be different than the code being shipped. IE I don't have any DEBUG=true sort of things in my production code.
3) I don't want the project source tree cluttered up with test sources. This keeps the project pane manageable inside JBuilder.
Here are the steps I took, listed in detail, since one of my personal dislikes is asking a question and getting such a high-level answer that a beginner wastes hours floundering about cluelessly trying to figure out what the expert meant.
Build JUnit and Test It
Outside of JBuilder, cd into your projects area, create a JUnit subdirectory, and unpack the JUnit install file. This gets you docs, sources, and perhaps a pre-built jar file. Now go into JBuilder and create a new JUnit project. Set the project's src, doc, and output paths to match the directory you just built. Let JBuilder use its default classes output path. Ignore the pre-built jar file.
Using Jim Cook's "multifile" OpenTool, I added the whole junit package to the project. (I may have manually moved things around a little to fit JBuilder's layout, I don't recall. All I had to do was stick the source code into a src directory. http://www.javabuilders.com/opentools/multifile.html
Under project properties, set the Main class to 'junit.swingui.LoadingTestRunner.' Set Application parameters to 'junit.tests.AllTests.'
Here's a subset of my JUnit.jpr file for reference:
sys[0].OutPath=classes sys[0].SourcePath=src sys[0].DocPath=doc sys[0].Title=JUnit runtime.0[0].application.class=junit.swingui.LoadingTestRunner runtime.0[0].application.parameters=junit.tests.AllTestsBuild the JUnit project and get it to run its own tests. If you setup the directory paths properly, JBuilder will even find the javadocs using the Doc tab in the editor pane, too.
Create a JBuilder Library
Under 'Project Properties/Paths/Required Libraries', click 'Add...', 'New...', and see the 'Edit Library' dialog. Put 'JUnit' in the name, and click 'Add...'. Navigate to the JUnit project's classes directory and hit 'ok'. Fill in the 'Source' and 'Documentation' tabs appropriately pointed at JUnit project's src and doc directories. Click 'OK' to save the new library definition. Back in the 'Select one or more libraries' dialog, click 'cancel'. (We don't need to make JUnit refer to itself in the classpath.) I point at the class dir rather than the jar file because I believe the jar files are slower.
Create a Testing Project
Since XP says to create tests before code, I'm going to create the Test project first. Assuming the project name is 'Blah', create a new project TestBlah. In its src directory, 'mkdir your', 'mkdir your/package', 'mkdir your/package/tests', and then copy JUnit/src/junit/tests/AllTests.java into src/your/package/tests. This gives you a starting point. (Substitute your real package name where needed.)
Now go into TestBlah's project properties, and add the JUnit library we created previously as a required library. JBuilder will now know to place JUnit in the TestBlah application classpath.
Go into the 'Compiler tab' and uncheck check stable packages and make stable packages. This prevents JBuilder from recompiling JUnit and cluttering up the classes path with duplicate class files. Under the 'Run' tab, set the main class to 'junit.swingui.LoadingTestRunner', and the 'Application Parameters' to 'your.package.AllTests'.
Now go into 'AllTests.java' and edit it appropriately, starting with the 'suite()' method. Delete everything in it, basically. Then run the tests. It should pass, because it's not actually testing anything. If they don't run, something is configured improperly in your TestBlah project.
Now create a UnitTest. Copy one from the JUnit tests to use as a sample, rename it, and edit it to leave the infrastructure behind. (Basically, rewrite 'setUp()', 'tearDown()', and 'suite()'. A good sample to select would be one which contains a 'main()' method. This main() method calls 'textui.Testrunner.run(suite())' and makes a very handy way to quickly run just one set of tests without having to wait for all of Swing to load. (Right-click on a Test's java file, select 'make', select 'run'. Sweet.)
Create the Project
Now use JBuilder as usual to create your Blah project. Use package scope for everything you want to write a UnitTest for. When it compiles, switch back to your TestBlah project (use the dropdown list of open projects in the toolbar immediately above the project pane.) Go back into TestBlah Project Properties and add the new 'Blah' project as another required library. It doesn't matter what order they are listed. You don't need to list Blah's source path in TestBlah's properties.
Now build and run the tests, either by running the whole TestBlah project or by running just the Java file.
Comments
I prefer to use the reflection method of finding tests. This minimizes the changes required to implement new test methods. 'AllTests.suite()' uses the form:
public static Test suite() { TestSuite suite= new TestSuite(); suite.add( TestClass.suite() ); }This requires you to remember to add new 'suite.add()' calls when ever you add new class files to the TestBlah project. I occasionally forget to do this.
TestClass.java contains, in part:
public static Test suite() { TestSuite suite= new TestSuite( TestClass.class); }This uses reflection to find all the test* methods within the compiled class. (Too bad this doesn't work easily for AllTests!)
By using the LoadingClassRunner within the TestBlah project, you can start the Gui test runner once and leave it up. Use the test class browser to reload tests as needed. You don't have to reload anything if you're changing just Blah code, as I recall. If you recompile a test class, you will have to use the Gui's reload button to refresh it.
One problem with the right-click/run method is that the Text ui truncates the output, especially if its printing exceptions. I haven't looked into why, yet, but plan to if the next JUnit release still does it.
I create a separate Test class for every Project class. I name the test methods within each Test class to correspond to the method names in the Project class. IE ShunWizard.class has TestShunWizard.class, and where ShunWizard.class contains methods a, b, and c, I'll have methods testA, testB, and testC (which looks a little odd in this example - I follow Sun's naming conventions, so the capitalization in the Test class differs slightly from the Project class.) -- JoiEllis
I use a Default project file like the following:
sys[0].Libraries=Jini;Junit 3.2 sys[0].OutPath=classes sys[0].SourcePath=src;test sys[0].DocPath=docI then usually create two different projects one called project.jpr and another called project_test.jpr. In the former I put "src" at the top of the path order and in the latter, I make "test" the first path. Then, whenever I want to create a new TestCase, I switch to the project_test.jpr so it can be in the same package as the ClassUnderTest, but is put into a different directory structure. To JBuilder, it looks like one package hierarchy. This way, I can JavaDoc or JarFile? my system without including the TestCases. Of course, if I want to, they are easy to add, but this is rarely needed for distribution. I also set up a Library for JavaUnit by adding the following lines to my library.properties file:
library.sourcepath.Junit\ 3.2=<my_junit_path>/junit3.2/src library.classpath.Junit\ 3.2=[<my_junit_path>/junit3.2/junit.jar] library.docpath.Junit\ 3.2=<my_junit_path>/junit3.2/javadoc library.required.Junit\ 3.2=Then I edited the libray.names= line to include ";Junit\ 3.2". You can still use this setup without creating the additional project file, but then you have to go and manually change the source path order in Project Properties to switch between creating production classes and test classes. I find it more simple to just do the following after creating any project:
D:\projects\my_project> copy myproject.jpr myproject_test.jprThen I just make sure "test" is before "src" in the myproject_test.jpr. This makes life very easy! Then I just make sure "test" is before "src" in the myproject_test.jpr.
It looks like you're doing basically what I'm doing except that I wanted to actually see JUnit run its own tests. At the time, I didn't have anything else to use as an example for using Junit itself. I now have the JUnit library configured as a required library in my Default Project so it appears automatically for all new projects.
I also use the "same-package-different-directory" trick to separate Jar and JavaDoc contents. -- JoiEllis
I also created the following abstract class to simplify the use of junit.framework.TestCase and ease the subclassing of existing test cases. This class (and the general methods I use for extending it) makes it easier to create a test class hierarchy that matches the hierarchy of the classes they test. Even if you don't want to subclass your test cases, the AbstractTestCase class still makes writing tests a lot simpler by removing the need to create a static main for every test.
Here's the basic code:
public abstract class AbstractTestCase extends junit.framework.TestCase { // An instance of the active test's metaclass static Class sm_test = null; //* @param test is used to set the active metaclass object static void setActive( Class test ) { sm_test = test; } //* The single shared <tt>main</tt> static public void main( String[] argv ) { junit.textui.TestRunner.run( m_test ); } //* @param sName the name of the test class public AbstractTestCase( String sName ) { super( sName ); } }That's all the infrastructure needed! Now, you can create new test case classes simply by extending AbstractTestCase and adding a static block to set yourself as the active class. For example:
public class DNodeListTest extends AbstractTestCase { ///--Abstract Test Case Requirements //* Set active class in a static block (called by JVM) static { setActive( DNodeListTest.class ); } //* Public ctor required by the JUnit TestRunner public DNodeListTest( String s ) { super( s ); } ///--The TestFixture code specific to NodeListTest protected DNodeList m_nodes; // in same package //* Called before running each test method public void setUp() { // Set only if not already set by a subclass if ( m_nodes == null ) m_nodes = new DNodeList(); <populate the list with test data> } //* Called after running each test method public void tearDown() { m_nodes.clear(); m_node = null; } ///--The NodeList test cases... public void testInsert() { m_nodes.insert( new Integer(5) ); assertEquals( m_nodes.first(), new Integer(5) ); } <and so on for each member of NodeList?> }That's pretty much it. Now you can simply right click on NodeListTest in the workspace and select either "Run" or "Debug". There is no need to write main as it will use the static main of its superclass. There is also no reason to deal with suite.add as it is done through reflection.
I'm unclear about why you have to edit the source paths on your projects. I neglected to mention that my TestBlah project points at the Blah project as a required library in addition to the JUnit library. So, my two JBuilder projects don't cross-pollinate beyond loading the classes at runtime.
My TestBlah project consists of about two dozen different classes. How does one use reflection to run them all en masse without using a suite() method in AllTests? -- JoiEllis
How did you create your Blah project so you could use it as a library. I use JavaUnit while creating the Library, I don't see much use in creating another project just for testing. My "project_test.jpr" is simply a convenience to direct a TestCase.java file to a subdirectory of test instead of a subdirectory of src. In your model src contains the Blah source code that are not tests. How else could you decide, if creating a new class named Debug, whether it should be saved as:
d:\projects\blah\src\com\xyz\util\Debug.javaOr as:
d:\projects\blah\test\com\xyz\util\Debug.java-- RobertDiFalco
I think you misunderstand my layout. My src directory does not mix tests and non-tests, that's exactly what I'm trying to avoid doing!
There's nothing at all special about a project being used as a JBuilder library. You simply create a library definition (either by editing the library.properties file, or using the IDE itself). All it really does is put the Blah project's classes directory into the CLASSPATH variable when running the TestBlah stuff.
Here's my layout:
Project Blah: /home/joi/jbprojects/Blah/src /home/joi/jbprojects/Blah/classes /home/joi/jbprojects/Blah/doc Project TestBlah /home/joi/jbproject/TestBlah/src /home/joi/jbproject/TestBlah/classes /home/joi/jbproject/TestBlah/doc Required Libraries: JUnit, Blah Project JUnit /home/joi/jbproject/JUnit/src /home/joi/jbproject/JUnit/classes /home/joi/jbproject/JUnit/docBlah is the main project. When I want to write a new test, I switch over to the TestBlah project.
Blah and JUnit are also defined as JBuilder Libraries, so TestBlah can refer to them easily. I could have simply added their src paths to TestBlah''s source path, but I chose to use libraries instead because I think libraries are cleaner and they unclutter the Test tree's classes directory. It also allows me to run JavaDoc on the Blah project tree without polluting the results with test crap.
I do it this way to accomplish my previously mentioned goals: keeping the Blah project's project pane uncluttered with test crap, keeping the Blah.jar file as small as possible by excluding the test classes from the jar, and keeping the Blah classes unaware that there is a test harness available to exercise them. The classes I ship are identical to the classes I test.
I like the idea of an abstract test class with a main() in it - some of my test classes do not provide a main method because the sources I used as templates did not have a main().
I think we're doing nearly the same thing, we're just using slightly different setups to accomplish it. I chose to stick with JBuilder's intuitive model for laying out source code into projects rather than struggling to figure out how to coerce JBuilder into allowing me to have two src trees but one class directory. -- JoiEllis
I think I understand, it just seems as if it introduces another couple of steps - creating a library for the code you are developing (which none may exist when starting out) and then adding that to another project called test - and discourages TestBeforeCode? since you view your tests as a distinct project from your source code. To me they are the same project. In fact, the whole reason for the "src;test" source path solution was to make them act as one project and package structure. I don't consider tests clutter because I use TestBeforeCode?. However, if I did, I could always remove "test" from the "src;test" string in the "project.jpr". As I said previously, if JBuilder had let us easily toggle the source path order each time a file was created I wouldn't even need to create a test project. Think of it this way. The Test Project doesn't exist to organize things, it only exists to provide a context switching mechanism so I can quickly toggle where a file is saved - in "src" or in "test". The whole idea is to have the project workspace's package tree-view look the same regardless of whether I am creating a test file or a production file. Does this explanation do a better job of clarifying things? It's a very different approach. As you want the a test project, my attempt was to eliminate a test project but to still have separation.