共用方式為


Building Testable Windows Phone Applications

patterns & practices Developer Center

On this page: Download:
Audience | Sample Applications | The Solutions | The MVVM Pattern | Connecting View Models to Views | Unit Testing Windows Phone Applications | Abstracting Windows Phone 7.1 SDK Classes | Mock Implementations | Running Unit Tests Download code samples

This documentation and accompanying sample applications will show you how to build easily testable applications that target Windows Phone OS 7.1.

Some of the topics that you will learn about include building testable Windows Phone applications that:

Audience

This documentation and accompanying sample applications are best suited for developers with the following experience levels.

Some experience with:

  • C#
  • Silverlight
  • Windows Phone 7.0 or 7.1 SDK
  • The Model-View-ViewModel (MVVM) pattern

Little or no experience with:

  • Building testable Windows Phone applications

Sample Applications

This documentation includes a series of companion sample applications written in Silverlight for Windows Phone OS 7.1. Each sample application is purely for demonstration purposes, and is not representative of the standard required to pass certification and be eligible for listing in the Windows Phone Marketplace.

There are five sample applications that accompany this documentation:

Sample Application

Demonstrates

TestableGPSSample

Capturing the location of the Windows Phone device, and then calculating the distance in meters between the Windows Phone device and a target location.

TestableAppServices

Page navigation.

TestableIsolatedStorage

Persisting user-supplied text to and from isolated storage.

TestableSensors

Acquiring and displaying the raw data from the multiple sensors that are present in a Windows Phone device.

TestableCamera

Displaying a photo taken with a chooser.

For each accompanying sample application the documentation shows you how to:

  1. Wrap the Windows Phone SDK class that provides the desired functionality for the application, in order to enable writing loosely coupled testable code.
  2. Consume the wrapped class.
  3. Write unit tests that test application business logic.

You can download the code for the sample applications at the following link:

Hh830877.48C48CC92B46D4C0C6BD93E42E48C478(en-us,PandP.10).png

The Solutions

Each solution organizes the source code and other resources into a project. The following table outlines the main projects that typically make up the each sample application.

Project

Description

Microsoft.Practices.Phone.Adapters

This project contains interfaces, adapters and facades for Windows Phone API functionality. This project can be downloaded from Nuget.

TestableApplicationName

This project contains the views and view models for the sample application, along with supporting classes and resources.

Microsoft.Practices.Phone.Testing

This project contains mock implementations of the adapters and facades contained in the Microsoft.Practices.Phone.Adapters project.

TestableApplicationName.Tests

This project contains unit tests that test the business logic of the application.

The Lib project folder contains compiled assemblies that the sample applications use.

The MVVM Pattern

The Model-View-ViewModel pattern, or MVVM is a simple pattern that can be used on all XAML platforms. Its intent is to provide a clean separation of concerns between the user interface controls and their logic.

There are three core components in the MVVM pattern: the model, the view, and the view model. Each serves a distinct and separate role. The following illustration shows the relationships between the three components.

Hh830877.1AFE20BAB0052F5AB0FC400BF3B6F3F7(en-us,PandP.10).png

The components are decoupled from each other, thus enabling:

  • Components to be swapped
  • Internal implementation to be changed without affecting the others
  • Components to be worked on independently
  • Isolated unit testing

In addition to understanding the responsibilities of the three components, it's also important to understand how the components interact with each other. At the highest level, the view "knows about" the view model, and the view model "knows about" the model, but the model is unaware of the view model, and the view model is unaware of the view.

The view model isolates the view from the model classes and allows the model to evolve independently of the view.

For more information about MVVM, see Chapter 5, Implementing the MVVM Pattern and Chapter 6, Advanced MVVM Scenarios of the Prism documentation. For information about implementing MVVM in Windows Phone applications see Developing a Windows Phone Application using the MVVM Pattern.

Connecting View Models to Views

There are several approaches that can be used to connect a view model to a view, including direct relations and data template relations. A view model locator is used here because this approach means that an application has a single class that is responsible for connecting view models to views. This still leaves developers free to choose to manually perform the hookup within the view model locator, or by using a dependency injection container. The following illustration shows the relationships between the view, view model locator, and view model.

These sample applications use the view model locator approach to connecting the view to a view model. This approach works well for applications with a limited number of pages (which is often the case with Windows Phone applications), but is not always appropriate in larger applications.

Follow link to expand image

The following code example from the TestableGPSSample App.xaml file shows how the view model locator class is identified and made available to each sample application. The application declares the ViewModelLocator class in the <Application.Resources> section of the App.xaml file.

<Application 
  x:Class="TestableGPSSample.App"
  …
  xmlns:TestableGPSSample="clr-namespace:TestableGPSSample">

  <Application.Resources>
    <ResourceDictionary>
      <TestableGPSSample:ViewModelLocator x:Key="ViewModelLocator"/>
    </ResourceDictionary>
  </Application.Resources>
  …
</Application>

The following code example from the TestableGPSSample MainPage.xaml file shows how a view can then reference the ViewModelLocator class as a static resource in its data context bindings.

<phone:PhoneApplicationPage 
  x:Class="TestableGPSSample.MainPage"
  …
  DataContext="{Binding Source={StaticResource ViewModelLocator},
    Path=MainPageViewModel}"
  …
</phone:PhoneApplicationPage>

The Path attribute identifies the property of the ViewModelLocator instance that returns the view model associated with the current page.

The following code example shows the TestableGPSSample ViewModelLocator class that returns the MainPageViewModel instance.

public class ViewModelLocator 
{
  public MainPageViewModel MainPageViewModel
  {
    get { return new MainPageViewModel(new GeoCoordinateWatcherAdapter
      (GeoPositionAccuracy.High)); }}
  }
}

The ViewModelLocator class connects views to view models.

Unit Testing Windows Phone Applications

Unit tests differ from integration or user acceptance tests in that they test the smallest unit of functionality possible. Typically, a unit test will test the behavior of a specific method. The table below compares unit and integration tests.

Unit Tests

IntegrationTests

Dependent systems are not required

Dependent systems are required and must be online

Fast to run

Slow to run

Typically run frequently (during development and code check-in)

Typically run periodically

Test developer expectations

Test real system behavior

The most important aspect of unit testing compared to integration testing is that unit tests should not be reliant on the functionality of dependent systems to exercise the code being tested. If the method being tested handles a condition such as an IsolatedStorageException being thrown, it would not be ideal to fill up or somehow corrupt isolated storage in order to trigger the code path. Similarly, testing code that calls services or queries databases should not be reliant on dependent systems. If a test requires dependent systems to be correctly configured, running, and populated with a known set of data, the test would be very fragile and would fail if any of these conditions were not met. In such circumstances you would see many failing tests because of issues with dependent systems, rather than issues with the code under test.

A good approach to increase software testability is to isolate dependent systems and have them passed into your business logic using an abstraction such as an interface or abstract class. This approach allows the dependent system to be passed into the business logic at run time. In addition, in the interests of testability, it also allows a mock version of the dependent system to be passed in at test time. For more information see, Forms of Dependency Injection on Martin Fowler's website.

Unit tests can be thought of as the code-based documentation of how the code under test should behave. If the code under test is refactored or reorganized, the functionality should stay the same and the unit tests should continue to pass. If the unit tests don’t pass, it may be due to improperly refactored code. Another possibility is that the desired code behavior has changed, in which case the unit tests should be updated or new tests should be added.

Abstracting Windows Phone 7.1 SDK Classes

There are few public interfaces or abstract classes available in the Windows Phone 7.1 SDK. Therefore, interfaces were generated for some of the classes in the Windows Phone 7.1 SDK that are used by the accompanying sample applications. The interfaces were generated by using a Reflector add-in named Doubler. Doubler is a code generator which can generate a wrapper class and its interface for a chosen type in an assembly.

Adapters and facades, which are wrapper classes that implement the generated interface, were also generated. Adapter classes pass parameters and return values to and from the underlying Windows Phone 7.1 SDK class. The adapter translates calls to its interface into calls to the original interface. Facade classes provide a simplified interface for a class, with the facade translating calls to its interface into calls to the original class. Creating adapters and facades enables you to write loosely coupled testable code.

An adapter is a design pattern that translates an interface for a class into a compatible interface. The adapter translates calls to its interface into calls to the original interface.
A facade is a design pattern that provides a simplified interface for a class. The facade translates calls to its interface into calls to the original class.
Both approaches enable you to write loosely coupled testable code.

The following illustration shows how the GeoCoordinateWatcherAdapter class, adapts the GeoCoordinateWatcher SDK class.

Hh830877.7E8E13056F6D007B92C639C52527F176(en-us,PandP.10).png

The GeoCoordinateWatcherAdapter class implements the IGeoCoordinateWatcher interface. Any classes that want to consume the GeoCoordinateWatcher class to obtain a location should consume the GeoCoordinateWatcherAdapter class instead.

The right-hand side of the illustration shows the MockGeoCoordinateWatcher class, which also implements the IGeoCoordinateWatcher interface and is used in unit tests instead of the GeoCoordinateWatcherAdapter class. This provides the ability to unit test business logic that needs to interact with the GeoCoordinateWatcher class.

The following wrapper classes are provided, and can be found in the Microsoft.Practices.Phone.Adapters project.

Wrapper

Implements

Abstracted Class

AccelerometerAdapter

IAccelerometer

Accelerometer

ApplicationFrameNavigationService

INavigationService

PhoneApplicationFrame

CameraCaptureTaskAdapter

ICameraCaptureTask

CameraCaptureTask

CompassAdapter

ICompass

Compass

GeoCordinateWatcherAdapter

GeoCoordinateWatcher, IGeoCoordinateWatcher

GeoCoordinateWatcher

GyroscopeAdapter

IGyroscope

Gyroscope

IsolatedStorageFacade

IIsolatedStorageFacade

IsolatedStorageFileAdapter,

IsolatedStorageFileStreamAdapter

IsolatedStorageFileAdapter

IIsolatedStorageFile

IsolatedStorageFile

IsolatedStorageFileStreamAdapter

IsolatedStorageFileStream, IIsolatedStorageFileStream

IsolatedStorageFileStream

MessageBoxAdapter

IMessageBox

MessageBox

MotionAdapter

IMotion

Motion

AccelerometerSensorReading

ISensorReading

AccelerometerReading

CompassSensorReading

ISensorReading

CompassReading

GyroscopeSensorReading

ISensorReading

GyroscopeReading

MotionSensorReading

ISensorReading

MotionReading

PhotoResultTaskEventArgs

TaskEventArgs

PhotoResult

Mock Implementations

Unit tests should focus on how the code under test functions in response to values returned by dependent systems. By using mocks, the return values or exceptions to be thrown by mock instances of dependent systems can easily be controlled. For more information see, Exploring the Continuum of Test Doubles in MSDN Magazine.

The Microsoft.Practices.Phone.Testing project contains mock implementations of the Windows Phone 7.1 SDK adapter and facade classes contained in the Microsoft.Practices.Phone.Adapters project. These mocks were developed for general-purpose use, with many methods having properties that accept delegates. Delegate accepting properties enable the execution of any desired behavior necessary for the unit test.

Running Unit Tests

The test projects use the Silverlight unit test framework for Windows Phone and Silverlight 4. To run the tests, first build and then deploy the project either to the Windows Phone emulator or to a real device. On the Windows Phone device, you can now launch an application named, for example, TestableGPSSample.Tests, and then select the unit tests you want to run.

A Silverlight unit-testing framework is used to run unit tests on the phone emulator and on real devices.

Next Topic | Home

Last built: February 10, 2012