Übersetzung vorschlagen
 
Andere Vorschläge:

progress indicator
Keine anderen Vorschläge
MSDN Magazin > Home > Ausgaben > 2009 > MSDN Magazin Juli 2009 >  Testlauf: Anforderung/Antwort-Tests mit F#
Inhalt anzeigen:  Englisch mit deutscher ÜbersetzungInhalt anzeigen: Englisch mit deutscher Übersetzung
Dies sind maschinell übersetzte Inhalte, die von den Mitgliedern der Community bearbeitet werden können. Sie können die Übersetzung verbessern, indem Sie auf den jeweils zum Satz gehörenden Link "Bearbeiten" klicken.Mithilfe des Dropdown-Steuerelements "Inhalt anzeigen" links oben auf der Seite können Sie zudem bestimmen, ob nur der englische Originaltext, nur die deutsche Übersetzung oder beides nebeneinander angezeigt werden.
Test Run
Request-Response Testing With F#
James McCaffrey
Code download available from the MSDN Code Gallery
Browse the Code Online
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 1 and 2. Figure 1 illustrates 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 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 2 shows an F# test harness in action.
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.
<%@ 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 3 for 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.
#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 = "http://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 = "http://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.
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 buffer.Length
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" expected 
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.com or v-jammc@microsoft.com.

Testlauf
Anforderung/Antwort-Tests mit F#
James McCaffrey
Herunterladen von Code von der MSDN Code Gallery verfügbar
Code online durchsuchen
Programmiersprache f# weist verschiedene Merkmale, die sich ideal für Software-Testautomatisierung. In der Kolumne dieses Monats zeige ich Ihnen Verwendung von f# zum Ausführen von HTTP-Anforderung-Antwort XML-Webdienste Webanwendungen testen. Insbesondere es erstellt ein kurze Testumgebung Programm, das ein Benutzer Ausübung simuliert eine XML-Webdienste Anwendung. F#-Testumgebung bucht programmgesteuert HTTP-Anforderung Informationen, die die Anwendung von einem Webserver. Anschließend ruft den HTTP-Antwortstream und untersucht den HTML-Text für einen erwarteten Wert Art um ein Pass-Fehler Ergebnis zu bestimmen. Eine nützliche testen Technik in eigenen rechts sondern, bietet lernen, wie HTTP-Anforderung-Antwort Testen mit f# führen eine hervorragende Möglichkeit, die Sprache F#-Informationen. Diese Spalte ausgegangen grundlegende Kenntnisse XML-Webdienste Technologie und intermediate .NET-Programmierung Fähigkeiten mit c# oder VB.NET,, aber nicht davon ausgegangen werden Sie Erfahrung mit der Sprache f# haben. Allerdings auch wenn Sie neue sind XML-Webdienste und Testautomatisierung im Allgemeinen Sie sollten möglicherweise trotzdem dieses Monats ohne viel Probleme folgen. Sehen, worauf ich hinaus will, dauern Sie eine aussehen AtFigures 1 und 2. Abbildung 1 veranschaulicht die Beispiel XML-Webdienste testende Webanwendung, die ich verwenden. Das System unter Test ist eine einfache, jedoch repräsentative Webanwendung, mit dem Namen „ MiniCalc.
Abbildung 1 XML-Webdienste Webanwendung unter Test
Ich halten absichtlich meine ASP.NET-Webanwendung unter Test so einfach wie möglich, dass ich die wichtigsten Punkte die Testautomatisierung verbergen nicht. Realistische Webanwendungen sind wesentlich komplexer als das dummy „ MiniCalc Anwendung in Abbildung 1, aber der f# Testtechniken hier einfach beschrieben für komplexe Anwendungen verallgemeinern. Die Webanwendung „ MiniCalc akzeptiert zwei Ganzzahlen und eine Anweisung hinzufügen oder multiplizieren. Danach sendet er die Werte auf einem Server, auf dem das Ergebnis berechnet. Der Server erstellt die HTML-Antwort und sendet es an den Clientbrowser zurückgesendet, wo das Ergebnis auf vier Dezimalstellen angezeigt. Abbildung 2 zeigt eine Testumgebung F#-Aktion.
Abbildung 2 Anforderungsantwort Testen mit f#
Meine Testumgebung f# heißt project.exe und akzeptiert keine Befehlszeilenargumente. Der Einfachheit halber habe ich hart codierte Informationen einschließlich der URL des der Webanwendung, Test Case Eingaben und Testfall erwarteten Ergebnisse. Wird erläutert, wie Sie die Testumgebung später parametrisieren können. Meine Testumgebung beginnt mit der Wiedergabe des Ziel-URL des localhost:15900/MiniCalc/Default.aspx. Beachten Sie, dass ich verwende die Visual Studio Development Server anstelle von IIS, sodass ich eine Anschlussnummer (15900), statt den IIS-Standardport 80 angeben. Im Testfall 001 bucht meiner Testumgebung f# programmgesteuert Informationen, die entspricht ein Benutzer 5 in TextBox1-Steuerelement eingeben, die eine 3 in TextBox2 RadioButton1 (an eine Addition) auswählen, und klicken auf Button1 (zu berechnen) eingeben. Hinter den Kulissen werden die Testumgebung erfasst die HTTP-Antwort von Server und sucht dann die Antwort auf ein Hinweis darauf, dass 8.0000 (das richtige Ergebnis 5 + 3) in das Steuerelement "TextBox3" Ergebnis ist. Die Testumgebung Titel Fällen die Anzahl der Tests, übergeben und die Zahl, die fehlschlagen (Dies ist eine erstaunlich interessante Operation in f#) und zeigt die Ergebnisse, nachdem alle Testfälle verarbeitet wurden.
In den folgenden Abschnitten dieses Artikels, beschreibe ich kurz die zu testende Webanwendung, damit Sie wissen genau was getestet wird. Als nächstes erläutert ich die Details der lightweight HTTP-Anforderung / Antwort-Automatisierung mithilfe der F#-Sprache erstellen. Ich umbrochen, bis durch beschreiben, wie Sie die Techniken ändern können habe ich an Ihre eigenen Anforderungen dargestellt. Ich stellen auch ein paar Meinungen über warum Sie die Zeit zum Untersuchen von f# ergreifen sollten. Ich denke, Sie die hier vorgestellte Informationen interessante finden können und eine hilfreich neben Ihrer Tests Toolset.

Die Anwendung unter Test
Betrachten wir nun den Code für die „ MiniCalc XML-Webdienste Webanwendung, die das Ziel der Testautomatisierung ist. Erstellte der Anwendung „ MiniCalc mithilfe XML-Webdienste integrierte Entwicklungswebserver nutzen. Nach dem Starten von XML-Webdienste, klicken auf Datei | neu | Website. Um zu vermeiden, dass die XML-Webdienste code - hinter Mechanismus und behalten alle den Code für meine Webanwendung in einer einzigen Datei die Option leere Website gewählten. Vermeiden Sie mithilfe von IIS, ausgewählte ich die Option File System aus Speicherort Feld Dropdown-Steuerelement. Ich entschieden, c# für die Anwendung „ MiniCalc verwenden, aber die ich in dieser Spalte vorstellen F#-Testumgebung arbeitet mit XML-Webdienste in VB.NET geschriebene Anwendungen. Darüber hinaus kann mit geringfügigen Änderungen der Testumgebung nicht .NET-Anwendungen Ziel, die mithilfe von Technologien wie z. B. klassischen ASP, CGI, PHP, JSP, Ruby und usw. geschrieben werden. Da ich möchten den XML-Webdienste Development Server ich angegebene einen standardmäßigen Speicherort im Dateisystem, z. B. C:\MyWebApps\MiniCalc anstelle von einem IIS-spezifischen Speicherort, z. B. C:\Inetpub\wwwroot\MiniCalc. Klicken OK im Dialogfeld Neue Website, um die Struktur der meine Webanwendung zu generieren. Als Nächstes wurde das Fenster Projektmappen-Explorer mit der rechten Maustaste auf den Projektnamen „ MiniCalc geklickt haben und neues Element hinzufügen aus dem Kontextmenü ausgewählt. Anschließend Webformular aus der Liste der installierten Vorlagen ausgewählt und akzeptiert den Namen der Datei default.aspx. Ich habe deaktiviert den "platzieren Sie Code in eigener Datei"Option und dann auf die Schaltfläche hinzufügen.
Als Nächstes doppelgeklickt ich auf den Dateinamen default.aspx im Projektmappen-Explorer, den Vorlage generierte Code zu bearbeiten. Ich den Vorlage Code gelöscht und durch den Code in Abbildung 3 ersetzt.
<%@ 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>
Um meine Quellcode klein und leicht verständlich zu halten, verwende ich nicht gute Programmierpraktiken in dieser Webanwendung. Insbesondere ich keine Fehlerüberprüfung und verwende einen Ansatz etwas planlosen Design durch Kombinieren von serverseitigen Steuerelemente (z. B. < Asp: TextBox >) mit einfachen (z. B. < Fieldset >). Die wichtigsten Teile der Codeauflistung im Abbildung 3 für Sie zu beachten sind die IDs der meine XML-Webdienste serverseitige Steuerelemente. Standard-IDs Label1 Verwendung (Eingabeaufforderung), TextBox1 und TextBox2 (Eingabe für zwei ganze Zahlen), RadioButton1 und RadioButton2 (Auswahl der Addition oder Multiplikation), Button1 (berechnen), und "TextBox3" (Ergebnis). Automatisierte HTTP-Anforderung-Antwort testen Durchführen einer XML-Webdienste Anwendung, Sie müssen die IDs der Steuerelemente der Anwendung kennen. In diesem Fall muss den Quellcode verfügbar da ich Erstellen der Anwendung selbst; binaber selbst wenn Sie eine Webanwendung testen, die Sie geschrieben haben, können Sie immer die Anwendung überprüfen, indem mithilfe des Webbrowsers Quelltext Funktionen.
Um zu überprüfen, dass meine zu testende Webanwendung ordnungsgemäß erstellt wurde, hit I < F5 >Schlüssel. Sie auf OK im resultierenden Dialogfeld Debuggen nicht aktiviert anweisen geklickt XML-Webdienste so ändern Sie die Webanwendung Web.config-Datei. XML-Webdienste Development Server gestartet und die Webanwendung eine zufällige Portnummer zugeordnet – in diesem Fall 15900, wie Sie sehen in Abbildung 1. Wenn ich eine Anschlussnummer angeben möchten hatte, ich konnte das Projekt „ MiniCalc im Projektmappen-Explorer aktiviert haben dann auf das Hauptfenster Menüelement anzeigen geklickt und die Option Eigenschaften ausgewählt. Dadurch würde eine "Verwenden dynamischer Ports" angezeigtOption standardmäßig auf True, und ich konnte ändern Sie den Wert auf False und geben einen Wert in die "Anschlussnummer"Feld.
Beachten Sie, dass das Aktion-Attribut des < Formular >Element ist default.aspx festgelegt. Mit anderen Worten, jedes Mal ein Benutzer eine Anforderung sendet, wird der gleiche default.aspx-Seitencode ausgeführt. Meine Webanwendung „ MiniCalc wird dadurch, das Verhalten einer einzelnen Anwendung anstatt einer Sequenz von anderen Webseiten. Da HTTP ein statusfreies Protokoll XML-Webdienste führt die Anwendung Auswirkungen mithilfe der Webanwendung Zustand in einem speziellen ausgeblendeten Werttyp, bezeichnet den ViewState beibehalten. Wie wir in Kürze sehen werden, ist der Umgang mit einer ASP.NET-Anwendung ViewState der Schlüssel zu Daten an die Anwendung programmgesteuert übermitteln.

XML-Webdienste Anforderungsantwort Testen mit f#
Nun, da wir die zu testende Webanwendung gesehen haben, wir sprechen Sie das F#-Testumgebung Testprogramm, das im Bildschirmfoto in Abbildung 2 erzeugt. F# ist mit Vorbehalt geplant, mit der nächsten Version ausgeliefert XML-Webdienste. Für diese Spalte ich beschloss, September 2008 verwenden Community Technical Preview (CTP)-Version f# und XML-Webdienste 2008. Die Zeit, die Sie diese Spalte lesen, können Sie andere Optionen für die Verwendung f# haben. In jedem Fall verwendet CTP-Version war äußerst stabil, und es alle problemlos installiert. F# während des Installationsvorgangs setzt alles, was Sie zum Schreiben von F#-Programmen in benötigen XML-Webdienste.
Ich begann, indem starten XML-Webdienste und dann auf Datei | neu | Projekt. Das Dialogfeld Neues Projekt ich Visual F#-Projekttyp ausgewählt und dann ausgewählten F# Application-Vorlage eine Befehlszeile-Anwendung erstellen. Mit der das Projekt "Projekt". Der Namen des Projekts wird der Name der daraus resultierende ausführbare Datei (hier, project.exe) so ein aussagekräftigeren Namen, z. B. Request-Response-Testumgebung vorzuziehen wurden möglicherweise. Nachdem Sie einen Pfad für meine F#-Projekt angeben, und klicken Sie auf OK, doppelgeklickt ich auf die Datei Program.fs in der Lösung XML-Webdienste Explorer-Fenster ein Bearbeitungsfenster geöffnet. Die allgemeine Struktur für meine F#-Testumgebung wird im Abbildung 4 aufgeführt.
#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 = "http://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
Die erste Zeile der F#-Quellcode, #light, weist den Compiler f# lightweight Syntax verwenden, in dem Einzug und Zeilenumbrüche in Teil Programmstruktur bestimmen. Dies ist standard Vorgehensweise für F#-Programme. Ich verwenden Sie das Öffnen F#-Schlüsselwort, um die relevanten Namespaces .NET in den Wirkungsbereich, ähnlich wie-Schlüsselwort in c# oder Imports-Schlüsselwort in Visual Basic zu bringen. Beachten Sie, da ich Licht Syntax verwendet bin, ich keine explizite Anweisung Abschlusszeichen z. B. das von c#, JavaScript und anderen Sprachen C-bezogenen verwendet Semikolon verwenden. Ich öffnen System.Text, um die Encoding-Klasse leicht zu verwenden, um Text in Bytes zu konvertieren. Die Namespaces System.NET und System.IO beinhalten die Schlüssel zum programmgesteuert zu einer Webanwendung Daten buchen benötigten Klassen. System.Web-Namespace ist nicht standardmäßig eine F#-Anwendung sichtbar, d. h. ich diesen Namespace explizit, hinzugefügt indem mit der rechten Maustaste auf den Namen des Projekts in Visual Studio auswählen und dann die Option Verweis hinzufügen. Ich öffnen System.Web, so dass ich die HttpUtility-Klasse verwenden können, URL-Codierung durchführen. Meine nächste Anweisung ist einfach:
printfn "\nBegin F# HTTP request-response test of MiniCalc Web App\n"
Die Funktion printfn() Bibliothek zeigt Informationen an die Befehlsshell erwartungsgemäß. Zeichenfolgen in f# durch doppelte Anführungszeichen getrennt sind und eingebettete Escapesequenzen wie z. B. \n für einen Zeilenvorschub enthalten. Einer der die wichtigsten Merkmale von f# ist, dass fast alles um Funktionen besteht, und die meisten Funktionen geben einen Wert, der explizit behandelt werden muss zurück. Die Funktion printfn() werden jedoch keinen Wert zurück. Als Nächstes legen Sie mein Ziel-URL:
let url = "http://localhost:15900/MiniCalc/Default.aspx"
Ich verwenden Sie das Let-Schlüsselwort und den =-Operator einen Zeichenfolgenwert an eine Kennung namens Url gebunden. (Viele Mitglieder des Teams f# bevorzugen Begriff Symbol anstelle Bezeichner zu verwenden.) Beachten Sie die Portnummer im URL-Zeichenfolge. In diesem Fall keinen ich an den Datentyp für Bezeichner Url damit der F#-Compiler den Typ abzuleiten. Ich konnte Url Bezeichner explizit als Zeichenfolge eingegeben haben:
let url : string = " . . . "
Anstelle von hart codierte URL in der F#-Quelle, konnte ich es als Befehlszeilenargument unter Verwendung des integrierten Sys.argv Arrays abrufen:
let url = Sys.argv.[1]
Sys.argv.[0] steht der Programmname. [1] ist das erste Argument und So weiter. Als Nächstes echo ich den Ziel-URL:
printfn "URL under test = %s \n" url
Die printfn()-Funktion verwendet c-Formatvorlage, wobei "% s" eine Zeichenfolge angibt. Andere allgemeine Bezeichner umfassen %d für ganze Zahl, %x für hexadezimal, %.2f für Gleitkomma mit zwei Dezimalstellen und eine generische %A für alle Typen. F#-unterstützt Ausnahmebehandlung mit eine Konstruktion versuchen Sie es mit. Wenn eine im Try-Block Ausnahme Steuerelement werden übertragen die mit Block, in dem die Ausnahme behandelt wird. Beachten Sie, dass mit hellen Syntax, die ich nicht verwenden geschweifte Klammern zu definieren, beginnen und Endpunkt für einen Code blockieren. Stattdessen alle Anweisungen, sind eingerückt die gleiche Anzahl von Leerzeichen (Sie können keine Tabstoppzeichen verwenden) sind Teil der gleichen Codeblock. Die letzte Zeile in meiner Try-Block ist ein Aufruf ReadLine(), sodass meiner Testumgebung Ausführung anhalten und halten die Eingabeaufforderung auf dem Bildschirm wird:
Console.ReadLine() |> ignore 
ReadLine() gibt eine Zeichenfolge zurück, und in f# muss für der Rückgabewert berücksichtigt werden, oder der Compiler generiert einen Fehler. Hier verwendet das | >Verkettungszeichen integrierte ignorieren-Objekt den Rückgabewert an. Als Alternative für die Ablehnung eines Rückgabewert in den meisten Fällen können Sie den Bezeichner Sonderzeichen _ (Unterstrich) wie folgt verwenden:
let _ = Console.ReadLine()
Jedoch Subtile F#-Syntax Gründen funktioniert dieser Ansatz wie die letzte Anweisung einen Codeblock nicht. Mein Code zur Ausnahmebehandlung verwendet eine interessante F#-Konstruktion:
| :? System.OverflowException as e ->
    printfn "Fatal overflow exception %s" e.Message
| :? System.Exception as e ->
    printfn "Fatal: %s" e.Message
Sie können den ersten Teil dieser Code bedeutet, "Wenn die Ausnahme entspricht Typ System.OverflowException, dann eine Kennung namens e die Ausnahme zuweisen und drucken Sie dann eine Zeichenfolgennachricht, die die Ausnahme Nachrichtentext enthält." interpretieren Die |-Token ist der Operator Übereinstimmung. Mit anderen Worten, wenn eine Ausnahme ausgelöst wird, versucht das Programm die Ausnahme mit zwei Mustern übereinstimmen. Der:? Operator-Tests für Werte, sondern Typen.

Testumgebung-Details
Nun, dass ich die allgemeine Struktur Meine F#-Testumgebung erläutert haben, sehen wir uns die Details. Wenn Sie auf Abbildung 4 verweisen, sehen Sie sich, dass ich eine Funktion definieren, die von der Anwendung „ MiniCalc testende ViewState und EventValidation Informationen abruft. In f# müssen Sie Funktionen im Quellcode definieren, bevor Sie diese aufrufen. Wie zuvor beschrieben ist HTTP ein statusfreies Protokoll XML-Webdienste verwendet einen speziellen ViewState Wert Status beibehalten. Ein ViewState-Wert ist eine Base64-codierte Zeichenfolge, die den Zustand der XML-Webdienste-Webanwendung nach jeder Anforderung-Antwort Roundtrip darstellt. Ein EventValidation-Wert ist ein ViewState Wert vergleichbar, aber wird aus Gründen der Sicherheit verwendet, um Angriffe Skript einfügen. Wie,, aktiviert Falls Sie programmgesteuert eine Webanwendung XML-Webdienste buchen möchten, müssen Sie die Anwendung aktuellen ViewState Wert und aktuellen EventValidation Wert senden. Der Code für die Funktion, die ViewState und EventValidation Werte abruft ist im Abbildung 5 aufgeführt.
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)
Beachten Sie, dass ich das Let-Schlüsselwort verwenden und den =-Operator eine F#-Funktion definieren. Ich meine Funktion GetVSandEV name und geben Sie einen einzigen Eingabeparameter mit dem Namen Url, der Typ String hat. Meine Funktionssignatur nicht explizit angegeben den Rückgabetyp, aber ich konnte haben geschehen ist, wie ich in Kürze erläutern werde. Meine Funktion beginnt durch Instanziieren eines WebClient-Objekts:
let wc = new WebClient()
Da ich eine offene System.NET-Namespace-Anweisung, die die WebClient-Klasse enthält gespeichert, muss ich den Namen der Klasse vollständig qualifizieren. Als Nächstes senden ich eine Initialisierung-Anforderung an den Ziel-URL:
let st = wc.OpenRead(url)
let sr = new StreamReader(st)
let res = sr.ReadToEnd()
Ich verwenden Sie die OpenRead()-Methode, kombiniert mit einem StreamReader-Objekt und dessen ReadToEnd()-Methode, eine Anforderung an die Webanwendung Ziel senden, nehmen Sie die gesamte HTML-Quelle der Antwort als Zeichenfolge und binden das Ergebnis auf einen Bezeichner mit dem Namen Res. In den meisten Fällen empfehle ich Sie explizit F#-Bezeichner, z. B. Let Res eingeben: Zeichenfolge = sr.ReadToEnd(), aber in dieser Situation ich den F#-Compiler den Typ der Bezeichner Res ableiten lassen. Zu diesem Zeitpunkt müssen die ViewState und EventValidation Werte aus dem String-Wert gebunden, auf die Res-ID analysiert. Der Teil der Zeichenfolgenwert an Auflösungen gebunden, der dem ViewState-Wert ähnelt:
<input type="hidden" name="__VIEWSTATE"
 id="__VIEWSTATE" value="/wEPDwUK . . . Zmv==" />
Zuerst suchen Sie den Speicherort des Anfangs des ViewState Wertes mithilfe der String.IndexOf()-Methode:
let startI = res.IndexOf("id=\"__VIEWSTATE\"", 0) + 24 
Das Argument bedeutet, dass mit der Suche Res am Index 0 position 0, womit der Anfang des Antwortzeichenfolge ist. Beachten Sie, dass da Meine Zielzeichenfolge doppelten Anführungszeichen enthält, ich die Zielzeichenfolge begrenzen mithilfe einer \"Escape-Sequenz. Nachdem ich mein Zielwert, wo beginnt finde, der tatsächliche Wert der ViewState beginnt 24 Zeichen aus, dass Index. Beachten Sie, dass "– VIEWSTATE"hat zwei führenden Unterstrich-Zeichen, nicht nur eine. Dadurch ist eine ziemlich einfachen Möglichkeit, für den Anfangswert des ViewState analysieren und mein Code zerbrechliche. Jedoch in dieser Situation bin dem Durchführen von schnellen und einfachen Testautomatisierung, und ich bin bereit, die Möglichkeit zu akzeptieren, die mein Code beschädigen kann. Jetzt finde ich die Position im Res, wo der ViewState Wert endet:
let endI = res.IndexOf("\"", startI)
Ich Suche beim ersten Vorkommen eines beliebigen Zeichens doppelte Anführungszeichen nach dem Anfang der ViewState Wert, den ich einfach gefunden (StartI) und binden die Position mit ID EndI. Nun, ich weiß, wo der ViewState Wert beginnt und endet in der Antwort Zeichenfolge Res, kann ich den Wert mithilfe der SubString-Methode zu extrahieren:
let viewState = res.Substring(startI, endI - startI)
Die zwei Argumente an die Methode SubString() sind der Index in Res zu extrahieren und die Anzahl der Zeichen (nicht den Index zum Extrahieren, beenden) zu extrahieren ist ein häufiger Fehler. Dank der Indizierung Methoden habe Sie wie immer vorsichtig damit gestartet oder ein Zeichen zu früh beendet oder zu spät sein. Der Bezeichner ViewState enthält jetzt den Anfangswert ViewState für das Ziel ASP.NET-Webanwendung. Extrahieren den Wert EventValidation folgt demselben Muster:
let startI = res.IndexOf("id=\"__EVENTVALIDATION\"", 0) + 30 
let endI = res.IndexOf("\"", startI)
let eventValidation = res.Substring(startI, endI - startI)
Beachten Sie, dass ich meine Bezeichner StartI und EndI wiederverwenden und Sie neue Werte binden. Dies ist in f# wird der Code innerhalb einer Methodendefinition, wie es hier ist zulässig, aber in der obersten Ebene Code außerhalb von einer Methode nicht zulässig. An diesem Punkt haben ich meine zwei Werte gebunden Bezeichner ViewState und EventValidation. Verwendung eine praktische F#-Funktion zum effektiv gleichzeitig beide Rückgabewerte:
(viewState, eventValidation)
Ich verwende ein Rückgabewert explicit-Schlüsselwort; nichtdie letzte Zeile eine F#-Funktion stellt automatisch den Rückgabewert dar. Hier verwende ich Klammern, um eine F#-Tupel, erstellen, die Sie als eine Reihe von Werten vorstellen können. Wie bereits erwähnt, konnte ich meine getVSandEV()-Funktion in einer Weise definiert habe, die den Rückgabewert explizit angibt:
let getVSandEV (url : string) : (string * string) = . . .
Die (Zeichenfolge * Zeichenfolge) Notation bedeutet, dass meine Funktion gibt ein Tupel von zwei Zeichenfolgen zurück. Nun, dass ich meine Hilfsfunktion definiert haben, Einrichten ich meinen Testfalldaten:
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"
  |]
Hier erstellen Sie ein unveränderlich f#-Array mit dem Namen TestCases, die drei Zeichenfolgen enthält. Der [|. . . |]-Notation wird von f# verwendet, um ein Array zu trennen. Da meine drei Zeichenfolgen ziemlich lang sind, ich jede Zeichenfolge in zwei Teile aufteilen und verwenden Sie die + Zeichenfolge Verkettungsoperator zum Sie erneut Zusammenfügen. F# akzeptiert auch die ^ Zeichen für die Zeichenfolgenverkettung. Der erste Teil der ersten Zeichenfolge im Array TestCases, 001, ist eine Test Case. Nach einem Kommatrennzeichen TextBox1 habe = 5 & TextBoxt2 = 3, wenn die Webanwendung „ MiniCalc gebucht, wird einen Benutzer diese Werte eingeben simuliert. Dritte Name / Wert-Paar (Operation = RadioButton1) ist eine Möglichkeit zum simulieren, ein Benutzer ein RadioButton-Steuerelement Element auswählt, in diesem Fall das Optionsfeld, das zusätzlich entspricht. Sie die falsch geraten haben möglicherweise bei etwa RadioButton1 (wie ursprünglich) = aktiviert. RadioButton1 ist jedoch ein Wert des Steuerelements Vorgang kein Steuerelement selbst. Das vierte Name / Wert-Paar (Button1 = geklickt) ist etwas irreführend. Geben einen Wert für Button1 So simulieren Sie hat ein Benutzer geklickt wurde, aber jeder beliebige Wert, der funktionieren müssen. So, I could have used "Button1=yadda" or even just "Button1="Wenn ich hatte. Aber „ Button1 = geklickt "ist aussagekräftigere. Das nächste Feld (Wert = "8.0000") in meinen Testfall Daten sind ein erwarteter Wert in Form einer Zeichenfolge, die ich in den Antwortstream HTML gesucht wird. Final-Feld (Add 5 und 3) ist eine einfache Bemerkung.
Mit Array f# meinen Testfall Datenspeicher ist vielleicht das einfachste Verfahren, aber es sind mehrere Alternativen. Ich könnte ersetzt haben das [|. . . |] Trennzeichen mit einfachen eckigen Klammern, z. B.: Lassen Sie TestCases = ["001. . ." eine F#-Liste erstellen. Listen sind, allgemeine und thematischen mit älteren funktionale Programmiersprachen wie z. B. LISP und Prolog, aber in dieser Situation würde ich keinen Vorteil erhalten, indem eine Liste. Alternativ konnte ich f# änderbaren Array wie folgt erstellt haben:
let testCases = Array.create 3 ""
testCases.[0] <- "001,. . ."
testCases.[1] <- "002,. . ."
testCases.[2] <- "003,. . ."
Die Verwendung von veränderlichen Arrays ist ziemlich selten in f#, und erneut, ich möchte erhalten keinen Vorteil, indem eine hier;Die Möglichkeit, Sie änderbaren Arraysyntax anzeigen größtenteils erwähnt. Wenn Sie wieder auf die allgemeine Test Harness Struktur, die in Abbildung 3 dargestellt verweisen, können Sie sehen ich bin jetzt bereit, jeden Testfall Verarbeitung zu starten. Sie zunächst einrichten Leistungsindikator Bezeichner für die Anzahl der Testfälle speichern, die übergeben und fehlschlagen:
let numPass = ref 0
let numFail = ref 0
In den meisten Fällen ich möchte einfach binden diese Bezeichner an 0, z. B. "NumPass lassen = 0. Ich muss jedoch Initialisieren der Leistungsindikatoren außerhalb einer Funktion und erhöht jeder Leistungsindikator innerhalb der Funktion (wir in Kürze sehen werden), jedoch die endgültigen Werte außerhalb der Funktion drucken. Dadurch wird ein kleine Problem, aufgrund des Umfangs Sichtbarkeit Probleme. In ungeeigneter Situationen wie folgt wird eine das Ref-Schlüsselwort verwenden, wie ich hier getan haben. Ich werde zum Arbeiten mit Ref-IDs in Kürze eingehen. Jetzt kommt den schwierigste Syntax Teil meiner Testumgebung f#. Ich jeden Testfall zu verarbeiten:
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
  )
Verwendung der | >Verkettungszeichen Meine TestCases-Array an die Seq.iter()-Methode, zu senden, jeden Testfall Element im Array verarbeitet. Seq.ITER() erwartet ein Argument, die eine Funktion, die auf jedes Element in der Reihenfolge angewendet werden. Explizit schreiben mit einer Funktion ist konnte, aber ein thematischen F#-Ansatz ist, eine anonyme Funktion im Handumdrehen mit "Fun" definierenSchlüsselwort. Meine anonyme Funktion nimmt einen einzelnen Zeichenfolgenparameter mit dem Namen TestCase, die für jedes Element im Array TestCases wiederum gebunden wird. Hier muss Parameter TestCase Typzeichenfolge sein, da jedes Element im Array TestCases eine Zeichenfolge ist, damit ich die explizite Eingabe für TestCase weggelassen haben könnte. Analysieren innerhalb meiner Funktion anonyme ich die Werte im aktuellen Testfall:
let delimits = [|',';'~'|];
let tokens = testCase.Split(delimits)
let caseID = tokens.[0]
let input = tokens.[1]
let expected = tokens.[2]
let comment = tokens.[3]
Verwendung der F#-Hinweis [|. . |] die Syntax durch ein Semikolon-Trennzeichen um ein Array von Zeichen als ein Argument für die String.Split()-Methode anzugeben. Als Nächstes rufen Sie die getVSandEV()-Funktion auf zuvor definiert:
let (vs, ev) = getVSandEV url 
Denken Sie daran, dass getVSandEV() akzeptiert ein einzelnes Zeichenfolgenargument und gibt ein Tupel von zwei Zeichenfolgen zurück. Ich verwende Klammern, um das Tupel erfassen, dekonstruieren Tupel Werte und an Bezeichner Vs und ev binden. Beachten Sie, dass in f# wird, müssen keine Klammern verwenden, wenn eine Anwendung benutzerdefinierte Funktion aufrufen. Als Nächstes erstelle ich meine aktuelle POST-Daten:
let data = input +
 "&__VIEWSTATE=" + HttpUtility.UrlEncode(vs) +
 "&__EVENTVALIDATION=" + HttpUtility.UrlEncode(ev)
let buffer = Encoding.ASCII.GetBytes(data)
Ich nehmen den Wert an Bezeichner Eingabe gebunden und verketten die erforderlichen Werte ViewState und EventValidation. Da ViewState und EventValidation Werte Base64-codiert sind, enthält Sie möglicherweise Zeichen, die nicht in einer HTTP POST-Anforderung gültig sind (insbesondere, = Füllzeichen), so dass ich die UrlEncode()-Methode in den System.Web-Namespace, verwenden um solche problematischer Zeichen in eine Escape-Sequenz z. B. % konvertieren 3D. Verwenden Sie die GetBytes()-Methode, um meine Zeichenfolge in ein Array von Bytes zu konvertieren. Jetzt kann ich ein HTTP-Anforderung-Objekt erstellen:
let req = WebRequest.Create(url) :?> HttpWebRequest
req.Method <- "POST"
req.ContentType <- "application/x-www-form-urlencoded"
req.ContentLength <- int64 buffer.Length
Ein mit einem Factory-Mechanismus der Klasse WebRequest HttpWebRequest-Objekt instanziiert. Ich muss den Rückgabewert der Methode Create() umgewandelt, so dass ich die Downcasting verwenden:? >Operator interpretierbare bedeuten "und als Typ Xxx umgewandelt". Da Eigenschaften des Objekts meine .NET änderbaren sind, die ich verwenden die <-Operator, um Werte für "POST", "Application/X-Www-Form-Urlencoded" und Buffer.Length um die Methode, ContentType und ContentLength Eigenschaften des Anforderungsobjekts zuzuweisen. Beachten Sie die Umwandlung die Length-Eigenschaft des Puffers als int64 Bezeichner Syntax verwenden, anstatt ähnelt C-basierte Sprachen Verwendung der:? >Operator. In f# Sie c-Typumwandlung Syntax bei .NET Werttypen wie z. B. int64, und verwenden:? >mit Verweistypen wie HttpWebRequest. Mit dem Anforderungsobjekt erstellt kann ich es für die Webanwendung unter Test ausgelöst deaktivieren:
let reqSt = req.GetRequestStream()
reqSt.Write(buffer, 0, buffer.Length)
reqSt.Flush()
reqSt.Close()
Die Write()-Methode wird nicht das Argument Byte Array geschrieben, damit ich die Flush()-Methode dazu explizit aufrufen wird. Ich kann jetzt abrufen die HTTP-Antwort und an eine Kennung namens html binden:
let res = req.GetResponse() :?> HttpWebResponse
let resSt = res.GetResponseStream()
let sr = new StreamReader(resSt)
let html = sr.ReadToEnd()
Wenn Sie eine .NET-Methode aufrufen, die nur ein einzelnes Argument akzeptiert, können Sie in f# die Klammern im Aufruf weglassen. Allerdings ist .NET Methoden mit keine Argumente, mit zwei oder mehrere Argumente und Konstruktor ruft alle Klammern erforderlich. Daher schlägt das F#-Team stets Klammern verwenden, beim Aufrufen von .NET Methoden. Jetzt anzeigen ich meine Eingabe Testfall:
printfn "============================"
printfn "Test case: %s" caseID
printfn "Comment  : %s" comment
printfn "Input    : %s" input
printfn "Expected : %s" expected 
Und ich untersuchen Sie anschließend die HTML-Antwort auf die erwartete Zeichenfolge suchen:
if html.IndexOf(expected) >= 0 then
  printfn "Pass"
  numPass := !numPass + 1
else
  printfn " ** FAIL **"
  numFail := !numFail + 1
Ich verwenden Sie die String.IndexOf()-Methode, die-1 Wenn Zeichenfolgenargument nicht gefunden wird oder die Indexposition des Arguments zurückgibt, wenn das Argument gefunden wird. Beachten Sie, die in f#, wenn. . dann Syntax verwendet eine explizite dann Schlüsselwort. Denken Sie daran, dass ich meine zwei Zähler Bezeichner das Ref-Schlüsselwort deklariert. Haben gesehen, dass f# verwendet =, um einen Anfangswert an einen Bezeichner zu binden und die <-Operator, um einen änderbaren Bezeichner zu aktualisieren. Dieser Code zeigt, dass F#-verwendet das: =-Operator, den Wert ein Bezeichner verweist zu ändern. Beachten Sie auch, dass da ich mit Ref-Objekten arbeite, ich verwende den! Operator dereferenzieren und das Ref-Objekt zugeordneten Wert abgerufen. Nachdem jeder Test Case verarbeitet hat, können Sie Drücken einer Taste pause und Ausführung für 1 Sekunde zu verzögern:
let _ = Console.ReadLine()
System.Threading.Thread.Sleep(1000)
) // end anonymous function
Hier verwende ich häufig f# Ausdrucksweise, um den Rückgabewert der Console.ReadLine() verwerfen, indem die Rückgabe an die Special_identifier zuweisen. Stattdessen konnte ich die Rückgabe an "Ignorieren" geleitet wurden wie ich bereits erwähnt. Beachten Sie, da ich System.Threading-Namespace nicht geöffnet wurde, ich vollständig Meine Aufruf Thread.Sleep() Methode qualifiziert müssen. Nachdem alle Test Case-Eingaben in Array-TestCase von der anonymen Funktion in Seq.iter() verarbeitet wurde, wird die Steuerung an das Ende der F#-Testumgebung und Ergebnisse drucken können:
printfn "Number pass = %d" !numPass
printfn "Number fail = %d" !numFail
printfn "\nEnd F# test run"
Jetzt ist meine Testumgebung zum Ausführen bereit. Nachdem ich sicherstellen, dass Visual Studio Development Server ausgeführt wird, kann ich meiner Testumgebung aus einer Befehlsshell ausführen, wie in Abbildung 2 dargestellt.

Schlussbemerkung
Die hier vorgestellten Beispiel F#-Testumgebung sollte Ihnen eine solide Basis um eine Testumgebung zu erstellen, die Ihre eigenen Tests Situation entspricht. Im Allgemeinen wird das schwierigste Problem, das Sie konfrontiert werden, ermittelt, welche Daten auf Ihre Webanwendung unter buchen testen. Eine gute Möglichkeit dazu besteht darin, manuell Übung Ihrer Webanwendung und Erfassen von Daten des HTTP-Anforderung mit einem Sniffer Tool, um anzuzeigen, welche Informationen an den Webserver gesendet wird, die die Anwendung hostet. Stehen viele Tools im Internet als kostenlose Downloads.
Lassen Sie mich Adresse warum Sie möglicherweise die Sprache f# untersuchen. Jeder neue Programmiersprache erlernen sind schließlich eine erhebliche Investitionen Zeit erforderlich. Lernen f# benötigen einige Aufwand, besonders wenn Sie mit funktionalen Programmierung vertraut sind. Hier sind vier Gründe, die ich beschloss, f# erfahren. Zunächst hat f# sehr gute informelle technische Rezensionen aus mehreren Meine Kollegen gelangt, deren Meinungen ich berücksichtigen. Zweitens beeinträchtigt Sie nie Ihren Toolset und fortsetzen eine neue Technologie hinzuzufügen. Drittens glauben ich, dass eine neue Programmiersprache erlernen Ihnen hilft, andere Sprachen besser verstehen und effizienteren Verwendung. Vierte, bin ich die Sprache f# interessantes und einfach angenehmer, um zu erfahren suchen.
Bestätigung: Mein Dank an Tim Ng, senior Development lead im F#-Team, viel Technische Hinweise für diesen Artikel geliefert hat.

Senden Sie Ihre Fragen und Kommentare für James in testrun@microsoft.com.

Dr.. James McCaffrey arbeitet für Volt Information Sciences Inc. und organisiert technische Schulungen für Softwareentwickler von Microsofts Redmond, Washington Campus. Er hat an verschiedenen Microsoft-Produkten mitgearbeitet, unter anderem Internet Explorer und MSN Search. James McCaffrey ist Autor von .NET Test Automation Recipes (Apress, 2006). James McCaffrey kann unter jmccaffrey@volt.com oder v-jammc@microsoft.com kontaktiert werden.

Page view tracker