Share via


Test Run

Lightweight Testing with Windows PowerShell

Dr. James McCaffrey

Code download available at:  Test Run 2007_05.exe(155 KB)

Contents

The Module Under Test
Ad Hoc Interactive Module Testing
Lightweight Module Test Automation
Wrapping Up

You can think of Windows PowerShell as a dramatic upgrade to the old cmd.exe command shell and associated .bat files. Although designed with systems administration tasks in mind, Windows PowerShell™ has features that also make it ideally suited for lightweight testing tasks. This month, I test Microsoft® .NET Framework-based code modules from both the Windows PowerShell command line and lightweight Windows PowerShell scripts.

Take a look at the screenshots shown in Figure 1 and Figure 2. Figure 1 shows Windows PowerShell performing ad hoc, interactive testing of a .NET DLL module named MyPointLib. Windows PowerShell provides syntax that lets you create objects and dynamically load libraries into the running shell process. I load the MyPointLib library under test, instantiate a Point object, probe the object’s available methods, invoke a Distance method, and verify a correct result.

Figure 1 Ad Hoc Testing with Windows PowerShell on the Command Line

Figure 1** Ad Hoc Testing with Windows PowerShell on the Command Line **(Click the image for a larger view)

Figure 2 shows the output of a Windows PowerShell script. Here, you’ll see that I perform classic module testing—I instantiate Point objects of the MyPointLib library, call instance and static versions of the Distance method, and compare actual results with expected results to determine test case pass/fail results.

Figure 2 Classic Module Testing with a Script

Figure 2** Classic Module Testing with a Script **(Click the image for a larger view)

In this column, I first describe the class library under test, and then I go over the Windows PowerShell commands shown in Figure 1. I also point out Windows PowerShell techniques for interactively testing .NET-based modules. Next I explain the script that produced the output in Figure 2. The source code for the test harness and the library under test are in the download.

I then explore how you can adapt the ideas presented here to meet your own testing needs, and then discuss the scenarios in which Windows PowerShell is more appropriate than alternatives such as unit testing frameworks like NUnit. I am confident you’ll find that Windows PowerShell is a great addition to your software testing arsenal.

The Module Under Test

For demonstration purposes, I kept the MyPointLib library quite simple (see Figure 3). (If you are an experienced .NET developer, you may want to skip ahead to the next section.) I haven’t included code necessary to a production implementation, such as exception handling.

Figure 3 The MyPointLib.cs Test Library

using System;
namespace MyPointLib
{
  public class Point
  {
    private int x;
    private int y;

    public enum DistanceKind { Euclidean, CityBlock };

    public Point() { }

    public Point(int x, int y)
    {
      this.x = x; this.y = y;
    }

    public int X { get { return this.x; } set { this.x = value; } }

    public int Y { get { return this.y; } set { this.y = value; } }

    public override string ToString()
    {
      return “(“ + this.x + “,” + this.y + “)”;
    }

    public double Distance(Point p)
    {
      return Distance(this, p);
    }

    public static double Distance(Point p1, Point p2)
    {
      return Distance(p1, p2, DistanceKind.Euclidean);
    }

    public static double Distance(Point p1, Point p2, DistanceKind dk)
    {
      switch(dk)
      {
        case DistanceKind.CityBlock:
          return Math.Abs(p1.x - p2.x) + Math.Abs(p1.y - p2.y);
        case DistanceKind.Euclidean:
          return Math.Sqrt((Math.Pow(((double)(p1.x - p2.x)), 2)) +
            (Math.Pow(((double)(p1.y - p2.y)), 2)));
        default:
          throw new ArgumentException(“dk”);
      }
    }
  }
}

Though I used C# for MyPointLib, Windows PowerShell can be used to examine and test .NET libraries regardless of the implementation language, and Windows PowerShell can also be used to test classic COM libraries.

The MyPointLib library implements a two-dimensional Point object in rectangular (x,y) form. Notice I also declare a publicly visible enumeration type DistanceKind:

private int x;
private int y;

public enum DistanceKind { Euclidean, CityBlock };

There are several ways to compute the distance between two points. The Euclidean distance (probably the formula you learned in high school) is the most common. In another approach, the city block technique, you can only travel left and right along integer-valued-axis lines. (The city block distance is sometimes called the taxi cab distance.)

Accessing a custom enumeration type in a .NET library using Windows PowerShell requires special syntax, which I will point out shortly. I implement a default constructor and an auxiliary constructor that accepts two arguments:

public Point() {}

public Point(int x, int y)
{
  this.x = x; this.y = y;
}

The default constructor instantiates a point with coordinates (0,0). If you are going to test library modules with Windows PowerShell, you must know how to call both kinds of constructors (no-arguments and arguments). Because my x and y fields are private, I implement X and Y properties to get and set their values:

public int X { get { return this.x; } set { this.x = value; } }

Since I am using C# here, which is case-sensitive, I can use lowercase x for the field name and uppercase X for the property name (similarly y and Y) without any ambiguity. The ability to access class properties is fundamental to module testing with Windows PowerShell.

The MyPointLib library also overrides the ToString method:

public override string ToString()
{
  return “(“ + this.x + “,” + this.y + “)”;
}

My ToString method is very typical of the no-parameter methods you will want to access when testing with Windows PowerShell.

The Point class contains three different Distance methods. The first is an instance method:

public double Distance(Point p)
{
  return Distance(this, p);
}

Instance methods are the most common form of methods you will encounter when examining and testing .NET library modules using Windows PowerShell. The Distance method returns the Euclidean distance between the implied this-context Point object and the Point object P passed in as a parameter, according to the formula distance = sqrt( (x1 - x2)2 + (y1 - y2)2 ). It doesn’t implement that formula explicitly; instead, it delegates to the second Distance method, which is static:

public static double Distance(Point p1, Point p2)
{
  return Distance(p1, p2, DistanceKind.Euclidean);
}

The static Distance returns the Euclidean distance just as the instance version does; the only difference is how the two methods are called. As I’ll demonstrate next, calling a static method in a .NET library module with Windows PowerShell requires different syntax than calling an instance method. This method relies on the third Distance method for the actual computation. The third Distance method accepts a parameter that tells the method which kind of distance formula (Euclidean or CityBlock) to use when computing the distance:

public static double Distance(Point p1, Point p2, DistanceKind dk)
{
  switch(dk)
  {
    case DistanceKind.CityBlock:
      return Math.Abs(p1.x - p2.x) + Math.Abs(p1.y - p2.y);
    case DistanceKind.Euclidean:
      return Math.Sqrt((Math.Pow(((double)(p1.x - p2.x)), 2)) +
        (Math.Pow(((double)(p1.y - p2.y)), 2)));
    default:
      throw new ArgumentException(“dk”);
  }
}

The diagram in Figure 4 illustrates the difference between Euclidean distance and CityBlock distance between the points (0,0) and (3,4). The real purpose of this version of Distance is to show how to use enumeration types with Windows PowerShell. As you can see, the Euclidean distance is a direct line between points, while the CityBlock distance is longer, since it follows gridlines.

Figure 4 Euclidean vs. CityBlock Distance

Figure 4** Euclidean vs. CityBlock Distance **(Click the image for a larger view)

You can build the MyPointLib library from the Windows PowerShell command line if you want. If, for example, the source code for the MyPointLib class library is named MyPointLib.cs and is located in the C:\ModTestWithPS\AppLibs directory, you can build the library with the C# compiler by typing the following simple command:

PS C:\ModTestWithPS\AppLibs> csc.exe /target:library MyPointLib.cs

This will create library MyPointLib.dll in directory C:\ModTestWithPS\AppLibs. This example assumes the Windows PowerShell path environment variable includes the location of the csc.exe program. I will discuss how to set the path variable in the next section.

The Point class contains the types of fields you are most likely to encounter when performing module testing: default and auxiliary constructors, get and set properties, an overridden ToString method that takes no arguments, an enumeration type, and instance and static methods that both accept arguments.

Ad Hoc Interactive Module Testing

Let’s now look at some ways in which you can interactively examine and test .NET libraries using Windows PowerShell. The first part of the output shown in Figure 1 indicates that you are using Windows PowerShell:

Windows PowerShell
Copyright (C) 2006 Microsoft Corporation. All rights reserved.
PS C:\> _

When you launch a new instance of Windows PowerShell, it executes any commands stored in a special profile file. You can view your profile file by typing notepad.exe $profile at the Windows PowerShell prompt. For the example shown in Figure 1, my profile file contains the following four statements:

set-location \
# write-host “Adding location of csc.exe and ildasm.exe to path”
$env:path += ‘;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’
$env:path += ‘;C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin’

The first statement uses the set-location cmdlet to change the current working directory to the root C:\ drive. In case you’re new to Windows PowerShell, let’s take a closer look at cmdlets. Windows PowerShell contains about 130 cmdlets (pronounced “command-lets”), but you can actually accomplish the vast majority of day-to-day file system tasks using only about a dozen or so of these. The set-location cmdlet can be abbreviated as “sl”. In fact, most cmdlets have commonsense aliases that map to old cmd.exe shell commands—in this case, “cd”. You can get a list of all cmdlets by typing get-command, and you can get help on a particular cmdlet using the get-help cmdlet with the target cmdlet’s name (for example, get-help set-location). Windows PowerShell is not case-sensitive.

The second statement in the profile file is a print statement that has been commented out. If the # comment character were removed, the message in double-quotes would be printed to the shell at startup. The third line adds the path to the csc.exe program to the shell path environment variable. The fourth line adds the path to the ildasm.exe program to the shell path variable.

(For security reasons, the default Windows PowerShell policy is not to allow scripts to execute. You can change this with the set-executionpolicy cmdlet, but you’ll need to do so outside of the profile; if the execution policy disables the execution of scripts, the profile won’t run at startup, and thus attempting to change the execution policy in the profile would be futile.)

After Windows PowerShell initializes, I use the set-location cmdlet to change the current working directory ("cd" is a standard alias for set-location, so I could have used it instead):

PS C:\> set-location .\ModTestWithPS\AppLibs
PS C:\ModTestWithPS\AppLibs> _

Then I display all files with a .dll extension located in the current directory:

PS C:\ModTestWithPS\AppLibs> gci *.dll

  Directory: Microsoft.PowerShell.Core\FileSystem::C:\ModTestWithPS\AppLibs

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        12/12/2006  10:31 AM       3072 MyMathLib.dll
-a---        12/12/2006  10:02 AM       4096 MyPointLib.dll

I use gci, which is a shortcut for get-childitem. (I could also have typed dir.)

Up to this point, I haven’t demonstrated anything special about Windows PowerShell. The next command begins to show you some of the power behind it:

PS C:\ModTestWithPS\AppLibs> [Reflection.Assembly]::LoadFile(‘C:\ModTestWithPS\AppLibs\MyPointLib.dll’)

GAC    Version        Location
---    -------        --------
False  v2.0.50727     C:\ModTestWithPS\AppLibs\MyPointLib.dll

Here I call directly into the .NET Framework and use the LoadFile method on the Assembly type in the System.Reflection namespace to load my class library. The syntax [...] indicates that Windows PowerShell should look for an instance of the type between the brackets. The two colons “::” indicates that Windows PowerShell should look for a static property or method. Wow! Windows PowerShell is based on and gives you full access to the .NET Framework.

Now that my class library is loaded, I can instantiate an object from the library:

PS C:\ModTestWithPS\AppLibs> $p1 = new-object MyPointLib.Point

Windows PowerShell is object-oriented, giving it several advantages when compared to older shells, such as cmd.exe and the UNIX-based bash. The $p1 is a Windows PowerShell variable. All variables are preceded by the $ character, making them easy to recognize in scripts. I use the new-object cmdlet to invoke the default, no-arguments, Point constructor. Remember the default Point constructor sets the coordinates of the point to (0,0). I do not use parentheses in the call to new-object because I’m not really calling a method. Rather, I’m providing arguments that are used by the constructor. This is a subtle but important distinction—cmdlets are command-line tools, and they behave that way.

In this example, I know that the MyPointLib library contains a Point class because I have access to the library source code. But what if you are testing a DLL and do not have access to the source code? One easy way to determine which classes are housed in a .NET-based DLL is to use the ildasm.exe tool.

PS C:\ModTestWithPS\AppLibs> ildasm.exe MyPointLib.dll /text /classlist

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class Point                  (public) (auto) (ansi)
// Class DistanceKind           (auto) (ansi) (sealed) (nested public)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(etc.)

The /text switch parameter tells ildasm.exe to send output to the shell instead of to the default GUI display. And the /classlist parameter instructs ildasm.exe to include class information in the output.

Another way to determine what classes are available in an assembly is with Windows PowerShell. The following little script allows you to get the types from the MyPointLib assembly:

PS> $asms=[AppDomain]::CurrentDomain.GetAssemblies()
PS> $asm=$asms|where {$_.fullname -match “MyPointLib”}
PS> $assembly.gettypes()
IsPublic IsSerial Name         BaseType
-------- -------- ----         --------
True     False    Point        System.Object
False    True     DistanceKind System.Enum

The first line collects all of the loaded assemblies in the current AppDomain. The second line looks for the assembly where its fullname matches “MyPointLib”. Finally, it retrieves the types from the assembly.

Next I use the get-member cmdlet to determine exactly what instance methods belong to a Point object:

PS C:\ModTestWithPS\AppLibs> $p1 | get-member -membertype method

Name        MemberType Definition
----        ---------- ----------
Distance    Method     System.Double Distance(Point P)
Equals      Method     System.Boolean Equals(Object obj)
GetHashCode Method     System.Int32 GetHashCode()
GetType     Method     System.Type GetType()
(etc.)

The | character is the pipe operator and is critical to Windows PowerShell. You can interpret the command above to mean, “take the $p1 Point object and then send it to the get-member cmdlet.” In this case, I ask for all public instance methods. I could have specified a -static switch to display static methods. Next, I call the ToString method:

PS C:\ModTestWithPS\AppLibs> $p1.tostring()
(0,0)

This example shows how to call a method that accepts no arguments. Notice that because Windows PowerShell is not case-sensitive, I can type $p1.tostring(), as well as $p1.ToString(). I could also have typed the longer write-host $p1.tostring(), but Windows PowerShell tries to infer what you mean and invoke the appropriate behavior.

Next, I instantiate a second Point object:

PS C:\ModTestWithPS\AppLibs> $p2 = new-object MyPointLib.Point(1,1)

More generally, this demonstrates how to call an object constructor that accepts one or more arguments. Although I did not invoke either of the two properties of the Point class in the screenshot in Figure 1, I could have done so like this:

PS C:\ModTestWithPS\AppLibs> $p2.X
1

To call a property, you just use normal dot notation, as you might have guessed.

Now that I have created two Point objects, (0,0) and (1,1), I can call one of the Distance methods:

PS C:\ModTestWithPS\AppLibs> $d = $p1.Distance($p2) | out-host
1.4142135623731

In this case, I call the instance version of the Distance method and store the result into a variable named $d. I pipe the operation to the out-host cmdlet so that the result will be displayed in the Windows PowerShell shell. Without the pipe, the distance would have been silently stored into $d. (I will demonstrate how to call the static version of the Distance method in the next section.)

Next, I check the return value of the Distance method with an expected value:

PS C:\ModTestWithPS\AppLibs> $check = [System.Math]::Sqrt(2)
PS C:\ModTestWithPS\AppLibs> $check
1.4142135623731

Here, I call the Sqrt method of the System.Math type to compute the square root of 2, and then I store it into a variable named $check. The Windows PowerShell interpreter will automatically determine that variable $check should be type double because that is what the Sqrt method returns. This is called dynamic typing and is common to most scripting languages. I could have called Sqrt this way:

PS C:\ModTestWithPS\AppLibs> [double] $check = [System.Math]::Sqrt(2)

A unique feature of Windows PowerShell is that you can explicitly specify data types for variables. You can use any fundamental .NET data type, such as [Int32] (or the C# equivalent, such as [int]). Specifying data types gives you runtime error checking: when you use [double] in this way, it means that you’ll get an error if you try to store something other than a double in that variable.

To summarize, Windows PowerShell gives you the ability to perform interactive, ad hoc investigation and testing of .NET-based class libraries. Because Windows PowerShell allows you to directly call methods in the .NET Framework, you can use the LoadFile method on the Assembly type to load a .NET-based DLL. You can use the existing ildasm.exe tool with a /classlist switch inside a Windows PowerShell shell to determine what classes are available to you (or use Windows PowerShell to directly query the target Assembly for what types are exposed) and the Windows PowerShell new-object cmdlet to instantiate an object.

After instantiating an object, you can use the Windows PowerShell get-member cmdlet to determine which methods and properties are available. You can also call instance methods in the library under test using normal dot notation.

Lightweight Module Test Automation

Now I’d like to describe how to write lightweight Windows PowerShell scripts to test .NET-based class libraries. I will describe in detail the script that produced the output shown in Figure 2. The entire script is shown in Figure 5.

Figure 5 File Harness.ps1

# file: harness.ps1
# test MyPointLib.dll using data in testCases.txt

write-host “`nBegin MyMathLib.dll module test run using PowerShell`n”

$ep = get-executionpolicy
if ($ep -eq ‘unrestricted’) {
  write-warning “: harness running in unrestricted mode`n”
}

write-host “Loading library MyPointLib.dll `n”
[Reflection.Assembly]::LoadFile(‘C:\ModTestWithPS\AppLibs\MyPointLib.dll’) | out-null

$f = get-content testCases.txt
foreach ($line in $f)
{
  write-host “--------------”
  $tokens = $line.split(‘:’)

  $caseID = $tokens[0]
  $method = $tokens[1]
  $inp = $tokens[2]
  $expected = $tokens[3]  
  # The above lines can be written as
  # $caseID,$method,$inp,$expected = $line.split(“:”)


  $coords = $inp.split(‘,’)
  [int] $p1x = $coords[0]; [int] $p1y = $coords[1]
  [int] $p2x = $coords[2]; [int] $p2y = $coords[3] 
  # the above lines can be written as:
  # $p1x,$p1y,$p2x,$p2y = $inp.split(“,”)

  write-host “Case ID: $caseID  “ -nonewline
  write-host “Inputs: ($p1x,$p1y) ($p2x,$p2y)”
  write-host “Method: $method “ -nonewline 
  write-host “Expected: $expected”

  $p1 = new-object MyPointLib.Point($p1x,$p1y)
  $p2 = new-object MyPointLib.Point($p2x,$p2y) 

  if ($method -eq “Instance Distance “) {
    $actual = $p1.Distance($p2)
    $a = “{0:f2}” -f $actual
  }
  elseif ($method -eq “Static Distance   “) {
    $actual = [MyPointLib.Point]::Distance($p1,$p2)
    $a = “{0:f2}” -f $actual
  }
  else {
    write-host “Not yet implemented”
    continue
  }

  write-host “Actual: “$actual
  write-host “Result: “ -nonewline

  if ($a -eq $expected) {
    write-host “ Pass “ -backgroundcolor green -foregroundcolor darkblue
  }
  else {
    write-host “*FAIL*” -backgroundcolor red
  }


  trap {
    write-host “Fatal error trapped at case “ $caseID
    continue
  }
 
} # main loop

write-host “--------------”
write-host “`nEnd test run`n”

# end script

I begin my Windows PowerShell test harness script with a couple of comments:

# file: harness.ps1
# test MyPointLib.dll using data in testCases.txt

The # symbol is the comment character for Windows PowerShell scripts. Like Visual Basic®, Windows PowerShell is line-based, so comments run through the end of a line.

My test harness reads test case data from an external file named testCases.txt, which is:

001:Instance Distance :0,0,3,4:5.00
002:Instance Distance :1,1,4,4:4.24
003:Chebyshev Distance:0,0,3,4:5.00
004:Chebyshev Distance:1,1,4,4:4.24
005:Static Distance   :0,0,3,4:6.00:deliberate fail
006:Static Distance   :0,3,3,0:4.24

Each line represents a single test case, and each field is delimited by the colon character. The first field is a test case ID. The second is a string that tells the test harness which Distance method to test. (The trailing spaces are for readability.) The third field is a set of four comma-delimited integers that represent (x,y) coordinates. The fourth field is an expected distance value. And the fifth, which is an optional field, is a test case comment. Notice test case 005 should generate a deliberate failure—the distance between (0,0) and (3,4) is 5.00, not 6.00.

Next I display a message to the Windows PowerShell shell:

write-host “`nBegin MyMathLib.dll module test run using PowerShell`n”

I use the write-host cmdlet, which is the most flexible way to send output to the console. Alternatives include using the write-output and out-host cmdlets, or simply placing a message without any cmdlet.

String literals in Windows PowerShell can be delimited by either single quotes or double quotes. Single quoted strings appear exactly as typed, but double-quoted strings evaluate certain escape characters inside the string. In this example, the back-tick character followed by "n" is the escape sequence for an embedded newline.

Next, my test harness script examines the current shell execution policy:

$ep = get-executionpolicy
if ($ep -eq ‘unrestricted’) {
  write-warning “: harness running in unresticted mode`n”
}

As you can see, I call the get-executionpolicy cmdlet and store the result into a variable $ep. If Windows PowerShell is in unrestricted execution mode, I use the write-warning cmdlet to display the text “WARNING” followed by a message in yellow text against a black background, as shown in Figure 2.

Then I load my library under test:

write-host “Loading library MyPointLib.dll `n”
[Reflection.Assembly]::LoadFile(
    ‘C:\ModTestWithPS\AppLibs\MyPointLib.dll’) | out-null

I already explained the process of calling the LoadFile method from the Windows PowerShell command line. When called from inside a Windows PowerShell script, the mechanism is exactly the same. Here I pipe output to the out-null cmdlet to suppress progress messages.

Now I read the entire contents of my test case data file into memory and iterate through each line:

$f = get-content ‘testCases.txt’
foreach ($line in $f)
{
  # process each test case here
}

I use the get-content cmdlet to read the entire contents of file testCases.txt into an object named $f. Then I use the Windows PowerShell foreach loop structure to iterate through the file contents a line at a time, storing the result into the arbitrarily named variable $line.

Inside the main processing loop, I parse each test case field:

$tokens = $line.split(‘:’)
$caseID = $tokens[0]
$method = $tokens[1]
$inp = $tokens[2]
$expected = $tokens[3]

I use the Split method to break each line at colon characters and store each part of the line into an array named $tokens. For readability, I transfer the contents of each cell of $tokens into descriptively named variables: $caseID, $method, $inp, and $expected. (Note that I use $inp because $input is a special, reserved Windows PowerShell variable.)

Then I parse my inputs:

$coords = $inp.split(‘,’)
[int] $p1x = $coords[0]; [int] $p1y = $coords[1]
[int] $p2x = $coords[2]; [int] $p2y = $coords[3]

Notice I use the explicit-typing feature of Windows PowerShell to tell my script that my input is type int.

After sending some messages to the shell, I am ready to instantiate two Point objects:

$p1 = new-object MyPointLib.Point($p1x,$p1y)
$p2 = new-object MyPointLib.Point($p2x,$p2y) 

I described the new-object cmdlet in the previous section. The new-object cmdlet can also instantiate classic COM objects if you add a -com switch.

Now I can call my Distance method under test:

if ($method -eq “Instance Distance “) {
  $actual = $p1.Distance($p2)
  $a = “{0:f2}” -f $actual
}
elseif ($method -eq “Static Distance   “) {
  $actual = [MyPointLib.Point]::Distance($p1,$p2)
  $a = “{0:f2}” -f $actual
}
else {
  write-host “Not yet implemented”
  continue
}

I use an if/elseif/else control structure to branch on the value of the $method variable, which was read from test case input. Windows PowerShell has a complete set of control structures, including a switch statement. A minor quirk of Windows PowerShell is the use of Boolean operators (-eq, -lt, -gt, and so on) rather than syntactical tokens (==, <, >, and so on). Although this is primarily a matter of style, I do really like the Windows PowerShell approach. The designers of Windows PowerShell chose this approach because of file redirection. For example, if you do something like the following:

get-childitem > output.txt

then you want the > operation to be a file redirection, not a Boolean comparison. As such, the operators that are available in bash and Perl were employed.

I described how to call the instance version of the Distance method under test in the previous section. To call a static method, you prepend the method name with the namespace in square brackets followed by a pair of colon characters (as you might in C++):

[MyPointLib.Point]::Distance($p1,$p2)

Since the result of the instance and static Distance methods are type double, I format the actual result to two decimal places before comparing the actual result to the expected result:

$a = “{0:f2}” -f $actual

Basically, this says, “store into variable $a, the value in variable $actual, formatted to two decimal places.”

Now I compare the actual result to the expected result and display the test result:

if ($a -eq $expected) {
  write-host “ Pass “ -backgroundcolor green -foregroundcolor darkblue
}
else {
  write-host “*FAIL*” -backgroundcolor red
}

Notice my use of the -backgroundcolor and -foregroundcolor switch parameters of the write-host cmdlet. These provide an easy way to produce output with colored text.

A very nice feature of Windows PowerShell is the language’s error-handling capabilities. I’ve used the Windows PowerShell trap keyword to handle any exceptions that are thrown in my script:

trap {
  write-host “Fatal error trapped at case “ $caseID
  continue
}

Here, with the trap block inside my main processing loop, I simply print a message and then use the continue keyword to tell my script to continue execution with the next loop iteration.

Notice that my test harness does not test the third Distance method of the Point class, which uses an enumeration type. Take a look at its signature:

public enum DistanceKind { Euclidean, CityBlock };

public static double Distance(Point p1, Point p2, DistanceKind dk)

Calling this version of the Distance function would look something like this:

$dk = [MyPointLib.Point+DistanceKind]::Euclidean
$dist  = [MyPointLib.Point]::Distance($p1,$p2,$dk)

Wrapping Up

Windows PowerShell has all the features you need to write effective, lightweight test automation scripts. These features include a complete set of control structures, the ability to explicitly type variables, error-handling capabilities, and the ability to call simple cmdlets that produce functionality that would otherwise require several lines of code.

Windows PowerShell is a free, separate-install available from microsoft.com/powershell. It can be installed on machines running Windows® XP, Windows Vista™, Windows Server® 2003, and the next version of Windows Server code-named "Longhorn."

"But James," you say. "I already can perform module testing in several other ways. What advantages does Windows PowerShell offer me?" These other ways are not obsolete, but sometimes Windows PowerShell is more appropriate. It is particularly useful for ad hoc, interactive .NET library testing from the command line.

Before Windows PowerShell came along, your most practical option for testing a .NET-based library was to fire up Visual Studio®. But sometimes your host machine does not have Visual Studio installed, and sometimes you just want a quick way to call a method in the .NET library under test. These two situations are ideally suited for Windows PowerShell.

My experience has shown that Windows PowerShell also makes sense for very lightweight test automation scripts. Visual Studio has the advantages of integrated .NET Framework help and a sophisticated text editor. Windows PowerShell has the advantages of very low overhead and better interactive capabilities in most cases.

"But James! How long will it take me to learn yet another language?" Windows PowerShell was designed to be intuitive and easy to learn. Early adopters have reported that Windows PowerShell is very easy to pick up.

Send your questions and comments for James to testrun@microsoft.com.

Dr. James McCaffrey works for Volt Information Sciences, Inc., where he manages technical training for software engineers working at Microsoft. He has worked on several Microsoft products, including Internet Explorer and MSN Search. He is also the author of .NET Test Automation: A Problem-Solution Approach (Apress, 2006). James can be reached at jmccaffrey@volt.com or v-jammc@microsoft.com.