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]
**********************************************************************