July 2009

Volume 24 Number 07

Test Run - Request-Response Testing With F#

By James McCaffrey | July 2009


The Application Under Test
ASP.NET Request-Response Testing with F#
Harness Details
Wrap Up

The F# programming language has several characteristics that make it well-suited for software test automation. In this month's column, I show you how to use F# to perform HTTP request-response testing for ASP.NET Web applications. Specifically, I create a short test-harness program that simulates a user exercising an ASP.NET application. The F# harness programmatically posts HTTP request information to the application on a Web server. It then fetches the HTTP response stream and examines the HTML text for an expected value of some sort to determine a pass/fail result. In addition to being a useful testing technique in its own right, learning how to perform HTTP request-response testing with F# provides you with an excellent way to learn about the F# language. This column assumes you have basic familiarity with ASP.NET technology and intermediate .NET programming skills with C# or VB.NET, but does not assume you have any experience with the F# language. However, even if you are new to ASP.NET and test automation in general, you should still be able to follow this month's column without too much difficulty. To see where I'm headed, take a look atFigures 1and 2. Figure 1illustrates the example ASP.NET Web application under test that I use. The system under test is a simple but representative Web application, named MiniCalc.

Figure 1

Figure 1 ASP.NET Web Application Under Test

I deliberately keep my ASP.NET Web application under test as simple as possible so that I don't obscure the key points in the test automation. Realistic Web applications are significantly more complex than the dummy MiniCalc application shown in Figure 1, but the F# testing techniques I describe here easily generalize to complex applications. The MiniCalc Web application accepts two integers and an instruction to add or multiply. It then sends the values to a Web server where the result is computed. The server creates the HTML response and sends it back to the client browser where the result is displayed to four decimal places. Figure 2shows an F# test harness in action.

Figure 2

Figure 2 Request-Response Testing with F#

My F# harness is named project.exe and does not accept any command-line arguments. For simplicity, I have hard-coded information including the URL of the Web application, test case input, and test case expected results. I will explain how you can parameterize the test harness later. My harness begins by echoing the target URL of localhost:15900/MiniCalc/Default.aspx. Notice that I am using the Visual Studio development Web server rather than IIS, so I specify a port number (15900) instead of the IIS default port 80. In test case 001, my F# harness programmatically posts information that corresponds to a user typing 5 into control TextBox1, typing a 3 into TextBox2, selecting RadioButton1 (to indicate an addition operation), and clicking on Button1 (to calculate). Behind the scenes, the harness captures the HTTP response from the Web server and then searches the response for an indication that 8.0000 (the correct result of 5 + 3) is in the TextBox3 result control. The harness tracks the number of test cases that pass and the number that fail (which is a surprisingly interesting operation in F#) and displays those results after all test cases have been processed.

In the sections of this column that follow, I briefly describe the Web application under test so you'll know exactly what is being tested. Next, I walk you through the details of creating lightweight HTTP request-response automation using the F# language. I wrap up by describing how you can modify the techniques I've presented to meet your own needs. I also present a few opinions about why you should take time to investigate F#. I think you'll find the information presented here interesting and a useful addition to your testing toolset.

The Application Under Test

Let's take a look at the code for the MiniCalc ASP.NET Web application, which is the target of my test automation. I created the MiniCalc application using Visual Studio 2008 to take advantage of the built-in development Web server. After launching Visual Studio, I clicked on File | New | Web Site. In order to avoid the ASP.NET code-behind mechanism and keep all the code for my Web application in a single file, I selected the Empty Web Site option. To avoid using IIS, I selected the File System option from the Location field drop-down control. I decided to use C# for the MiniCalc application, but the F# test harness I present in this column works with ASP.NET applications written in VB.NET. Additionally, with slight modifications the harness can target non-.NET Web applications that are written using technologies such as classic ASP, CGI, PHP, JSP, Ruby, and so on. Because I intend to use the Visual Studio development server, I specified a standard location in the file system, such as C:\MyWebApps\MiniCalc, rather than an IIS-specific location, such as C:\Inetpub\wwwroot\MiniCalc. I clicked OK on the New Web Site dialog to generate the structure of my Web application. Next, I went to the Solution Explorer window, right-clicked on the MiniCalc project name, and selected Add New Item from the context menu. I then selected Web Form from the list of installed templates and accepted the Default.aspx file name. I cleared the "Place code in separate file" option and then clicked the Add button.

Next, I double-clicked on the Default.aspx file name in Solution Explorer to edit the template-generated code. I deleted all the template code and replaced it with the code shown in Figure 3.

Figure 3 MiniCalc Web Application Under Test Source

<%@ Page Language="C#" %> <script language="C#" runat="server"> private void Button1_Click(object sender, System.EventArgs e) { int alpha =

int.Parse(TextBox1.Text.Trim()); int beta = int.Parse(TextBox2.Text.Trim()); if (RadioButton1.Checked) { TextBox3.Text = Sum(alpha, beta).ToString("F4"); } else if (RadioButton2.Checked) { TextBox3.Text = Product(alpha, beta).ToString("F4"); } else TextBox3.Text = "Select method"; } private static double Sum(int a, int b) { double ans = a + b; return ans; } private static double Product(int a, int b) { double ans = a * b; return ans; } </script> <html> <head> <style type="text/css"> fieldset { width: 16em } body { font-size: 10pt; font-family: Arial } </style> <title>Default.aspx</title> </head> <body bgColor="#ffcc99"> <h3>MiniCalc by ASP.NET</h3> <form method="post" name="theForm" id="theForm" runat="server" action="Default.aspx"> <p> <asp:Label id="Label1" runat="server"> Enter integer:&nbsp&nbsp</asp:Label> <asp:TextBox id="TextBox1" width="100" runat="server" /> </p> <p> <asp:Label id="Label2" runat="server"> Enter another:&nbsp</asp:Label> <asp:TextBox id="TextBox2" width="100" runat="server" /> </p> <p></p> <fieldset> <legend>Arithmentic Operation</legend> <p> <asp:RadioButton id="RadioButton1" GroupName="Operation" runat="server" /> Addition </p> <p> <asp:RadioButton id="RadioButton2" GroupName="Operation" runat="server" /> Multiplication </p> <p></p> </fieldset> <p> <asp:Button id="Button1" runat="server" text=" Calculate " onclick="Button1_Click" /> </p> <p> <asp:TextBox id="TextBox3" width="120" runat="server" /> </p> </form> </body> </html>

In order to keep my source code small in size and easy to understand, I am not using good coding practices in this Web application. In particular, I do not do any error checking and I use a somewhat haphazard design approach by combining server-side controls (such as <asp:TextBox>) with plain HTML (such as <fieldset>). The most important parts of the code listing in Figure 3for you to note are the IDs of my ASP.NET server-side controls. I use default IDs Label1 (user prompt), TextBox1 and TextBox2 (input for two integers), RadioButton1 and RadioButton2 (choice of addition or multiplication), Button1 (calculate), and TextBox3 (result). To perform automated HTTP request-response testing for an ASP.NET application, you must know the IDs of the application's controls. In this situation, I have the source code available because I am creating the application myself; but even if you are testing a Web application you didn't write, you can always examine the application by using a Web browser's View Source functionality.

To verify that my Web application under test was built correctly, I hit the <F5> key. I clicked OK on the resulting Debugging Not Enabled dialog to instruct Visual Studio to modify the Web application's Web.config file. Visual Studio then started the development server and assigned a random port number to the Web application—in this case 15900, as you can see in Figure 1. If I had wanted to specify a port number, I could have selected the MiniCalc project in Solution Explorer, and then clicked on the View main menu item and selected the Properties Window option. This would display a "Use dynamic ports" option set to a default of True, and I could change the value to False and enter a value in the "Port number" field.

Notice that the action attribute of my <form> element is set to Default.aspx. In other words, every time a user submits a request, the same Default.aspx page code is executed. This gives my MiniCalc Web application the feel of a single application rather than a sequence of different Web pages. Because HTTP is a stateless protocol, ASP.NET accomplishes the application effect by maintaining the Web application's state in a special hidden value type, called the ViewState. As we'll see shortly, dealing with an ASP.NET application's ViewState is the key to programmatically posting data to the application.

ASP.NET Request-Response Testing with F#

Now that we've seen the Web application under test, let's go over the F# test harness program that produced the screenshot in Figure 2. F# is tentatively scheduled to ship with the next version of Visual Studio. For this column, I decided to use the September 2008 Community Technical Preview (CTP) version of F# and Visual Studio 2008. By the time you read this column, you may have other options for using F#. In any case, the CTP version I used was extremely stable, and I installed it without any difficulty. The F# installation process places everything you need to write F# programs into Visual Studio.

I started by launching Visual Studio and then clicking File | New | Project. On the New Project dialog, I selected the Visual F# project type, then selected the F# Application template to create a command-line application. I named my project, "Project." The project name will become the name of the resulting executable (here, project.exe), so a more descriptive name, such as Request-Response-Harness, might have been preferable. After specifying a location for my F# project and clicking OK, I double-clicked on file Program.fs in the Visual Studio Solution Explorer window to open an editing window. The overall structure for my F# harness is listed in Figure 4.

Figure 4 F# Test Harness Structure

#light open System open System.Text open System.Net open System.IO open System.Web printfn "\nBegin F# HTTP request-response test of MiniCalc Web App\n"

let url = "https://localhost:15900/MiniCalc/Default.aspx" printfn "URL under test = %s \n" url // define function to get ViewState & EventValidation // set up test case data try // set numPass & numFail counters to 0 // iterate and process each test case // display number pass & number fail printfn "\nEnd F# test run" Console.ReadLine() |> ignore with | :? System.OverflowException as e -> printfn "Fatal overflow exception %s" e.Message Console.ReadLine() |> ignore | :? System.Exception as e -> printfn "Fatal: %s" e.Message Console.ReadLine() |> ignore // end source

The first line in my F# source code, #light, instructs the F# compiler to use lightweight syntax, where indentation and newlines in part determine program structure. This is standard practice for F# programs. I use the F# open keyword to bring the relevant .NET namespaces into scope, similar to using keyword in C# or the imports keyword in Visual Basic. Notice that because I am using light syntax, I do not use an explicit statement terminator such as the semicolon character used by C#, JavaScript, and other C-related languages. I open System.Text in order to easily use the Encoding class to convert text to bytes. The System.Net and System.IO namespaces house the key classes needed to programmatically post data to a Web application. The System.Web namespace is not visible by default to an F# application, so I explicitly added that namespace by right-clicking on the project name in Visual Studio and then selecting the Add Reference option. I open System.Web so that I can use the HttpUtility class to perform URL-encoding. My next instruction is straightforward:

printfn "\nBegin F# HTTP request-response test of MiniCalc Web App\n"

The printfn() library function displays information to the command shell as you'd expect. Strings in F# are delimited by double-quotes and can contain embedded escape sequences such as \n for a newline. One of the key characteristics of F# is that almost everything revolves around functions, and most functions return a value that must be explicitly dealt with. However, the printfn() function does not return a value. Next I set my target URL:

let url = "https://localhost:15900/MiniCalc/Default.aspx"

I use the let keyword and the = operator to bind a string value to an identifier named url. (Many members of the F# team prefer to use the term symbol rather than identifier.) Notice the port number in the URL string. In this case, I do not specify the data type for identifier url so the F# compiler will have to infer the type. I could have explicitly typed identifier url as a string:

let url : string = " . . . "

Instead of hard-coding the URL into the F# source, I could fetch it as a command-line argument using the built-in Sys.argv array:

let url = Sys.argv.[1]

Sys.argv.[0] is the program name, .[1] is the first argument, and so on. Next I echo the target URL:

printfn "URL under test = %s \n" url

The printfn() function uses C-language style formatting, where %s indicates a string. Other common specifiers include %d for integer, %x for hexadecimal, %.2f for floating point with two decimals, and a generic %A for all types. F# supports exception handling using a try-with construction. If an exception is thrown in the try block, control will be transferred to the with block where the exception will be handled. Notice that with light syntax I do not use curly braces to define begin and end points for a code block. Instead, all statements that are indented the same number of blank spaces (you cannot use tab characters) are part of the same code block. The last line in my try block is a call to ReadLine(), so that my harness will pause execution and hold the command shell on the screen:

Console.ReadLine() |> ignore

ReadLine() returns a string, and in F#, the return value must be accounted for or the compiler will generate an error. Here I use the |> pipe operator to send the return value to the built-in ignore object. As an alternative for discarding a return value in most situations, you can use the special _ (underscore) identifier like this:

let _ = Console.ReadLine()

However, for subtle F# syntax reasons, this approach does not work as the last statement of a code block. My exception handling code uses an interesting F# construction:

| :? System.OverflowException as e -> printfn "Fatal overflow exception %s" e.Message | :? System.Exception as e -> printfn "Fatal: %s" e.Message

You can interpret the first part of this code to mean, "If the exception matches type System.OverflowException, then assign the exception to an identifier named e, and then print a string message that includes the exception Message text." The | token is the match operator. In other words, if an exception is thrown, my program attempts to match the exception with two patterns. The :? operator tests for types rather than values.

Harness Details

Now that I've explained the overall structure of my F# test harness, let's look at the details. If you refer to Figure 4, you'll see that I define a function that fetches ViewState and EventValidation information from the MiniCalc application under test. In F#, you must define functions in source code before you call them. As I described earlier, because HTTP is a stateless protocol, ASP.NET uses a special ViewState value to maintain state. A ViewState value is a Base64-encoded string that represents the state of the ASP.NET Web application after every request-response round trip. An EventValidation value is somewhat similar to a ViewState value, but is used for security purposes to help prevent script-insertion attacks. As it turns out, if you want to programmatically post to an ASP.NET Web application, you must send the application's current ViewState value and current EventValidation value. The code for the function that fetches ViewState and EventValidation values is listed in Figure 5.

Figure 5 Function to Fetch ViewState and EventValidation

let getVSandEV (url : string) = let wc = new WebClient() let st = wc.OpenRead(url) let sr = new StreamReader(st) let res = sr.ReadToEnd() sr.Close() st.Close() let

startI = res.IndexOf("id=\"__VIEWSTATE\"", 0) + 24 let endI = res.IndexOf("\"", startI) let viewState = res.Substring(startI, endI - startI) let startI = res.IndexOf("id=\"__EVENTVALIDATION\"", 0) + 30 let endI = res.IndexOf("\"", startI) let eventValidation = res.Substring(startI, endI - startI) (viewState, eventValidation)

Notice that I use the let keyword and the = operator to define an F# function. I name my function getVSandEV and specify a single input parameter named url that has type string. My function signature does not explicitly indicate the return type, but I could have done so, as I'll explain in a moment. My function begins by instantiating a WebClient object:

let wc = new WebClient()

Because I placed an open statement to the System.Net namespace that houses the WebClient class, I do not need to fully qualify the class name. Next, I send a priming request to the target URL:

let st = wc.OpenRead(url) let sr = new StreamReader(st) let res = sr.ReadToEnd()

I use the OpenRead() method, combined with a StreamReader object and its ReadToEnd() method, to send a request to the target Web application, grab the entire HTML source of the response as a string, and bind that result to an identifier named res. In most cases, I suggest you explicitly type F# identifiers, such as let res : string = sr.ReadToEnd(), but in this situation, I let the F# compiler infer the type of identifier res. At this point, I need to parse out the ViewState and EventValidation values from the string value bound to the res identifier. The part of the string value bound to res that has the ViewState value resembles:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUK . . . Zmv==" />

First, I find the location of the beginning of the ViewState value using the String.IndexOf() method:

let startI = res.IndexOf("id=\"__VIEWSTATE\"", 0) + 24

The 0 argument means begin searching res at index position 0, which is the beginning of the response string. Notice that because my target string contains double-quote characters, I delimit the target string by using an \" escape sequence. Once I find where my target value begins, the actual ViewState value starts 24 characters from that index. Note that "—VIEWSTATE" has two leading underscore characters, not just one. This is a pretty crude way to parse for the initial ViewState value and makes my code brittle. However, in this situation, I am performing quick and easy test automation and I'm willing to accept the possibility that my code may break. Now I find the location within res where the ViewState value ends:

let endI = res.IndexOf("\"", startI)

I search for the first occurrence of any double-quote character after the beginning of the ViewState value I just found (at startI) and bind that location with identifier endI. Now that I know where the ViewState value begins and ends within the response string res, I can extract the value using the SubString method:

let viewState = res.Substring(startI, endI - startI)

The two arguments to the SubString() method are the index within res to begin extracting from, and the number of characters to extract (not the index to end extracting, which is a common mistake). As always with indexing methods, you've got to be very careful to avoid starting or ending one character too soon or too late. The identifier viewState now holds the initial ViewState value for the target ASP.NET Web application. Extracting the EventValidation value follows the same pattern:

let startI = res.IndexOf("id=\"__EVENTVALIDATION\"", 0) + 30 let endI = res.IndexOf("\"", startI) let eventValidation = res.Substring(startI, endI - startI)

Notice that I reuse my startI and endI identifiers and bind them to new values. This is legal in F# when the code is inside a method definition, as it is here, but not legal in top-level code outside of a method. At this point, I have my two values bound to identifiers ViewState and EventValidation. I use a neat F# feature to effectively return both values simultaneously:

(viewState, eventValidation)

I do not use an explicit return keyword; the last line of an F# function automatically represents the return value. Here I use parentheses to construct an F# tuple, which you can think of as a set of values. As I mentioned above, I could have defined my getVSandEV() function in a way that explicitly indicates the return value:

let getVSandEV (url : string) : (string * string) = . . .

The (string * string) notation means my function returns a tuple of two strings. Now that I've defined my helper function, I set up my test case data:

let testCases = [| "001,TextBox1=5&TextBox2=3&Operation=RadioButton1" + "&Button1=clicked,value=\"8.0000\",Add 5 and 3"

"002,TextBox1=5&TextBox2=3&Operation=RadioButton2" + "&Button1=clicked,value=\"15.0000\",Multiply 5 and 3" "003,TextBox1=0&TextBox2=0&Operation=RadioButton1" + "&Button1=clicked,value=\"0.0000\",Add 0 and 0" |]

Here I create an immutable F# array named testCases that contains three strings. The [| . . . |] notation is used by F# to delimit an array. Because my three strings are quite long, I break each string into two parts and use the + string concatenation operator to stitch them back together. F# also accepts the ^ character for string concatenation. The first part of the first string in array testCases, 001, is a test case ID. After a comma delimiter, I have TextBox1=5&TextBoxt2=3 that, when posted to the MiniCalc Web application, will simulate a user typing these values. The third name-value pair (Operation=RadioButton1) is a way to simulate a user selecting a RadioButton control item—in this case, the RadioButton that corresponds to addition. You might have incorrectly guessed (as I originally did) at something like RadioButton1=checked. However, RadioButton1 is a value of the Operation control, not a control itself. The fourth name-value pair (Button1=clicked) is somewhat misleading. I need to supply a value for Button1 to simulate that it has been clicked by a user, but any value will work. So, I could have used "Button1=yadda" or even just "Button1=" if I had wanted to. But "Button1=clicked" is more descriptive. The next field (value="8.0000") in my test case data is an expected value in the form of a string that I'll look for in the HTML response stream. The final field (Add 5 and 3) is a simple comment.

Using an F# array to store my test case data is perhaps the simplest approach, but there are several alternatives. I could have replaced the [| . . . |] delimiters with plain square brackets such as: let testCases = [ "001 . . ." ] to create an F# List. Lists are common and thematic with older functional programming languages such as LISP and Prolog, but in this situation, I'd gain no advantage by using a List. Alternatively, I could have created an F# mutable array like this:

let testCases = Array.create 3 "" testCases.[0] <- "001,. . ." testCases.[1] <- "002,. . ." testCases.[2] <- "003,. . ."

The use of mutable arrays is quite rare in F# and, again, I'd gain no advantage by using one here; I mention the possibility mostly to show you mutable array syntax. If you refer back to the overall test harness structure shown in Figure 3, you can see that I am now ready to start processing each test case. I begin by setting up counter identifiers to store the number of test cases that pass and fail:

let numPass = ref 0 let numFail = ref 0

In most cases, I'd simply bind these identifiers to 0, for example, "let numPass = 0". However, I need to initialize the counters outside a function and increment each counter inside the function (as we'll see shortly), but print the final values outside the function. This causes a minor problem, because of scope visibility issues. In awkward situations like this, one approach is to use the ref keyword as I've done here. I'll explain how to work with ref identifiers shortly. Now comes the trickiest syntax part of my F# test harness. I process each test case:

testCases |> Seq.iter(fun (testCase : string) -> // parse current test case // get ViewState and EventValidation // send HTTP request and get response // check

response for expected value // print pass or fail )

I use the |> pipe operator to send my testCases array into the Seq.iter() method, which will process each test case item in the array. Seq.iter() expects an argument that is a function that will be applied to every item in the sequence. I could write a function explicitly, but a thematic F# approach is to define an anonymous function on the fly using the "fun" keyword. My anonymous function accepts a single-string parameter named testCase, which will be bound to each item in turn in the testCases array. Here, the parameter testCase must be type string because each item in the array testCases is a string, so I could have omitted the explicit typing for testCase. Inside my anonymous function, I parse out the values in the current test case:

let delimits = [|',';'~'|]; let tokens = testCase.Split(delimits) let caseID = tokens.[0] let input = tokens.[1] let expected = tokens.[2] let comment = tokens.[3]

Notice I use the F# [| . . |] syntax with a semicolon delimiter to specify an array of characters as an argument for the String.Split() method. Next, I call the getVSandEV() function I defined earlier:

let (vs, ev) = getVSandEV url

Recall that getVSandEV() accepts a single-string argument and returns a tuple of two strings. I use parentheses to capture the tuple, deconstruct the tuple values, and bind them to identifiers vs and ev. Notice that in F#, I do not need to use parentheses when calling a program-defined function. Next, I build up my actual POST data:

let data = input + "&__VIEWSTATE=" + HttpUtility.UrlEncode(vs) + "&__EVENTVALIDATION=" + HttpUtility.UrlEncode(ev) let buffer = Encoding.ASCII.GetBytes(data)

I take the value bound to identifier input and concatenate the required ViewState and EventValidation values. Because ViewState and EventValidation values are Base64 encoded, they may contain characters that are not valid in an HTTP POST request (in particular, = padding characters), so I use the UrlEncode() method in the System.Web namespace to convert any such troublesome characters into an escape sequence such as %3D. I use the GetBytes() method to convert my string into an array of bytes. Now I can create an HTTP request object:

let req = WebRequest.Create(url) :?> HttpWebRequest req.Method <- "POST" req.ContentType <- "application/x-www-form-urlencoded" req.ContentLength <- int64


I instantiate an HttpWebRequest object using a factory mechanism of the WebRequest class. I must cast the return value of the Create() method, so I use the downcast :?> operator, which you can interpret to mean "and cast as type xxx". Because the properties of my .NET object are mutable, I use the <- operator to assign values of "POST", "application/x-www-form-urlencoded", and buffer.Length to the Method, ContentType, and ContentLength properties of the request object. Notice that to cast the Length property of identifier buffer as int64, I use syntax similar to C-based languages rather than using the :?> operator. In F#, you use C-style cast syntax with .NET value types, such as int64, and use :?> with reference types, such as HttpWebRequest. With the request object created, I can fire it off to the Web application under test:

let reqSt = req.GetRequestStream() reqSt.Write(buffer, 0, buffer.Length) reqSt.Flush() reqSt.Close()

The Write() method does not actually write its byte array argument, so I explicitly call the Flush() method to do so. Now I can fetch the HTTP response and bind it to an identifier named html:

let res = req.GetResponse() :?> HttpWebResponse let resSt = res.GetResponseStream() let sr = new StreamReader(resSt) let html = sr.ReadToEnd()

In F#, when you call a .NET method that accepts only a single argument, you can omit the parentheses in the call. However, .NET methods with no arguments, those with two or more arguments, and constructor calls all require parentheses. Therefore, the F# team suggests always using parentheses when calling .NET methods. Now I display my test case input:

printfn "============================" printfn "Test case: %s" caseID printfn "Comment : %s" comment printfn "Input : %s" input printfn "Expected : %s"


And then I examine the HTML response to look for the expected string:

if html.IndexOf(expected) >= 0 then printfn "Pass" numPass := !numPass + 1 else printfn " ** FAIL **" numFail := !numFail + 1

I use the String.IndexOf() method, which returns -1 if its string argument is not found, or the index location of the argument if the argument is found. Notice that in F#, if . . then syntax uses an explicit then keyword. Recall that I declared my two counter identifiers using the ref keyword. We've seen that F# uses = to bind an initial value to an identifier, and the <- operator to update a mutable identifier. This code shows that F# uses the := operator to change the value referenced by an identifier. Notice, too, that because I am working with ref objects, I use the ! operator to dereference and get the value associated with the ref object. After each test case has been processed, I pause for a key-press and then delay execution for 1 second:

let _ = Console.ReadLine() System.Threading.Thread.Sleep(1000) ) // end anonymous function

Here I use the common F# idiom to discard the return value of Console.ReadLine() by assigning the return to the special_identifier. Instead, I could have piped the return to "ignore", as I explained earlier. Notice that because I did not open namespace System.Threading, I must fully qualify my call to the Thread.Sleep() method. After all test case input in the array testCase has been processed by the anonymous function in Seq.iter(), control is transferred to the end of the F# harness and I can print summary results:

printfn "Number pass = %d" !numPass printfn "Number fail = %d" !numFail printfn "\nEnd F# test run"

Now my harness is ready to run. Once I make sure the Visual Studio development Web server is running, I can execute my harness from a command shell, as shown in Figure 2.

Wrap Up

The example F# harness I've presented here should give you a solid base to create a test harness that meets your own particular testing situation. In general, the most difficult problem you'll face is determining what data to post to your Web application under test. One good way to do this is to manually exercise your Web application and capture HTTP request data with a sniffer tool to see what information is being sent to the Web server that is hosting the application. There are many such tools available on the Internet as free downloads.

Let me address why you might want to investigate the F# language. After all, learning any new programming language requires a significant investment of time. Learning F# will require some effort, especially if you are new to functional programming. Here are four reasons I decided to learn F#. First, F# has gotten very good informal technical reviews from several of my colleagues whose opinions I respect. Second, it never hurts to add a new technology to your toolset and resume. Third, I believe that learning a new programming language helps you understand other languages better and use them more effectively. Fourth, I'm finding the F# language interesting and just plain enjoyable to learn.

Acknowledgement: My thanks to Tim Ng, a senior development lead on the F# team, who provided much technical advice for this article.

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's Redmond, Washington campus. He has worked on several Microsoft products including Internet Explorer and MSN Search. James is the author of .NET Test Automation Recipes(Apress, 2006). James can be reached at jmccaffrey@volt.comor v-jammc@microsoft.com.