Tour wrap-up: Unit testing - Now that we've gone through the five parts in this series we'll show one final, but important step, adding unit tests.
Adherents for a "Test First" methodology make a strong case for the importance of writing unit tests as an integral part of your application development process – and one should be commenced early in the development cycle. Let’s see how to lay the foundation for integrated, ongoing, automated testing and test development.
We’ll use the Silverlight Unit Test Framework to set up a test environment for our Silverlight solution. This framework is installed with the Silverlight Toolkit; recall that that is a different animal from the Silverlight Tools:
Items in the Silverlight Toolkit are categorized into quality bands to denote their stage of development: Mature/SDK; Stable; Preview; and Experimental. The Silverlight test components are in the Experimental band as of this writing (mid-May, 2010); so expect them to grow, change, and mature with time.
Be sure that you’ve installed the Silverlight Toolkit before proceeding.
Let’s get started:
Change the name from SilverlightTest1 to SilverlightTest. Click [OK].
Accept all of the default settings on the resulting “New Silverlight Application” dialog:
Solution Explorer BEFORE adding the Test components | Solution Explorer AFTER adding the Test components |
The template has added a pair of “SilverlightTestTestPage” files to the web project, and made the SilverlightTestTestPage.aspx page the start page for the web project.
It also added a new SilverlightTest project which contains a Tests.cs file with stubs for a test class and method. Since we want to take advantage of Silverlight-specific test features here, we modified the class as follows to inherit from Microsoft.Silverlight.Testing.SilverlightTest.
C# | using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using Microsoft.Silverlight.Testing; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace SilverlightTest : Microsoft.Silverlight.Testing.SilverlightTest { [TestClass] public class Tests { [TestMethod] public void TestMethod1() { } } } |
VB | Imports System Imports System.Net Imports System.Windows Imports System.Windows.Controls Imports System.Windows.Documents Imports System.Windows.Ink Imports System.Windows.Input Imports System.Windows.Media Imports System.Windows.Media.Animation Imports System.Windows.Shapes Imports Microsoft.Silverlight.Testing Imports Microsoft.VisualStudio.TestTools.UnitTesting Namespace SilverlightTest : Microsoft.Silverlight.Testing.SilverlightTest <TestClass> Public Class Tests <TestMethod> Public Sub TestMethod1() End Sub End Class End Namespace |
Since we’ll want to use DevForce facilities in our tests, let’s add the necessary references to the new SilverlightTest project:
The SimpleSteps assembly includes a compiled copy of the DevForce-generated business object model, as well as other things we may want to test (such as methods in the view model, MainPageViewModel).
Let’s also add a few using statements:
C# | using System.Linq; using IdeaBlade.Core; using IdeaBlade.EntityModel; using IdeaBlade.Linq; using IdeaBlade.Validation; using SimpleSteps; |
VB | Imports System.Linq Imports IdeaBlade.Core Imports IdeaBlade.EntityModel Imports IdeaBlade.Linq Imports IdeaBlade.Validation Imports SimpleSteps |
We’re ready now to create a simple test. Here we’re going to show how to test an asynchronous query. You may not want to perform server queries in your unit tests, but instead rely upon local cache for your test data needs. If you choose to test any server access – login, query, or save - then you’ll need to write an asynchronous test. We show you how here. Modify TestMethod1 to make it appear as follows:
C# | [TestMethod] [Asynchronous] [Tag(“Fetch”)] public void RetrieveFrenchCustomers() { NorthwindIBEntities mgr = new NorthwindIBEntities(); var query = mgr.Customers.Where(c => c.Country == "France"); mgr.ExecuteQueryAsync<Customer>(query, GotCustomers, null); } public void GotCustomers (EntityQueryOperation<Customer> args) { Assert.IsTrue(args.Results.Count() > 0, "Expected to retrieve some French customers, but found none."); base.TestComplete(); } |
VB | <TestMethod()> _ <Asynchronous()> _ <Tag("Fetch")> _ Public Sub RetrieveUSCustomers() Dim mgr As NorthwindIBEntities = New NorthwindIBEntities() Dim query = mgr.Customers.Where(Function(c) c.Country = "USA") mgr.ExecuteQueryAsync(Of Customer)(query, AddressOf GotCustomers, Nothing) End Sub Public Sub GotCustomers(ByVal args As EntityQueryOperation(Of Customer)) Assert.IsTrue(args.Results.Count() > 0, "Expected to retrieve some customers, but found none.") MyBase.TestComplete() End Sub |
Note several things about this test method:
We’re almost ready to run our test, but first let’s build the solution. We get one error:
DevForce entities are marked with DataContract and DataMember attributes from this assembly, so let’s add the reference now.
Now run the solution. You should see this dialog in your browser window. Ignore it for now (or click the [No, run all tests] button).
You should then see the following page. We’ve expanded the tree control at the left to show the detail on the RetrieveFrenchCustomers test (which passed).
Let’s make the test fail to see what that looks like. Close the browser window to shut down the test application. Find the statement that defines the query and change the match string from “France” to “XXXFrance”:
C# | var query = mgr.Customers.Where(c => c.Country == "XXXFrance"); |
VB | Dim query = mgr.Customers.Where(Function(c) c.Country = "XXXFrance") |
Re-run the app, again ignoring the “Tag Expressions” dialog. The app now breaks at the Assert statement1:
If you don’t want each test failure to break, you can disable “Just My Code”. Alternately, you can add any expected test failures, such as the AssertFailedException, to the Debug | Exceptions window:
Once you’ve done so, the Silverlight test UI will show all test results.
Your learned the basics of setting up your test project, so you now have a foundation you can build upon. Laying in tests from an early stage will encourage you to factor your code so that it is as testable as possible. You’ll never be sorry!
There’s a lot more to learn about testing than we can cover in this Guide. For example, you can add lots of tests and then run only a subset of them using the Tag attribute; mix synchronous and asynchronous tests, test your UI, and much more. Searching the web for "Silverlight Unit Test Framework" or the like will lead you to many resources. One excellent one is the site of the framework’s author, at http://www.jeff.wilcox.name/topics/dev/testing/.
To set your solution up to run as a regular Silverlight application again, all you need do is to re-establish the Default.aspx page as the web project’s Start Page. Right-click that file in the Solution Explorer, and select Set As Start Page:
The user interface for the application built during this tour uses a DataForm component supplied by the Silverlight 4 Toolkit (different from the Silverlight 4 Tools!). You can download the Toolkit here:
http://silverlight.codeplex.com/
To work with this application, as all DevForce 2010 Silverlight applications, you will need Visual Studio 2010, the Silverlight 4 runtime (SilverlightTools.exe), and DevForce installed.