Test Run

Testing Silverlight Apps Using Messages

James McCaffrey

Download the Code Sample

I am a big fan of Silverlight and in this month’s column I describe a technique you can use to test Silverlight applications.

Silverlight is a complete Web application framework that was initially released in 2007. The current version, Silverlight 3, was released in July 2009. Visual Studio 2010 provides enhanced support for Silverlight, in particular a fully integrated visual designer that makes designing Silverlight user interfaces a snap.

The best way for you to see where I’m headed in this article is to take a look at the apps themselves. Figure 1 shows a simple but representative Silverlight application named MicroCalc. You can see that MicroCalc is hosted inside Internet Explorer, though Silverlight applications can also be hosted by other browsers including Firefox, Opera and Safari.

Figure 1 MicroCalc Silverlight App
Figure 1 MicroCalc Silverlight App

Figure 2 shows a lightweight test harness, which is also a Silverlight application.


Figure 2 Test Harness for MicroCalc

In this example, the first test case has been selected. When the button control labeled Run Selected Test was clicked, the Silverlight test harness sent a message containing the selected test case input data to the Silverlight MicroCalc application under test. This test case data consists of instructions to simulate a user typing 2.5 and 3.0 into the input areas of the application, selecting the Multiply operation, and then clicking the Compute button.

The application accepted the test case data and programmatically exercised itself using test code that is instrumented into the application. After a short delay, the test harness sends a second message to the application under test, requesting that the application send a message containing information about the application’s state—namely, the value in the result field. The test harness received the resulting message from the application and determined that the actual value in the application, 7.5000, matched the expected value in the test case data, and displayed a Pass test case result in the harness comments area.

This article assumes you have basic familiarity with the C# language, but does not assume you have any experience with Silverlight. In the sections of this column that follow, I first describe the Silverlight application under test. I walk you through the details of creating the lightweight Silverlight-based test harness shown in Figure 1, then I explain how to instrument the application. I wrap up by describing alternative testing approaches.

The Application Under Test

Let’s take a look at the code for the Silverlight MicroCalc application that is the target of my test automation example. I created MicroCalc using Visual Studio 2010 beta 2. Silverlight 3 is fully integrated into Visual Studio 2010, but the code I present here also works with Visual Studio 2008 with the Silverlight 3 SDK installed separately.

After launching Visual Studio, I clicked on File | New | Project. Note that a Silverlight application is a .NET component that may be hosted in a Web application, rather than a Web application itself. In the New Project dialog, I selected the C# language templates option. Silverlight applications can also be created using Visual Basic, and you can even create Silverlight libraries using the new F# language.

I selected the default Microsoft .NET Framework 4 library option and the Silverlight Application template. Silverlight contains a subset of the .NET Framework, so not all parts of the .NET Framework 4 are available to Silverlight applications. After filling in the Name (SilverCalc) and Location (C:\SilverlightTesting) fields, I clicked the OK button. (Note that SilverCalc is the Visual Studio project name, and MicroCalc is the application name.)

Visual Studio then prompted me with a New Silverlight Application dialog box that can be confusing to Silverlight beginners. Let’s take a closer look at the options in Figure 3.


Figure 3 New Silverlight Application Dialog Box Options

The first entry, “Host the Silverlight application in a new Web site,” is checked by default. This instructs Visual Studio to create two different Web pages to host your Silverlight application.

The next entry is the name of the Visual Studio project that contains the two host pages. The project will be added to your Visual Studio solution.

The third entry in the dialog box is a dropdown control with three options: ASP.NET Web Application Project, ASP.NET Web Site, and ASP.NET MVC Web Project. A full discussion of these options is outside the scope of this article, but the bottom line is that the best general purpose option is ASP.NET Web Application Project.

The fourth entry in the dialog box is a dropdown to select the Silverlight version, in this case 3.0. After clicking OK, Visual Studio creates a blank Silverlight application.

I double-clicked on the MainPage.xaml file to load the XAML-based UI definitions into the Visual Studio editor. I modified the default attributes for the top-level Grid control by adding Width and Height attributes and changing the Background color attribute:

<Grid x:Name="LayoutRoot"
  Background="PaleGreen" 
  Width="300" Height="300">

By default, a Silverlight application occupies the entire client area in its hosting page. Here I set the width and height to 300 pixels to make my Silverlight application resemble the default size of a WinForm application. I adjusted the color to make the area occupied by my Silverlight application clearly visible.

Next I used Visual Studio to add the labels, three TextBox controls, two RadioButton controls and a Button control onto my application as shown in Figure 1. Visual Studio 2010 has a fully integrated design view so that when I drag a control, such as a TextBox, onto the design surface, the underlying XAML code is automatically generated:

<TextBox Width="99" Height="23" Name="textBox1" ... />

After placing the labels and controls onto my MicroCalc application, I double-clicked on the Button control to add its event handler to the MainPage.xaml.cs file. In the code editor I typed the following C# code to give MicroCalc its functionality:

private void button1_Click(
  object sender, RoutedEventArgs e) {

  double x = double.Parse(textBox1.Text);
  double y = double.Parse(textBox2.Text);
  double result = 0;
  if (radioButton1.IsChecked == true)
    result = x * y;
  else if (radioButton2.IsChecked == true)
    result = x / y;
  textBox3.Text = result.ToString("0.0000");
}

I begin by grabbing the values entered as text into the textBox1 and textBox2 controls and converting them to type double. Notice that, to keep my example short, I have omitted the normal error-checking you’d perform in a real application.

Next I determine which RadioButton control has been selected by the user. I must use the fully qualified Boolean expression:

if radioButton1.IsChecked == true

You might have expected that I’d use the shortcut form:

if radioButton1.IsChecked

I use the fully qualified form because the IsChecked property is the nullable type bool? rather than plain bool.

After computing the indicated result, I place the result formatted to four decimal places into the textBox3 control.

MicroCalc is now ready to go and I can hit the F5 key to instruct Visual Studio to run the application. By default, Visual Studio will launch Internet Explorer and load the associated .aspx host page that was automatically generated. Visual Studio runs a Silverlight test host page through the built-in Web development server rather than through IIS. In addition to an .aspx test host page, Visual Studio also generates an HTML test page that you can manually load by typing its address into Internet Explorer.

The Test Harness

Now that you’ve seen the Silverlight application under test, let me describe the test harness.

I decided to use Local Messaging to send messages between the harness and the application. I began by launching a new instance of Visual Studio 2010. Using the same process as described in the previous section, I created a new Silverlight application named TestHarness. As with the MicroCalc application, I edited the top-level Grid control to change its default size to 300x300 pixels, and its background color to Bisque in order to make the Silverlight control stand out clearly. Next I added a Label control, two ListBox controls and a Button control to the harness design surface.

After changing the Content property of the Button control to Run Selected Test, I double-clicked the button to generate its event handler. Before adding the logic code to the handler, I declare a class-scope LocalMessageSender object and test case data in the MainPage.xaml.cs file of the harness so that the harness can send messages to the application under test:

public partial class MainPage : UserControl {
  LocalMessageSender lms = null;
  private string[] testCases = new string[] {
    "001:2.5:3.0:Multiply:7.5000",
    "002:8.6:2.0:Divide:4.3000"
  };
...

The LocalMessageSender class is contained in the System.Windows.Messaging namespace so I added a reference to it with a using statement at the top of the .cs file so that I don’t have to fully qualify the class name. I employ a simple approach for my test case data and use a colon-delimited string with fields for test case ID, first input value, second input value, operation and expected result. Next I add class scope string variables for each test case field:

private string caseID;
private string input1;
private string input2;
private string operation;
private string expected;
...

These variables aren’t technically necessary, but make the test code easier to read and modify.

Now I instantiate a LocalMessageReceiver object into the MainPage constructor so that my test harness can accept messages from the application under test:

public MainPage() {
  InitializeComponent();

  try {
    LocalMessageReceiver lmr =
      new LocalMessageReceiver("HarnessReceiver",
        ReceiverNameScope.Global,
        LocalMessageReceiver.AnyDomain);
...

The LocalMessageReceiver object constructor accepts three arguments. The first argument is a name to identify the receiver—this will be used by a LocalMessageSender object to specify which receiver to target. The second argument is an Enumeration type that specifies whether the receiver name is scoped to the global domain or to a more restricted domain. The third argument specifies where the receiver will accept messages from, in this case any domain.

Next I wire up an event handler for the receiver, and then fire up the receiver object:

lmr.MessageReceived += HarnessMessageReceivedHandler;
lmr.Listen();
...

Here I indicate that when the test harness receives a message, control should be transferred to a program-defined method named HarnessMessageReceivedHandler. The Listen method, as you might expect, continuously monitors for incoming messages sent from a LocalMessageSender in the application under test.

Now I instantiate the sender object I declared earlier:

lms = new LocalMessageSender(
  "AppReceiver", LocalMessageSender.Global);
lms.SendCompleted += HarnessSendCompletedHandler;
...

Notice that the first argument to the sender object is the name of a target receiver object, not an identifying name of the sender. Here my test harness sender will be sending messages only to a receiver named AppReceiver located in the application under test. In other words, receiver objects have names and will accept messages from any sender objects, but sender objects do not have names and will send messages only to a specific receiver.

After instantiating the sender object, I wire up an event handler for the SendCompleted event. Now I can load my test cases and handle any exceptions:

...
    foreach (string testCase in testCases) {
      listBox1.Items.Add(testCase);
    }
  } // try
  catch (Exception ex) {
    listBox2.Items.Add(ex.Message);
  }
} // MainPage()

I simply iterate through the test case 
array, adding each test case string to the listBox1 control. If any exception is caught, I just display its text in the listBox2 control used for comments.

At this point I have a sender object in the harness that can send test case input to the application, and a receiver object in the harness that can accept state information from the application. Now I go back to the button1_Click handler method I added earlier. In the handler, I begin by parsing the selected test case:

string testCaseData = (string)listBox1.SelectedItem;
string[] tokens = testCaseData.Split(':');
caseID = tokens[0];
input1 = tokens[1];
input2 = tokens[2];
operation = tokens[3];
expected = tokens[4];
...

Now I’m ready to send test case input to the Silverlight application under test:

string testCaseInput = 
  input1 + ":" + input2 + ":" + operation;
listBox2.Items.Add("========================");
listBox2.Items.Add("Test case " + caseID);
listBox2.Items.Add(
  "Sending ‘" + testCaseInput + "’ to application");
lms.SendAsync("data" + ":" + testCaseInput);
...

I stitch back together just test case input. I do not send the test case ID or the expected value to the application because only the harness deals with those values. After displaying some comments to the listBox2 control, I use the SendAsync method of the LocalMessageSender object to send the test case data. I prepend the string “data” so that the application has a way to identify what type of message is being received.

My button event handler finishes up by pausing for one second in order to give the application time to execute, and then I send a message asking the application for its state information:

System.Threading.Thread.Sleep(1000); 
  lms.SendAsync(“response”);
} // button1_Click

Recall that I wired up an event handler for send completion, but in this design I do not need to perform any explicit post-send processing.

The final part of the harness code deals with the message sent from the Silverlight application to the harness:

private void HarnessMessageReceivedHandler(object sender,
  MessageReceivedEventArgs e) {

  string actual = e.Message;
  listBox2.Items.Add(
    "Received " + actual + " from application");
  if (actual == expected)
    listBox2.Items.Add("Pass");
  else
    listBox2.Items.Add("**FAIL**");

  listBox2.Items.Add("========================");
}

Here I fetch the message from the application, which is the value in the textBox3 result control, and store that value into a variable named actual. After displaying a comment, I compare the actual value that was sent by the application with the expected value parsed from the test case data to determine and display a test case pass/fail result.

Instrumenting the Silverlight Application

Now let’s examine the instrumented code inside the Silverlight application under test. I begin by declaring a class-scope LocalMessageSender object.

This sender will send messages to the test harness:

public partial class MainPage : UserControl {
  LocalMessageSender lms = null;
  public MainPage() {
    InitializeComponent();
...

Next I instantiate a receiver in the MainPage constructor to accept messages from the test harness, wire up an event handler, and start listening for messages from the harness:

try {
  LocalMessageReceiver lmr =
    new LocalMessageReceiver("AppReceiver",
      ReceiverNameScope.Global,
      LocalMessageReceiver.AnyDomain);
  lmr.MessageReceived += AppMessageReceivedHandler;
  lmr.Listen();
...

As before, note that you assign a name to the receiver object, and that this name corresponds to the first argument to the sender object in the harness. Then I deal with any exceptions:

...
  }
  catch (Exception ex) {
    textBox3.Text = ex.Message;
  }
} // MainPage()

I display exception messages in the textBox3 control, which is the MicroCalc application result field. This approach is completely ad hoc, but sending the exception message back to the test harness may not be feasible if the messaging code throws the exception. Now I handle messages sent by the test harness:

private void AppMessageReceivedHandler(object sender,
  MessageReceivedEventArgs e) {
  string message = e.Message;
  if (message.StartsWith("data")) {
    string[] tokens = message.Split(‘:’);
    string input1 = tokens[1];
    string input2 = tokens[2];
    string operation = tokens[3];
...

The test harness sends two types of messages. Test case input data starts with “data” while a request for application state is just “response.” I use the StartsWith method to determine if the message received by the application is test case input. If so, I use the Split method to parse the input into variables with descriptive names.

Now the instrumentation uses the test case input to simulate user actions:

textBox1.Text = input1;
textBox2.Text = input2;
if (operation == "Multiply")
  radioButton1.IsChecked = true;
else if (operation == "Divide")
  radioButton2.IsChecked = true;
...

In general, modifying properties of controls, such as the Text and IsChecked properties in this example, to simulate user input is straightforward. However, simulating events such as button clicks requires a different approach:

button1.Dispatcher.BeginInvoke(
  delegate { button1_Click(null,null); });

The Dispatcher class is part of the Windows.Threading namespace so I added a using statement referencing that class to the application. The BeginInvoke method allows you to asynchronously call a method on the Silverlight user interface thread. BeginInvoke accepts a delegate, which is a wrapper around a method. Here I use the anonymous delegate feature to simplify my call. BeginInvoke returns a DispatcherOperation object, but in this case I can safely ignore that value.

The Dispatcher class also has a CheckAccess method you can use to determine whether BeginIvoke is required (when CheckAccess returns false) or whether you can just modify a property (CheckAccess returns true).

I finish my instrumentation by dealing with the message from the test harness that requests application state:

...
  }
  else if (message == "response") {
    string actual = textBox3.Text;
    lms.SendAsync(actual);
  }
} // AppMessageReceivedHandler()

If the message received is just the string “response,” I grab the value in the textBox3 control and send it back to the test harness.

The test system I describe in this article is just one of many approaches you can use and is best suited for 4-4-4 ultra-light test automation. By this I mean a harness that has an expected life of 4 weeks or less, consists of 4 pages or less of code, and requires 4 hours or less to create.

The main advantage of testing using Messages compared to other approaches is that the technique is very simple. The main disadvantage is that the application under test must be heavily instrumented, which may not always be feasible.

Two important alternatives to the technique I have presented here are using the HTML Bridge with JavaScript and using the Microsoft UI Automation library. As usual, I’ll remind you that no one particular testing approach is best for all situations, but the Messages-based approach I’ve presented here can be an efficient and effective technique in many software development scenarios.

**Dr.**James McCaffrey works for Volt Information Sciences Inc., where he manages technical training for software engineers working at the Microsoft Redmond, Wash., campus. He has worked on several Microsoft products including Internet Explorer, and MSN Search. Dr. McCaffrey is the author of “.NET Test Automation Recipes” (Apress, 2006). He can be reached at jammc@microsoft.com.

Thanks to the following technical experts for reviewing this article: Karl Erickson and Nitya Ravi