Almost every user-facing product at Rosetta Stone is written in Flash using ActionScript 3 and the Flex framework. In order to help improve our confidence in the quality of our many sizable applications, we, like so many others, turn to unit testing!
In our experience, the difficulty of programmatically testing graphical applications of nontrivial size is due to two features of Flex:
- Graphical applications are often characterized by asynchronous, user-initiated program execution flows. Reasoning about asynchronous behavior, like any concurrent behavior, is hard.
- Graphical user interface frameworks like Flex encourage the storage of program state in user interface components. This state can be difficult to programmatically verify. Additionally, frequently changing layout can make tests brittle and hard to maintain.
In this series of blog posts, I hope to explain some of the approaches we’ve taken to dealing with these challenges. To kick things off, let’s talk about how to verify program state in a simple MXML application:
Now consider a unit test that’s trying to assert some truths about this program’s behavior:
Whoops! If we try to run this test, we’ll get a runtime error telling us that the
userFirstNameTextInput attribute of
UserView has not yet been initialized. This is due to Flex’s phased instantiation process. We can’t depend on
UserView‘s child components being instantiated until
UserView has been parented and then initialized.
We could, in theory, try to fix the above test by parenting
UserView to the static
Application.application, making our test an asynchronous test, and adding an event listener for the “
initialize” event which finally asserts our program state.
Alternatively, we could try to skip the asynchronous instantiation process by parenting
UserView and then calling
initialize() on the
UserView directly to instantiate its child components within the calling thread of execution. However, this approach breaks down quickly when the user interface is more dynamic. Consider, for example, an animation that is played before the child components are ready to be used. In order to guarantee that our view is ready to be programmatically tested, we need to either
- fall back to using an asynchronous test and be sure that we’re listening for the right sequence of events, or
- try to find a way to complete the animations synchronously.
#1 is a brittle solution. It is highly coupled to the view’s animation implementation. Solution #2 drastically changes the behavior of our view, potentially invalidating our tests!
Wow, that’s a lot of complexity to test such a simple program. Let’s not do that.
Instead, let’s try and encapsulate our program state in a Model object! Many moderate-to-large sized Flex applications are written using the Model-View-Controller design idiom. To review, it breaks down a graphical application into three components:
- The Model comprises application state and business logic
- The View is a user-friendly presentation of that application state
- The Controller translates user interactions (e.g. button clicks) into actions on the model
In our running example, we could factor a good portion of the state of our program into a simple ActionScript model type. In order to do this, we should ask ourselves the question: “What is the runtime state of my program?” In our simplistic example, the runtime state and behavior of our program can be captured as:
- The Strings representing the user’s first and last names
- Initial value is “”
- Allowable transitions: any to any
- The enabled state of the submit button and the editable states of the text fields
- Initial value is true
- Allowable transitions: true to false
Let’s make the attributes bindable so that the view can be automatically updated to reflect the state of the model. In fact, we can completely obviate the Controller’s job by using a combination of MXML’s
- Bindings to seamlessly update view state from model state
- Inline ActionScript to translate view events (e.g. button clicks) into actions on the model (e.g. invocation of the
And now our refactored View type:
And finally, our refactored unit test:
As long as we’re willing to rely on the MXML binding and inline ActionScript functionality, this is an effective (and quite scalable!) way to test program state and behavior.
For more information on Model View Controller, see
- Patterns of Enterprise Application Architecture – Martin Fowler, 2002;
- A Description of the Model-View-Controller User Interface Paradigm in the Smalltalk-80 System – Krasner & Pope, 1988.
For more information on unit testing in Flex, see FlexUnit – Adobe’s own Flex unit testing framework.