OK, so it's Friday afternoon and I've got a few moments while I sup on a cold 
bevvy. David & I kind of got the fervour about CALL FORM/WORKER at the same 
time and this is one of the things about them that really captured our 
imagination.

We are using CALL FORM and CALL WORKER to automate unit testing of our forms. 
This has been something we've been wanting to do for a very long time but has 
been impossible or at the very least impractical prior to the introduction of 
these two commands.

There is very little cost to setting it up if you implement your form and 
object methods as separate project methods, and if you use meaningful names for 
the objects on the form.

The beauty of this system is that the forms and their associated methods are 
driven completely externally, so they need to have absolutely no extra logic 
built in to handle the messaging. Furthermore, the control methods for driving 
it are completely generic. The only thing that needs any knowledge of the form 
interactions is the method that's describing the unit tests.

We have a small number of message types that can be sent to the form. These are 
things like:
 - set a value (could apply to any object on the form, e.g. field, button, 
checkbox, listbox, etc.)
 - delay for a number of ticks
 - trigger a form or object event

These messages are created as C_OBJECTs, e.g. we have a method 
UT_UIMessageSetTextValue. This takes an object name and a text value as 
parameters and returns a C_OBJECT representing that message. We have methods to 
set different data types, trigger a form or object event, delay, etc. We can 
then create a queue of these messages and send them to a worker process for 
delivery to the form (window). We have a generic method to do all of this, 
which takes one or more of the C_OBJECT messages, creates another C_OBJECT 
containing the current process number and an OBJECT ARRAY of the supplied 
messages, and then passes that object (using CALL WORKER) to a worker process.

So a simple example of driving a wrapper for the Request dialog might look like 
this:


$vT_ExpectedResult:="Some Value"
UT_UIMessageQueue(\
        UT_UIMessageSetTextValue ("Response_Fld";$vT_ExpectedResult);\
        UT_UIMessageDelay (30);\
        UT_UIMessageSetLongintValue ("OK_Btn";1);\
        UT_UIMessageTriggerEvent ("DLG_RequestFM";On Clicked))
  
$vT_ActualResult:=WRAP_Request ("Enter a value")

ASSERT($vT_ActualResult=$vT_ExpectedResult;"Unexpected response")


The worker process determines the correct window reference (the frontmost 
window for the process contained in the C_OBJECT it receives), waits for that 
window to load, and iterates through the queue of C_OBJECTS. If it's a delay 
message, the delay is actually performed in the worker. If it's an event, the 
form or object's project method is called using CALL FORM, passing the event to 
be triggered. Otherwise the message is sent to a "broker" method using CALL 
FORM which sets the requested object value.

It sounds complicated, trying to write it all out, but the framework is 
actually beautifully simple. It just consists of a handful generic methods, 
none of which extends to more than about 40 lines of code:

- UT_UIMessageSet<XXX>Value
- UT_UIMessageDelay
- UT_UIMessageTriggerEvent
- UT_UIMessageQueue
- UT_UIWorker
- UT_UIBroker

And strictly speaking the delay capability is probably not necessary, but it's 
kinda cool watching the form objects update as if by magic in front of your 
eyes.

Oh and +1 for the proposed command name changes - definitely clarifies the 
functions of these two commands.

Regards
Justin Carr
Genie Solutions
**********************************************************************
4D Internet Users Group (4D iNUG)
FAQ:  http://lists.4d.com/faqnug.html
Archive:  http://lists.4d.com/archives.html
Options: http://lists.4d.com/mailman/options/4d_tech
Unsub:  mailto:[email protected]
**********************************************************************

Reply via email to