|
Misc Articles |
| Home.About Strandz.Misc Articles |
|
|
|
|
Subcutaneous Testing for GUIs 'Subcutaneous Testing' is a term coined by Martin Fowler that refers to unit tests that do not need GUI controls to run. The trick with this style of testing is to have views that are so dumb that they do not need to be tested, and somehow aim your unit tests in behind the thin GUI layer made up of these views.
"Testing a presentation through a GUI window is often awkward, and in some cases impossible. You have to build some kind of UI driver that will drive the GUI. Some people use GUI tools that simulate raw mouse and keyboard events - but these tools usually create brittle tests that give false positives whenever minor changes occur to the presentation. More detailed tools address the UI controls more directly, these are less brittle but still troublesome."- Martin Fowler (http://www.martinfowler.com/eaaDev/OrganizingPresentations.html)
A Strandz application can be written to never refer to data controls directly, but instead to refer to them indirectly through attributes. In turn these attributes can refer to dummy controls just as easily as real ones. To make the swap you just need to change the type of attribute that is used. Thus effectively 'under the skin' testing is achieved by swapping the GUI toolkit you have used (currently there is only one choice here - Swing) for the non-GUI control (text holder) that lies behind a Not using GUI data controls covers the binding part of the story, but we also need to consider the control aspect. How do we test inserting a new row on the screen if the user has to press a button to do it? How about actions that are not a part a part of the framework, and might also be represented by a button? In practise this is not a big deal as it is easier to invoke the action behind the event than it is to programatically click the button, so that is the way tests would usually be written. For example:
/**
* A new roster slot such that the worker's rule
* is to -be rostered every week for the Friday
* dinner shift- should pass validation
*/
public void testIntervalRSValidationWeekly()
{
rosterSlotNode.GOTO();
workerStrand.INSERT();
intervalAttribute.setItemValue("weekly");
dayAttribute.setItemValue("Friday");
whichShiftAttribute.setItemValue("dinner");
boolean ok = workerStrand.POST();
assertTrue(ok);
}
Finally the way to really ensure that none of your tests cause any painting to the screen is to run them on a headless server (ie. Linux without a Window Manager installed). Design-Time XMLEncoding of JavaBeans
Here we are going to go through the programming decisions made as we create a radio group style control out of Swing components. A Worker has a field called
We are going to use a
Thus we choose to create a higher-level When writing a JavaBean control it is useful to determine which state information is only required at runtime, and thus does not need to be persisted (XMLEncoded) at design time (DT) when the user is using a tool to do things such as bind a control to a domain object. The answer to this question of course depends on your DT tool (*). For our purposes, at DT our control will not need to:
Note that the way XMLEncoder works is by constructing instances and then getting and setting their properties to determine which are the non-default ones, and putting these values into the XML. Thus part your job when creating a JavaBean is to ensure that only DT properties end up in the XML file.
Now armed with our helper class and XMLEncoder tool we are ready to create our control. This control will be a (*) For example some DT tools may allow the user to not only view the control but interact with it as they would at runtime. In our case this might mean being able to see the control and select a button. Then at runtime the last button selected would be the default selection that the user would see. (**) Reasoning behind the init() method... Screen Validation Using Domain ObjectsHave you ever been in the situation where you have a perfectly decent validation routine that will work on a particular quite complex domain object (DO), yet you really want to validate before the persistent DO has been created? This will be the case when the user is inserting new information. One choice would be to manually create a 'to be discarded' DO and pass it to the validation routine. Another would be to have the validation routine work for the the field values of the DO. The latter case would involve a method with many parameters, so you might decide to pass in some kind of DTO. And there are other combinations of things you might like to try as you attempt to make it all as tidy as possible. The one thing you would not be able to avoid is meticulously obtaining the data items from every control on the screen/s.
Such drudgery is unnecessary if your binding framework gives you runtime access to the relationship between the screen items and the DO. In Strandz each DO is represented by a Cell, and a Cell, which also has access to the screen items, has a method called
class WorkerRecordValidationT implements RecordValidationTrigger
{
public void validateRecord(RecordValidationEvent validationEvent)
throws ValidationException
{
Worker screenWorker = (Worker)dt.workerCell.getItemValue();
screenWorker.validate();
}
}
public class Worker
{
public void validate() throws ValidationException
{
validate( away1Start, away1End);
validate( away2Start, away2End);
}
private void validate(Date firstDate, Date secondDate) throws ValidationException
{
if(!(firstDate == null && secondDate == null))
{
//at least one of them is filled in
if(!(firstDate != null && secondDate != null))
{
//it is not the case that both are filled in
throw new ValidationException("There must be both an
As you have probably worked out from above, a Worker DO has two holiday periods, and the validation is to make sure that a start and end are both filled in for a holiday period. In Strandz validation triggers indicate a failure by throwing a ValidationException which is handled by another trigger that you supply:
class HandlerT implements ValidationHandlerTrigger
{
public void handleError(ApplicationError e)
{
List msg = e.getMsg();
new MessageDlg(NameUtils.colourInVariables(msg));
Err.alarm(msg.get(0).toString());
}
}
The use case we have given here is validation of data in the controls on the screen/s, and specifically record validation. However the Here we are going to outline an essential stack which is currently being used in production for a number of applications. Not every rich client application requires reports, charts and graphs so open-source APIs such as JasperReports, JFreeChart and Vector Visuals will not be considered here. The main criterion that will be applied as we build up the stack is that each API we choose be easy to work with from a maintenance perspective. We will start our stack from the database end and work towards the user interface. We will assume that our rich client application is being deployed across the internet by Java Web Start or an equivalent. Development ought to be able to proceed without a database at all. If you are working with POJOs and have your own domain test data then bugs/unit tests/enhancements ought to be able to be worked on without dealing with persistence. If the stack uses an OODB then dealing with persistence is easier than with ORMs, where you have to worry about binding the POJOs to the database tables. Although an OODB would seem to this author to be the better solution, our ideal stack includes an ORM layer simply because of the lack of market penetration of OODBs. A domain object access API ought to be able to wrap around the chosen ORM (Hibernate, JDO, JPA, no ORM (OODB) or in-memory), and using it ought to offer as close to transparent persistence as possible.
The use of ORMs is supposed to preclude the use of DAOs and in our stack we indeed do not use them. With an ideal domain object access API you would be able to use dependency injection to specify the queries that are to be used. Fetching data would thus be done with reference to a query. From then on there would be no contact with this ORM API wrapper until the transaction is to be committed or rolled back by a command issued without reference to particular POJOs. The Strandz applications currently in production use subclasses of Although swapping between actual ORM implementations (Hibernate etc) is easy once the queries have been translated, we still need to make a decision on which one is best. Implementations other than JDO's do not give you the equivalent of datastore identity. Certainly Hibernate and JPA (EJB 3.0) only give you the equivalent of JDO's application identity. This latter type of object identity requires the developer to specify detailed binding information as well as keys for each domain object. With datastore identity you think in terms of your domain model, and leave the ORM to deal with the database. The one drawback you need to bear in mind when choosing to steer clear of application identity is that starting off with existing production data or changing JDO vendors will essentially mean that you have to export your entire database and import it to the new vendor's format. The next question to answer is how to transport the domain objects across the internet. Given our least work criterion the choice is simple: use an exposed domain model. Apache Cayenne is the only open source vendor I know of that currently offers what in JDO terminology would be called a remote persistence manager. I have not yet used this recent discovery; only having experience with BEA's Kodo. My experience with Kodo is that it feels like it takes a long time for the domain objects to be returned. In theory this performance problem ought to be able to be overcome.
The approach currently taken is for the user to be left waiting at the start of using the application whilst the remote data loads, after which everything runs quickly. In JDO fetch groups are supposed to allow you to control this kind of behaviour; specifying that all your domain objects belong to the default fetch group ought to mean that no hollow objects are returned, and thus no further fetches are required once the initial query has completed. However this did not work as expected with a remote persistence manager, and copying (effectively to nowhere) the object graph/s on query return has been used to ensure all instances are touched and thus no hollow instances remain, giving us a quick to operate sentient rich client application. Spring Services can be used for the data crunching work that is best done on the server. These services can return DTOs to the client over HTTP so there are no firewall issues. As the application matures you may find that real POJOs also need to be returned. Because domain objects returned from a Spring service will not be persistence aware your client may need to substitute them with their persistence aware counterparts that will quite possibly be already loaded into your JVM via the remote persistence manager. As you can see there are further things to explore, and future open source products will be coming to the rescue. For instance your queries ought to be able to work both synchronously and asynchronously. My ideal would be to be able to specify a buffer size in terms of the number of domain objects to be returned synchronously from a query, and then perhaps some kind of simple strategy as to how more rows would be fetched in the background (asynchronously); allowing you to vary the total number of objects fetched and the the depth for each object. Of course whenever a hollow domain object is read synchronous fetching will ensue. Interesting discoveries that might help with this ideal stack in the future: http://forum.springframework.org/showpost.php?p=92847&postcount=23 Obtaining a Stack Trace from JVM Source Code for Debugging PurposesIf you ever experience a situation in which the easiest way forward would be to put some debugging in the JVM code itself then you can use the -Xbootclasspath option to override the original Sun-supplied source code with your own altered source code. There may be cases where the error message at the end of the stack trace tells you that a NPE has occurred, but you want to find out something about the object that is causing the problem. Or you may just have a poor understanding of a particular Sun API, and want a brute force way of getting to the heart of the problem - the example we will go through in this topic is in this category. I have found this particular debugging method useful whenever I have been faced with a hard to understand stack trace, and for example it has allowed me to find out just which Component the RepaintManager was having trouble with: java.lang.NullPointerException at javax.swing.SwingUtilities.computeIntersection(SwingUtilities.java:417) at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:430) at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(SystemEventQueueUtilities.java:114) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209) at java.awt.EventQueue.dispatchEvent(EventQueue.java:461) When you take a look at the source for SwingUtilities you can see that a variable for the problem component is available. With the technique outlined in this topic you will be able to code your own far more helpful stack trace, that perhaps prints the name of the problem component. To do the -Xbootclasspath trick you first of all copy the source code you want and alter it. You can then compile it with an ant task like this:
<property name="src.root" value="./src/javax"/>
<property name="out.root" value="./classes"/>
<target name="clean" description="remove all classes">
<mkdir dir="${out.root}"/>
<delete>
<fileset dir="${out.root}" includes="*.class"/>
</delete>
</target>
<target name="compile"
description="compile src">
<javac srcdir="${src.root}"
destdir="${out.root}"
debug="on"
memoryMaximumSize="240m"
fork="true">
</javac>
</target>
After compilation you can then jar it up using something like this from the command line: C:\java_src\classes>jar -cf ..\overrides.jar ./javax/swing Next you have to tell the JVM that it is to use overrides.jar versions of classes instead of the normal ones. This is where the -Xbootclasspath option comes in: java -Xbootclasspath/p:C:\java_src\overrides.jar -jar C:\sdz-zone\lib-sdz\wombat-memory.jar The '/p' option means that classes in overrides.jar are looked at before any others. wombat-memory.jar is one of those self contained jar files that specifies its own main class and classpath. For our example, the question we will try to answer is "Where is a JTree's DefaultMutableObject having it's userObject property set to null?". The first thing you might think of is to create a DefaultMutableObject subclass and override the method setUserObject(), catching the point where userObject is being set to null. Unfortunately my initial attempts at this revealed that no such point was ever being reached - and yet the 'null' was still appearing on the JTree control! So somehow code internal to DefaultMutableObject was writing null to its userObject. The JTree renderer uses DefaultMutableObject.toString() to output to the screen. Thus it is simple to throw a stack trace at the point where the toString() tries to display a DefaultMutableObject with a null userObject. Thus we can find the identity of our problem DefaultMutableObject, but not where it was created. In the renderer we display the identity of the offending DefaultMutableObject by altering its toString() method to the following:
public String toString() {
if (userObject == null) {
return "ID: " + id;
} else {
return userObject.toString();
}
}
As you can see an id variable was created. The 'innermost' constructor (ie. the constructor that is always called) was altered to have these two lines added:
timesConstructed++;
id = timesConstructed;
where timesConstructed is a static, and id a member variable. Thus one run of the code gives us the id of the offending DefaultMutableObject. In our case it is 95. Again we alter the constructor:
timesConstructed++;
id = timesConstructed;
System.out.println( "constructing DefaultMutableTreeNode ID:" + id);
if(id == 95)
{
throw new Error();
}
Thus we get a stack trace at the point of error in the code, and it is a line that looks like this:
DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(
obj);
Thus the 'eureka' moment here is discovering that the userObject can be set to null from the constructor, and that the constructor does not in turn call the setUserObject() method. That explains why overriding the method did not work. I have illustrated two techniques here which are pretty useful for java debugging generally. One that can be used often - that is reference counting within an instance, and another that will help you when all else has failed - obtaining a stack trace from JVM source code. An alternative for debugging into the source is source level debugging provided by your IDE - which can give you the call stack as you step through JVM source. |