Unit Testing for Smart Devices is one of the key features added as part of Orcas in the Smart Device Development front.  This feature has been built upon the existing framework in Visual Studio that enables unit testing desktop projects.  It should hopefully be a cakewalk for those already familiar with the testing features in Visual Studio Team Test.

For those who have not already used the testing features in Visual Studio, let me introduce the feature and walk you through a basic scenario of unit testing a smart device library. For a start, I will keep it simple. I will cover other deeper scenarios in the subsequent blogs.

Structure of the Smart Device Unit Tests

Unit tests are methods marked with the [TestMethod()] attribute and are members of a class marked with the [TestClass()] attribute. The test source code files containing these test classes must be part of a “Smart Device Unit Test Project”.  

The test methods contain logic to validate the expected results based on assertions.  There are various assertion classes like Assert, StringAssert and CollectionAssert  defined in the Unit Testing Framework that provide assertions to validate various conditional statements like Assert.IsTrue(), Assert.AreEqual() etc.

Additionally you can also used the [ExpectedException()] attribute to test whether a particular exception is thrown. In this case the test case will pass if the specified exception is thrown or fail otherwise.

Additionally you can have methods in the test class marked with [AssemblyInitialize()],[ClassInitialize()],[TestInitialize()] to set up the environment appropriately before execution of all tests in the test assembly, or all tests in a class or before each single test respectively. Similarly, you can have methods marked with [AssemblyCleanup()],[ClassCleanup()],[TestCleanup()] to perform environment teardown after execution of all tests in the assembly, or all tests in a class or for each test respectively. All the above attributes are defined in the Microsoft.VisualStudio.TestTools.UnitTesting namespace in Microsoft.VisualStudio.SmartDevice.UnitTestFramework.dll.

 Creating Smart Device Unit Tests

Now that we are warmed up, let me show you how to generating unit tests for a smart device project.  

1.    Setting up the project for which unit tests must be created

Let’s say that we need to test a BankAccount class that is part of a Pocket PC 5.0 C# smart device class library. The code for the BankAccount class is as below:

using System;

using System.Collections.Generic;

using System.Text;

 

namespace BankAccountDemo

{

    public class BankAccount

    {

        // Properties

        private float _currentBalance;

        public float CurrentBalance

        {

            get { return _currentBalance; }

        }

       

  // Constructors

        public BankAccount(float initialBalance)

        {

            this._currentBalance = initialBalance;

        }

 

        // Methods

        public void DepositMoney(float depositAmount)

        {

// note that I have a bug here

// I am deducting the deposit amount!!!

            this._currentBalance -= depositAmount;

        }

 

        public void MakePayment(float paymentAmount)

        {

            this._currentBalance -= paymentAmount;

        }

    }

}

2.    Generating unit tests

Visual Studio can generate test method stubs for all the methods in you class or for a specific method. All one needs to do is right-click the relevant code element and click ‘Create Unit Tests’ from the context menu.  Here, we right-click on the class name BankAccount and then ‘Create Unit Tests’. This will bring up the ‘Create Unit Tests’ dialog box which allows you to customize the generated test code.

You can choose to have your tests generated in either VB or C# Smart Device Test Project. For this example select ‘Create a new Smart Device C# Test Project' on the dialog and click OK. Provide the name of the test project and click Create.

This will automatically create a smart device test project containing test method stubs for testing all the methods and properties defined in the BankAccountClass. The test class will look like this:

using BankAccountDemo;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace BankAccountTestProject

{ 

    /// <summary>

    ///This is a test class for BankAccountTest and is intended

    ///to contain all BankAccountTest Unit Tests

    ///</summary>

    [TestClass()]

    public class BankAccountTest

    {

        /// <summary>

        ///A test for DepositMoney

        ///</summary>

        [TestMethod()]

        public void DepositMoneyTest()

        {

// TODO: Initialize to an appropriate value

            float initialBalance = 0F;           

BankAccount target = new BankAccount(initialBalance);

// TODO: Initialize to an appropriate value

float depositAmount = 0F;            target.DepositMoney(depositAmount);

Assert.Inconclusive("A method that does not return a value cannot be verified.");

        }        

  ///More test methods for each of the method/property

  ///in the  BankAccountClass ....

 

}

}

On creation of the test project, you will also notice the following:

  • Vsmdi file: The Visual Studio Test List Editor window lets you organize and categorize tests into various test lists. The user created test lists and links to the test cases are persisted in this metadata file. You can ignore this file for the moment.
  • Testrunconfig file: This file contains settings that control the way the tests are run. We will touch upon this file little later before starting execution of the tests.
  • Test Project: you will notice that the generated test project has references, in addition to standard system dlls, to the project to be tested and also the Microsoft.VisualStudio.SmartDevice.UnitTestFramework.dll. This dll contains many definitions, classes and attributes, essential to support unit testing for smart devices in Visual Studio. See Device Unit Test Framework for an overview and differences with respect to the desktop unit test framework 

3.    Updating the test methods to reflect the test case

Now we have the test method stubs, but they may not be entirely meaningful. What we need to do next is initialize the test method i.e. specify the initial and expected values. You may additionally want to tweak the implementation of the test method. For e.g., in the DepositMoneyTest method, we will set the initialBalance = 10F. After initializing a bank account with an initial balance of 10, we will deposit an amount of 5 units. So the expected current balance at the end of the transaction is 15. So we add an assert to verify whether they are the same:

/// <summary>

///A test for DepositMoney

///</summary>

[TestMethod()]

public void DepositMoneyTest()

{

float initialBalance = 10F;

      BankAccount target = new BankAccount(initialBalance);

      float depositAmount = 5F;

      target.DepositMoney(depositAmount);

      Assert.AreEqual(target.CurrentBalance, 15F, "Error in depositing money");

} 

 

Executing the Unit Tests

Now that we have created the project and completed authoring the tests, it’s time to execute them.

How do we specify on which device should we execute the tests? This is where the runconfig file I mentioned earlier comes in. If you double click this file, you will notice that runconfig editor comes up and displays a plethora of options based on which you can configure the test run. Navigate to the ‘Host’ page and you will see that the ‘Host’ is set to ‘Smart Device’ (this is the indication that this test run will be executed on a smart device). You can modify the platform and device to specify which device you want to execute the tests on.

 

The important point to note here is that the tests will be executed on the device specified in this run config file, and not on the target device that is currently chosen in the device toolbar.

Before starting the run, we need to manually deploy the appropriate version of NETCF on the chosen device. In Orcas, we could not include support for automatic deployment of NETCF and SQLCE, and so the tester must ensure he has manually prepared the device by installing the required cab files.

Once this is done, we are now ready to execute our tests on the device.  You can start executing the tests from the Test List Editor Window, or the Test View window, the Test tool bar or from the test source file itself (using the Run Tests context menu).  For now, click the ‘Run all tests in solution’ toolbar item in the Test toolbar.

The tests get downloaded and executed on the device and finally you can see the results of the tests in the test results window. You can double click on the result to see the details of the individual test:

Failed      DepositMoneyTest       Assert.AreEqual failed. Expected:<5>. Actual:<15>. Error in depositing money

Here the test has failed saying that while the expected output was 15, the actual output after depositing money was 5.  This implies an error in either the test method or the actual implementation.  Here, we know there is an error in the implementation of the DepositMoney() method.

All we need to do is go back to the BankAccount.cs code file and correct the DepositMoney method()

public void DepositMoney(float depositAmount)

{

     // The bug was that I was deducting the deposit amount!!!

     // this._currentBalance -= depositAmount;

     this._currentBalance += depositAmount;

}

 

And then hit the ‘Run all tests in solution’ toolbar item in the Test Toolbar!  This will take care of rebuilding both the projects and rerun the test this time with the correct code. And if you check the test results window, this time round the test passes! I can now safely Deposit my money.

Passed      DepositMoneyTest                 

Hopefully this has been a good start.  In the next blog, I will discuss how you can use the unit testing framework to test private methods.