Thursday, January 31, 2008

Automated Builds running .Net CF Unit Tests!

Having the capability of run unit testing for the .Net Compact Framework within Visual Studio 2008 is a very nice feature, but it goes beyond than just launch test runs from the IDE. If we have an automated build process or even a build server, we can include the .Net CF unit tests as part of the automated process. This is a great feature for practices like continuous integration or just for automated build processes.

But it's not completely painless. Probably you cannot even build the test project using MSBuild in your first try. I'm including in this post a sample solution, obviously a VS 2008 solution, which does can be built using MSBuild. It also run all the tests first on Windows Mobile 6 Classic Emulator and then on Windows Mobile 5 Pocket PC Emulator. Pretty sweet! But here goes the question:

How to make the project build and run the tests using MSBuild?

The first problem I found trying to build the test project is a missing reference. Actually, it's a missing search path on the project, because it's prepared for run from Visual Studio and not using MSBuild. To fix this, we just need to add a new search path in the project when it's not building inside Visual Studio:

1) Edit the test project file (i.e. TestProject1.csproj):
    - Unload the project (Right click - Unload Project)
    - Edit TestProject1.csproj

2) Add the following PropertyGroup:

<PropertyGroup Condition="'$(BuildingInsideVisualStudio)'!='true' ">
<DeviceTestAssemblySearchPath>$(DevEnvDir)\\PublicAssemblies</DeviceTestAssemblySearchPath>
</PropertyGroup>

3) Now the project can be built from the command line! But we need to launch "MSTest" after build in order to run the tests as part of the *automated* testproject1 build process:

<Target Name="AfterBuild" Condition="'$(BuildingInsideVisualStudio)'!='true' ">
<Exec Command="MSTest /testcontainer:bin\debug\testproject1.dll /runconfig:..\SmartDeviceTestRun.testrunconfig" />
</Target>

4) We can save the changes and reload the project (Right click - Reload Project)

It's done! Now you can build TestProject1.csproj using the default target and it will run the tests on the emulator as part of the build process.

Additionally, in the provided sample, I've removed the test list file (.vsmdi) and added a new .testrunConfig file which will be launched on Windows Mobile 5 Pocket PC (the first one will be launched on WM6 Classic Emulator, and I renamed it to clarify), and added a second <Exec> MSTest on AfterBuild target pointing this configuration file. Doing this, the automated build process will run the tests on BOTH platforms.

I'll post soon about how to create those testrun configuration files to setup different target platform for each test run in detail and what other ways we have to use them. But now, if you have Visual Studio 2008, and the WM6 Professional SDK installed on your machine, you can download the sample solution, try and open a "Microsoft Visual Studio 2008 Command Prompt" on the solution folder and just type MSBuild...

This is what I got:


Here's the sourcecode:


It seems we finally have automated test runs on the device as part of the build process... Sweet!!

Thursday, January 17, 2008

Unit Testing for .Net Compact Framework is now friendly enough!

When I joined to Microsoft Patterns & Practices Mobile Client Software Factory team a couple of years ago, I discovered test-driven development as a reality, but the first big issue we had to face then, was the lack of unit testing support for the .Net Compact Framework.

Having so many testing frameworks for the full framework, the .Net CF was nobody's land regarding unit testing. Not even Visual Studio 2005, which includes on its Team Suite edition a nicely integrated Unit Testing framework provides support for .Net CF Unit Testing. There was only one option: build our own Unit Testing framework (or at least our test runner), and that was what we called the p&p Compact Framework Test Runner, which is part of the Mobile Client Software Factory.

It was just enough effort to make things work. There was a big wish list for cool features that never were done like IDE integration. I want to mention also another old project with the same mission, the CFNUnitBridge by Trey.

Now Visual Studio 2008 (formerly Orcas) has arrived, and it includes support for .Net CF Unit Testing which very nice IDE-integration, with the same approach of the full framework unit testing. Such a good news!

Wanna try it?

Let's do a remake of a very interesting Daniel Moth's post from a couple of years ago:

1) Let's create a new Smart Device Class Library for .Net CF 3.5 on Visual Studio 2008

2) Add a new class to the project with the following content:

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;

namespace AppForTest
{
public class Class1
{
public Int32 GetBuildVersionPart()
{
return System.Environment.Version.Build;
}

}
}

3) Right Click on the method and choose "Create Unit Tests..."
image


And select only the GetBuildVersionPart() method
image


Enter the Test Project name
image


It will create a new test project, unit testing class and the following unit test method:

/// <summary>
///
A test for GetBuildVersionPart
///</summary>
[TestMethod()]
public void GetBuildVersionPartTest()
{
Class1 target = new Class1(); // TODO: Initialize to an appropriate value
int expected = 0; // TODO: Initialize to an appropriate value
int actual;
actual = target.GetBuildVersionPart();
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
}

4) Replace expected = 0 with expected = 50727 which is the build number for the full framework 3.5, and comment out the Assert.Inconclusive line (or delete it).


5) Run the GetBuildVersionPartTest from the Test View window:


image


It will build the projects, launch the emulator and run the tests on the emulator. After some seconds, the result will be:


image


This time the test has failed, the expected value was 50727, the build number for the full framework, but the actual value was 7283, the build number for the compact framework.


This time the Unit Test has run on the emulator! It really was a .Net CF Unit Test :)

Friday, January 11, 2008

Making Multiline MeasureString work with different font sizes

Today I got an email asking for help using my suggested Multiline MeasureString implementation when the control has a different font size.

Well, in the provided sample code I'm using the default font size on all the controls. To get a simpler code, I'm using always the form graphics as parameter for MeasureString:

textboxHeight = CFMeasureString.MeasureString(CreateGraphics(), newText,textboxRect, true).Height;

CreateGraphics() as shown gets the Graphics object for the form. MeasureString will calculate the height based on the selected font on that Graphics.


Let's work now with the adaptative UI sample. What if we change the font size in one of the controls?


image image


Let's see what happen if we change it to 15:


image


The application will not work properly anymore, because the font of the label is not the same font of the provided Graphics:


image


Apparently, the solution could be to get the graphics from the control instead of still using the form graphics. We can create an overload for CreateGraphics like this:

private Graphics CreateGraphics(Control control)
{
return Graphics.FromHdc(control.Handle);
}

But it doesn't work because the Graphics won't have any font selected. .Net CF releases fonts after using them when i.e. you call DrawString (thanks god!)... that's the reason for having the same measure based on the default font size in spite of what graphics we are using.


The real solution


Obviously, the problem has a solution. We can create a new overload for MeasureString, which receives the control as one of its parameters and: 1)creates the right graphics object 2)select the control's font in the graphics and 3)releases it after the string has been measured.


To get all this working, we'll add two imports into our CFMeasureString class: GetDC and SelectObject.


Please include the following code as part of the CFMeasureString class:

[DllImport("coredll.dll")]
static extern IntPtr GetDC(IntPtr hWnd);

[DllImport("coredll.dll")]
static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

/// <summary>
///
Measure a multiline string for a Control
/// </summary>
/// <param name="control">
control</param>
/// <param name="text">
string to measure</param>
/// <param name="rect">
Original rect. The width will be taken as fixed.</param>
/// <returns>
A Size object with the measure of the string according with the params</returns>
static public Size MeasureString(Control control, string text, Rectangle rect)
{
Size result = Size.Empty;
IntPtr controlFont = control.Font.ToHfont();
IntPtr hDC = GetDC(control.Handle);
using (Graphics gr = Graphics.FromHdc(hDC))
{
IntPtr originalObject = SelectObject(hDC, controlFont);
result = MeasureString(gr, text, rect, control is TextBox);
SelectObject(hDC, originalObject); //Release resources
}
return result;
}

In our sample application, we can replace the MeasureString calls in Form1.cs with the following:

labelHeight = CFMeasureString.MeasureString(label1, label1.Text, labelRect).Height;
textboxHeight = CFMeasureString.MeasureString(textBox1, newText, textboxRect).Height;
instructionsLabel.Height = CFMeasureString.MeasureString(instructionsLabel,instructionsLabel.Text, instructionsLabel.ClientRectangle).Height;

Now, even if we additionally change the textbox font size to 20, the application still working!


image


Thanks Sven for the feedback, I hope it helps!