Testing Design

From ParaQ Wiki
Revision as of 12:53, 1 December 2005 by Tshead (talk | contribs)
Jump to navigationJump to search

Requirements

  • Support easy testcase creation for user contributed tests.
  • Support playback and recording of testcases.
  • Support hand editing of testcases.
  • Test cases can be scripts (but we support a trivial "metafile" format, so we aren't dependent on any given script engine for testing).
  • Test commands are stored with a high level of abstraction instead of low-level events, to reduce test-case breakage (e.g. "button push" instead of "mouse down at x,y location").
  • Test commands still work if widget moves to new location in object tree (docking window can be top level or child), to reduce test-case breakage - implies a flat naming scheme for UI components.
  • Test commands not completely tied to type of object (spin box & slider bar represent the same thing & swapping them should make test work still).
  • Support verification - check that a line edit has the right text at a certain point in the test, or (more complex) retrieve data from VTK objects.
  • Support an inspector to navigate the object tree as an aid for hand-editing.

Design

The testing framework is centered around "recording" and "playback" of user interaction.

For recording, an instance of pqEventTranslator is created, which intercepts Qt events for the entire application. pqEventTranslator manages a collection of pqWidgetEventTranslator objects, which are specialized for specific Qt widget types. The translators convert low-level Qt events ("mouse move", "button down", "button up") into higher-level ParaQ events that can be usefully serialized and played back ("button activated"). Each high-level event is encapsulated in three strings: the name of the widget, the name of the event, and optional arguments to the event. Multiple recording "back ends" can be attached to pqEventTranslator, to serialize events - pqEventObserverStdout and pqEventObserverXML are existing examples, presumably a pqEventObserverPython object could "store" events in Python code. pqWidgetEventTranslator-derivatives will be provided for all "native" Qt widgets, so recording will "just work" for any UI created with stock Qt components. For non-standard widgets, developers may create their own pqWidgetEventTranslator implementations and add them to pqEventTranslator at runtime.

For playback, an instance of pqEventPlayer is created. pqEventPlayer manages a collection of pqWidgetEventPlayer objects, which are responsible for converting high-level ParaQ events into interaction with the UI. Note that there is not necessarily a one-to-one correspondence between pwWidgetEventTranslastor and pqWidgetEventPlayer objects - a single pqAbstractIntEventPlayer object is capable of handling events generated by both pqSpinBoxEventTranslator and pqAbstractSliderEventTranslator. The latter two objects are separate due to dissimilar behavior between Qt objects, while pqAbstractIntEventPlayer can handle playback for both because they both "map" events to a common, abstract concept. This makes it possible for a test case to continue working when the UI changes from one compatible widget (e.g. SpinBox) to another (Dial or Slider).

Widget Namespaces

The process of translating low-level events into serializable "scripts" and playing them back is straightforward and well understood, see (shameless plug) [K-3D] for another 3D graphics system that provides this capability. The one fundamentally tough problem is one of how to generate a persistent, repeatable "address" for a given user interface component. There must be some way to uniquely identify each user interface component for which user interaction is recorded, so that events can be directed to the correct components during playback. The main problem is with generating globally-unique addresses, while allowing for reasonable changes to the user interface without breaking test cases. As an example, a test recorded on one system may be played-back on another, with a different configuration of docked / floating toolbars. Ideally, the test case should continue to function.

Some alternatives:

  • Flat - Assign a globally-unique name to every widget. Pros: test cases work no matter how the UI is rearranged. Cons: Developers must explicitly name everything, names quickly become unwieldy, QtDesigner limits widget names (e.g. can't contain slashes), and QtDesigner-generated intance variables will share the long, unwieldy names.
  • The MS Way - Same as "Flat", only use 128-bit UUIDs as identifiers. Insert Homer Simpson-like shudder here ...
  • Widget Hierarchy - Use the Qt widget hierarchy to generate unique names. Pros: developers can use straightforward names for widgets in Qt designer, and only have to explicitly name top-level objects like dialogs. Cons: rearranging the widget hierarchy breaks test cases, e.g. floating/docking windows.
  • The K-3D Way - Use a hierarchical naming scheme, but make it orthogonal to the Qt widget hierachy. Pros: same as "Widget Hierarchy", plus floating and docking windows no longer break test cases. Cons: since this is a separate hierarchy, it has to be maintained at runtime, e.g. by explicitly registering widgets with some central manager.

And the winner is ...

... Widget Hierarchy. The QtTesting library generates a UI component address by walking the Qt widget hierarchy, concatenating object names (separated by slashes) into a hierarchical "path" string. It was decided that, although this method is especially brittle in the face of UI modifications, it requires the least developer effort, and integrates well with custom user interfaces and Qt designer.