|
To provide a framework that we can perform client side testing with all
standard expresso services such as logging, database connection management,
and many others we provide the following class:
com.jcorporate.expresso.services.test.ExpressoTestCase
To
create your own test case use the following sample as a model:
import junit.framework.*; import junit.extensions.*; import com.jcorporate.expresso.services.test.*;
public class SampleTestSuite extends ExpressoTestCase { public SampleTestSuite(String name) { super(name); }
public static void main(String[] args) throws java.lang.Exception { //Set the system properties we need junit.textui.TestRunner.run (suite()); }
public static junit.framework.Test suite() { return new TestSuite(FilterTest.class); }
public void test1() { //Do your testing code here }
public void test2() {
//Do more testing here
}
public void test3() {
//You can do more testing here
}
/*....Do as many test cases as you want, all with the public void signature */
}
How to Run this Test Case
To run the test case, you need
to set the following Java Virtual Machine parameters to
junit.argv.configDir=<Full Path
To Your Configuration Directory>
junit.argv.webAppDir=<Full Path To Tomcat's Webapp Directory>
Usually in the command line you'll
do this by having the command line:
-Djunit.argv.configDir=<blah
blah blah> -Djunit.argv.webAppDir=<blah blah blah>
The JUnit framework will automatically
use Java Introspection to run all the public(void) test cases. See the
JUnit documentation on how to use the framework to signal if a test
passed or failed. Also, see the class: com.jcorporate.expresso.core.misc.CookieTests
to see a fully functional sample of integration with JUnit Framework.
All expresso test case classes
will play well with the JUnit test runners. So if you wish to run a text-based
only test case, you can run junit.textui.TextRunner. If you wish to
run within a nice graphical environment, you can run your test suite
with:
To run all test cases within a
graphical environment, run the class:
junit.swingui.TestRunner
use the UI to pick the class you
wish to test and let her run.
The -c will determine which test
suite to run. You can use your own test cases, or use the class sample
above to run all Expresso test cases at once.
Picture of JUnit's Swing Test Runner doing its stuff.
Writing your Test Case Guidelines
There are a couple of necessary
guidelines to follow when writing your own test cases.
- Make sure that your tests are
fully automated. This will indeed pretty much take the most work in
creating your test cases, but without that automation, it is _IMPOSSIBLE_
to spot any minor errors that creep into your code while refactoring.
It cannot be stressed how much your time will be saved in the long
run if you can think of ways to automatically check for proper behavior.
- Whenever you need the expresso environment properly running, make sure
you derive from ExpressoTestCase.
- Always include a main() function. This allows for extremely quick testing
while you're building your classes and tests.
When we're first creating our application,
we want to make sure it runs correctly, right? But as we're developing
our schema, and creating out databases, we have to each time go through
the monotonous task of dropping a database, recreating it, starting
up Expresso, running DBCreate, etc. This quickly becomes quite a chore.
So we've created a special test case for testing Schema Creation. Here's
all the code you need. The following example is borrowed directly from
eForum and is complete:
import junit.framework.*; Import com.jcorporate.expresso.services.test.*; Import com.jcorporate.expresso.core.utility.DBToolTests;
public class SchemaTests extends DBToolTests { public SchemaTests(String testName) throws Exception { super(testName); }
public static void main(String[] args) throws Exception { //Set the system properties we need junit.textui.TestRunner.run (suite()); }
public static junit.framework.Test suite() throws Exception { return DBToolTests.suite(); }
protected void setUp() throws Exception { //System must be initialized prior to instanatiating the schema instance TestSystemInitializer.setUp(); Class c = Class.forName("com.jcorporate.eforum.ForumSchema"); schemaList.add(c.newInstance()); super.setUp(); } }
That's it! To make this code work
for your schema all you do is change the ONE line in setup()
that loads ForumSchema to use your own schema class. When this class
is run, it will make sure that all tables are removed from the Test database,
and then attempt to create all schemas (Including Expresso's) in the test
database. It will also run separately populateDefaultValues() and setupDefaultSecurity()
so you can make sure that everything is cooprating.
This kind of testing is an extremely
helpful and powerful tool for testing your schemas as you're building
them.
The bulk of your web application
is going to be logic within an Expresso Controller object, or possibly
a standard Servlet. Unfortunately, standard JUnit client-side testing
procedures definitely fall flat in being able to cope in this area.
Enter Apache's Cactus project.
Cactus was designed to provide
the unification of JUnit's client side testing API and in-container server-side
testing. What it does is initiate a test case on the client side so you
can stuff all the HTTP request parameters with everything you need such
as state to request, login cookies, etc. The test case then serializes
itself to the server, where a specially designed servlet loads your test
class and executes the code on the server side. The result is then sent
back to the client side so you can examine cookies sent back to client
or other results.
Here is an animation created by
the Apache Cactus documentation project that highlights what was said
above.
The rest of the cactus documentation set can be found here.
If you are writing
a servlet, the Cactus documentation will be sufficient to get you started
on a servlet. Even if you're writing a controller test case, it is strongly
recommended that you browse through the Cactus website to get an idea
of how a Cactus test case is written since the controller test cases
are extensions to a standard Cactus test.
If you are writing
a controller test harness, there are quite a few things to consider
such as having a running underlying database, negotiating security,
and parsing the controller response. To assist in this, we've provided
the classes:
com.jcorporporate.expresso.services.test.ControllerTestCase
and
com.jcorporate.expresso.services.test.ControllerTestSuite
A Quick Sample
Below is a sample
showing a simple usage of a controller test case (suite). All comments
with a number are footnoted/explained in the area below the code sample.
Import com.jcorporate.expresso.services.test.*; Import com.jcorporate.expresso.core.controller.*; Import org.apache.commons.cactus.*; Import org.apache.commons.cactus.util.*; Import junit.framework.*; Import org.w3c.dom.*;
Import java.net.HttpURLConnection; import java.io.IOException; import javax.servlet.*; Import java.util.*;
Public class SampleControllerTest extends ControllerTestCase {
public SampleControllerTest(String name) {
super(name, "com.jcorporate.expresso.services.DBSecurityMatrix"); //1
}
public static void main(String[] args) throws Exception {
junit.textui.TestRunner.run(suite()); //2
}
public static TestSuite suite() throws Exception { ControllerTestSuite cts = new ControllerTestSuite(); //3 cts.addReadOnlySchemaDependency("com.jcorporate.expresso.core.ExpressoSchema"); //4 cts.addTestSuite(SampleControllerTest.class); return CTS; }
/* Executed on the client side of the web request */ public void beginPromptState(WebRequest theRequest) throws Exception { super.logIn(theRequest); //5 super.setupParameters("prompt",theRequest); //6 }
/* Executed on the server side of the web request */ public void testPromptState() throws Exception { ControllerResponse response = super.controllerProcess(); //7 assertTrue("Got a null response", response != null);
assertTrue("Title returned from the controller state.", response.getTitle().length() > 0); //8 }
/*....Do as many test cases as you want, all with the public void signature */ }
Code Explanations:
- The second parameter to the
ControllerTestCase's constructor is the name of the class you wish
to test. In this case it's Expresso's own DBSecurityMatrix controller.
- By writing a main method that calls JUnit's Text Test Runner, you have
the ability to test out your classes and test cases by just running
this class.
- The controller test suite is responible for having a proper underlying
test database running. If one doesn't exist, it will create it for
you automatically, (including schemas), and if you need it to be erased
when you're done, it will do that too.
- This line(s) tells the test suite what schemas you must have up and
running for your test case to work properly. To add more schemas,
simply repeat the call for other schemas. Use addReadOnlySchemaDependency()
if you DO NOT do any modifications to the underlying databases
by running your controller. This will tell the system to not delete
your created test database at the end of a run since it's still in
pristine state. Call addSchemaDependency() if your controller
modifies the underlying database in any way.
- super.logIn()set's the appropriate cookies for a user Admin
with a blank password. This is what is created by default in a test
database.
- The first parameter of setupParameters() is the name of the state that
you will be testing with this test run. In this case it's "prompt".
The function also sets the appropriate parameters for the Controller
to return an XML formatted output.
- The first confusing thing to know is that testXXXXXX() RUNS ON THE
SERVER.Everything up to this point has been running on the client
side. To process the controller, however, your job is simple, just call
the ControllerTest case's controllerProcess() function and the testing
framework will call your controller state automatically. This function
will return a ControllerResponse object back to you, in effect, the
data that the controller generated.
- Once you get the ControllerResponse object you can walk through it's
Inputs,Outputs,Blocks and Transitions to make sure they are as you
would expect them to be. Use JUnit's assertTrue() and fail() functions
to test if things were properly sent back.
You can take a look at the class
com.jcorporate.expresso.services.controller.test.DBSecurityMatrixTests
for a complete working controller test case that tests a couple of states
including getting the results of a form POST.
Running The Test Case
Software Requirements
To run the Cactus tests you need
the following software pieces.
- A database capable of dealing
with more than one virtual machine connection. Sadly, this eliminates
the possibility of using the Hypersonic Database that comes with Expresso
as-is. There is a driver out there that uses RMI that allows more
than one connection, but it is in a separate download available on
the Internet.
- A Servlet API 2.3 compliant servlet container. This includes containers
such as Orion and the Tomcat 4 bundle that is included with the Expresso
full download. If you only have a Servlet API 2.2 compliant container
such as Tomcat 3, you need to separately download and install the
Cactus jar for servlet api 2.2. Such downloads can be found at:
http://jakarta.apache.org/commons/cactus/downloads.html
Configuring Cactus
There is one step that has to be
done for Cactus to run properly on your system.
- Locate the file cactus.properties
located in the WEB-INF/classes directory of your installation. Change
the URL to the Cactus redirector servlet depending on your system.
For most cases the URL will be very similar to what is listed already
in the file. This allows Cactus to know where to send it's test case
requests.
Running the test cases
- Before you can run your test
cases. You must start your servlet engine. Otherwise the test case
will fail when it goes to perform the server side code.
- Run the class you are testing with the following Virtual Machine Parameters:
-Djunit.argv.configDir=<Full path to your expresso config directory>
-Djunit.argv.webAppDir=<Full path to the expresso webapp directory>
-Djunit.argv.testContext=<whateverContextNameYouWant>
There is an example script for starting Junit in the classes directory called runJunit.sh
But for this script to work you will need most of the lib jars in your class path. There
is also a script for that in expresso-web/bin directory called setJunitEnv.sh.
You can run any JUnit test runner
for a controller test case including SwingTestRunner for graphical test
suites.
It should be noted that to follow Extreme Programming's (XP) unit testing
strategies, you will want to chain your test cases together. Check the
example of com.jcorporate.expresso.core.ExpressoTestSuite for a clear example
of this usage.
For technical information about how to use unit testing, please refer to
the Expresso Developers Guide (EDG) or the Javadocs about the ConfigManager.
|