Most user actions in an application can be executed in different ways. As I mentioned earlier, creating a new document can be done via the following methods:
- Clicking the File menu, clicking the New submenu, then clicking the New Document menu item
- Clicking the New Document toolbar button
- Typing Ctl+N
The test code infrastructure required for each of these execution variants are typically identical. The entire test case for each variant, in fact, is often identical except for the details of how the actions are implemented. Anyone who has written such cookie cutter test cases has surely wished for a better way.
We developed Execution Behaviors to eliminate this duplicated effort by pushing the variability in execution method from individual test cases into our LFM. An Execution Behavior is nothing more than one possible implementation of a given LFM method. We have two types: Composite Execution Behaviors that know they are implemented via one or more specific implementations, and child Execution Behaviors that represent a particular way of implementing an action that is semantically equivalent to every other child Execution Behavior for its “parent” Composite Execution Behavior.
The power this technique presents should be clear. Because the selection is taken out of the hands of the test case, we can write a single test case and run it a number of times (getting a different execution method each time) rather than write separate test cases for each execution method. Perhaps more importantly, any test case that calls a Composite Execution Behavior may end up using any of that Composite Execution Behavior’s child Execution Behaviors. Thus the test case exercises parts of the application it likely would not otherwise hit and is executed in a variety of contexts, all without doing anything special!
Execution Behaviors can be composed into other Execution Behaviors, so invoking a Composite Execution Behavior may cause a variety of other Execution Behaviors to be invoked as execution travels down through its tree of children. Child Execution Behaviors can also be accessed directly allowing test cases to invoke specific execution methods directly.
The Execution Behavior selection process can be customized in a variety of ways. First, the amount of time during which the Execution Behavior Manager will “remember” the choices it has made and use that knowledge when making further choices can be scaled from the execution of a single test case on the low end to include every test case run in the entire product cycle on the high end. Second, the Execution Behavior Manager can be configured to use any child Execution Behavior selection strategy you like. Third, the default selection process can be biased along several axes:
- Globally in relation to any property with which child Execution Behaviors are labeled. For example, if the menuing system has just been completely rewritten, we could instruct the Execution Behavior Manager to choose child Execution Behaviors that use the menus most of the time but still select ones that use shortcut key sequences occasionally.
- Locally to the test case in relation to any of these same properties. A test case may want to ensure that the mouse is used for all of its actions, for example.
- Locally to the child Execution Behavior. A child Execution Behavior can disable itself if the application is in a state in which it cannot execute.