Suggerisci traduzione
 
Altri utenti hanno suggerito:

progress indicator
Nessun altro suggerimento.
MSDN Magazine > Home > Tutti i numeri > 2009 > MSDN Magazine Giugno 2009 >  Modulo .NET test con IronPython
Visualizza contenuto:  affiancatoVisualizza contenuto: affiancato
Questo contenuto è stato tradotto mediante un sistema automatico e può essere modificato dai membri della community. Microsoft invita i membri a contribuire al miglioramento della traduzione facendo clic sul collegamento "Modifica" associato a ciascuna delle frasi seguenti.
Test Run
.NET Module Testing with IronPython
James McCaffrey
Code download available from the MSDN Code Gallery
Browse the Code Online
The Python scripting language has features that make it an excellent choice for performing several types of software testing. There are several implementations of Python available including CPython, the most common implementation on machines running UNIX-like operating systems, and IronPython, an implementation released in late 2006 that targets the Microsoft .NET Framework. In this month's column I'll demonstrate how to use IronPython to test .NET-based modules from both the Python command line and in lightweight Python scripts. I assume you have some experience with a scripting language such as JavaScript, Windows PowerShell, VBScript, Perl, PHP, or Ruby, but I do not assume you have any experience with Python. Take a look at Figure 1 to get an idea of where I'm headed. I am using IronPython to perform a quick, ad hoc test of a method that compares two card hands in a .NET module named TwoCardPokerLib. I describe the commands shown in Figure 1 in detail later in this column, but for now observe that I interactively add a reference to a DLL that houses my .NET module, instantiate two Hand objects from that module, and call a Compare method to determine if one hand beats the other hand. Now take a quick look at Figure 2. That screenshot shows the execution of a lightweight IronPython script that took me only minutes to create. Again, I go over all the details later, but you can see that I perform classic module testing by reading test case data from a text file, instantiate two Hand objects from the TwoCardPokerLib module, call the Compare method, and then compare actual results with expected results to determine test case pass/fail results.
>>> import sys
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib']
>>> sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\  Debug')
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib', 'C:\\ModuleTestingWithPython\\  TwoCardPokerLib\\bin\\Debug']
>>>
>>> import clr
>>> dir()
['_', '__builtins__', '__doc__', '__name__', 'clr', 'site', 'sys']
>>>
>>> clr.AddReferenceToFile("TwoCardPokerLib.dll")
>>> from TwoCardPokerLib import *
>>> dir()
['Card', 'Hand', '_', '__builtins__', '__doc__', '__name__', 'clr',   'site', 'sys']
>>>
>>> c1 = Card()
>>> c2 = Card("9d")
>>> print c1,c2
As 9d
>>>
>>> h1 = Hand(c1,c2)
>>> h2 = Hand("Ah","8c")
>>> expected = 1
>>> actual = h1.Compare(h2)
>>>
>>> if actual == expected: print "Pass\n",
... else: print "Fail\n"
...
Pass
>>>
>>> ^Z

C:\IronPython>
Figure 2 Test Automation with an IronPython Script
In the sections that follow, I'll describe the TwoCardPokerLib class library under test so you'll understand exactly what is being tested. Then I'll discuss the interactive IronPython commands I used in Figure 1. Next I present and explain in detail the short Python script that produced the output in Figure 2. The complete source code for the test harness and the library under test are in the download that accompanies this column.
I'll wrap up with a brief discussion of how you can adapt and extend the ideas presented here to meet your own testing needs. I'm confident you'll find that the Python testing techniques presented here will be a great addition to your software testing skill set.

The Module Under Test
Now let's look at the library under test. I decided to create a .NET class library for testing that has enough functionality to reveal the types of issues you face when performing real module testing in a production environment, but not so much complexity that the details of the library obscure the testing issues. I imagined a hypothetical game of two-card poker where each player receives just two cards from four, standard, 52-card decks. This naturally leads to an object-oriented design with a Card class, a Hand class, and a Compare method to determine which of two Hand objects is better. I decided that each two-card hand would be categorized as a Straight Flush (consecutive cards of the same suit), a Pair (two cards with the same rank), a Flush (two cards of the same suit), a Straight (two consecutive cards), and then Ace High down to Four High. Note that a Three High hand is not possible because Three-Two is a Straight and Three-Ace is an Ace High. Even such a simple example is rather interesting to implement. IronPython can be used to effectively test .NET libraries regardless of the implementation language, and IronPython can be used to test classic COM libraries. The source code for the Card class in my TwoCardPokerLib library is listed in Figure 3.
public class Card
{
  private string rank;
  private string suit;
  public Card() {
    this.rank = "A"; // A,2, . . ,9,T,J,Q,K
    this.suit = "s"; // c,d,h,s
  }
  public Card(string s) {
    this.rank = s[0].ToString();
    this.suit = s[1].ToString();
  }
  public override string ToString() {
    return this.rank + this.suit;
  }
  public string Rank {
    get { return this.rank; }
  }
  public string Suit {
    get { return this.suit; }
  }
  public static bool Beats(Card c1, Card c2) {
    if (c1.rank == "A") {
      if (c2.rank != "A") return true;
      else return false;
    }
    else if (c1.rank == "K") {
      if (c2.rank == "A" || c2.rank == "K") return false;
      else return true;
    }
    else if (c1.rank == "Q") {
      if (c2.rank == "A" || c2.rank == "K" || c2.rank == "Q") return false;
      else return true;
    }
    else if (c1.rank == "J") {
      if (c2.rank == "A" || c2.rank == "K" || c2.rank == "Q" || c2.rank == "J")
        return false;
      else
        return true;
    }
    else if (c1.rank == "T") {
      if (c2.rank == "A" || c2.rank == "K" || c2.rank == "Q" ||
        c2.rank == "J" || c2.rank == "T")
        return false;
      else return true;
    }
    else { // c1.rank is 2, 3, . . 9
      int c1Rank = int.Parse(c1.rank);
      int c2Rank = int.Parse(c2.rank);
      return c1Rank > c2Rank;
    }
  } // Beats()

  public static bool Ties(Card c1, Card c2) {
    return (c1.rank == c2.rank);
  }
} // class Card
In order to keep my code short and the main ideas clear, I have taken some shortcuts you wouldn't take in a production environment, such as omitting all error checks. My Card class was intentionally designed to illustrate many of the common features of a typical .NET module. Notice there is a default Card constructor, which creates an Ace of spades card object. There is a constructor that accepts a string such as Td to create an object that represents a ten of diamonds card. I implement get properties to expose the rank and suit of a Card object. And I implement static Beats and Ties methods that can be used to determine if one Card object beats or ties another Card object. My Hand class is too long to present in its entirety so I'll just describe the key parts of the class. The Hand class has just two member fields:
public class Hand {
  private Card card1;
  private Card card2;
  // constructors, methods, etc.
}
I make the important assumption that card1 is the higher of (or possibly equal to) the two Card objects. This greatly simplifies the logic of my Hand.Compare method. My Hand class has multiple constructors, a typical situation you must take into account when performing module testing. The default Hand constructor creates a hand with two ace–of-spades cards:
public Hand() {
  this.card1 = new Card(); 
  this.card2 = new Card();
}
I implement two other Hand constructors that allow me to either pass in two Card objects or two strings:
public Hand(Card c1, Card c2) {
  this.card1 = new Card(c1.Rank + c1.Suit);
  this.card2 = new Card(c2.Rank + c2.Suit);
}
public Hand(string s1, string s2) {
  this.card1 = new Card(s1);
  this.card2 = new Card(s2);
}
In Figure 4 I implement four private helper methods. As you'll see, private methods are not exposed to an IronPython test script.
private bool IsPair() { return this.card1.Rank == this.card2.Rank; }
private bool IsFlush() { return this.card1.Suit == this.card2.Suit; }
private bool IsStraight() {
  if (this.card1.Rank == "A" && this.card2.Rank == "K") return true;
  else if (this.card1.Rank == "K" && this.card2.Rank == "Q") return true;
  // etc: Q-J, J-T, T-9, 9-8, 8-7, 7-6, 6-5, 5-4, 4-3, 3-2
  else if (this.card1.Rank == "A" && this.card2.Rank == "2") return true; 
  else return false;
}
private bool IsStraightFlush() { return this.IsStraight() &&
  this.IsFlush();
}
As a general rule of thumb, when performing module testing you do not explicitly test private methods. The idea is that any errors in helper methods will be exposed when you test the public method that uses the helper. The Hand.Compare method is surprisingly tricky. I coded private Beats and Ties helper methods and then use them to implement the public Compare method:
public int Compare(Hand h) {
  if (this.Beats(h))
    return 1;
  else if (this.Ties(h))
    return 0;
  else if (h.Beats(this))
    return -1;
  else
    throw new Exception("Illegal path in Compare()");
}
I use the old C language strcmp(s,t) function paradigm for Hand.Compare—if the "left" Hand parameter (the "this" object) beats the "right" parameter (the explicit Hand input parameter), Compare returns 1. If the right parameter beats the left parameter Compare returns -1. If the two Hand objects are equal according to my rules, Compare returns 0. Most of the work is done by the private Beats method, which is shown in Figure 5.
private bool Beats(Hand h) {
  if (this.IsStraightFlush()) {
    if (!h.IsStraightFlush()) return true;
    if (h.IsStraightFlush()) {
      if (Card.Beats(this.card1, h.card1)) return true;
      else return false;
    }
  } // this.IsStraightFlush()
  else if (this.IsPair())
    // code  
  else if (this.IsFlush())
   //  code
  else if (this.IsStraight())
    // code    
  else 
    // code for Ace-high down to Four-high
  }

  return false;
}
The code for Beats is about a page long and you can check out the details if you're interested by examining the accompanying code download. I deliberately inserted a logic error into the private Ties method:
else if (this.IsFlush() && h.IsFlush() &&  
  this.card1.Rank == h.card1.Rank)          // error
    return true;
If the two Hand objects being compared are both Flushes, then I check to see if both hands have the same rank for the high card. However I don't check the second card of each hand as I should have:
else if (this.IsFlush() && h.IsFlush() &&  
  this.card1.Rank == h.card1.Rank &&
  this.card2.Rank == h.card2.Rank)         // correct
    return true;
This logic error produces the test case failure shown in Figure 2. I built my library by using Visual Studio to create a Class Library project named TwoCardPokerLib at C:\Module TestingWithPython, resulting in a TwoCardPokerLib.dll file at C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug.

Ad Hoc Interactive Module Testing
Now let's see how you can examine and test .NET libraries using IronPython. In particular, let's look at each of the commands shown in the screenshot in Figure 1. The first part of the output shown in Figure 1 indicates that I am using version 1.1.1 of IronPython.
IronPython is a free download available from CodePlex, the Microsoft-sponsored open source project, at codeplex.com/IronPython. It requires the .NET Framework 2.0 and runs on any machine that supports that version of the Framework. I am using Windows Vista. You don't really install IronPython, rather you simply download a single zipped file to your machine and extract its contents to any convenient directory. In my case I stored all the IronPython files and subdirectories at C:\IronPython. I invoked the Python command-line interpreter (ipy.exe) and specified an optional -X:TabCompletion argument to enable tab completion. If you type ipy.exe -h you'll get a list of all options. When IronPython starts up, it will execute a special startup script, named site.py, if such a script exists. I used the standard site.py file that comes with IronPython, without any modifications.
After IronPython initializes, I examine the current IronPython system path information:
>>> import sys
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib']
First I issue an import sys command so I have access to the methods inside the special IronPython sys module. Next I display the list of paths that IronPython will examine when looking for files that are not in the current directory. You can think of this as a local IronPython mechanism that is somewhat similar to the Windows operating system Path environment variable. Because I invoked IronPython with the TabCompletion feature, if I wanted to know what properties and methods are available to me from the sys module, I could have typed sys. and hit the <tab> key repeatedly. Next I tell IronPython where my .NET module under test is located:
>>> sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug')
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib', 'C:\\ModuleTestingWithPython\\TwoCardPokerLib\\bin\\Debug']
I use the path.append method of the sys module to add a new directory to the IronPython search list. Notice the r character in front of the string argument to append. In Python you can use either single-quotes or double-quotes around strings. However, unlike some languages, Python will evaluate escape characters such as \n inside both single-quoted and double-quoted strings. To ensure that a string is interpreted strictly as a literal (a "raw" string in Python terminology) you can use the r modifier as I've done above. After appending the new directory I verify that I didn't make any typing mistakes by issuing a sys.path command. Next I prepare to load my DLL under test by first enabling methods that can load .NET modules:
>>> import clr
>>> dir()
['_', '__builtins__', '__doc__', '__name__', 'clr', 'site', 'sys']
I issue an import clr (for the common language runtime) command and now I have access to the clr module, which has several methods that can load .NET assemblies. Then I use the dir() Python command to see which modules I currently have access to. Notice I do not currently have access to the TwoCardPokerLib library. Now I can use the clr module to gain access to my library under test:
>>> clr.AddReferenceToFile("TwoCardPokerLib.dll")
>>> from TwoCardPokerLib import *
>>> dir()
['Card', 'Hand', '_', '__builtins__', '__doc__', '__name__', 'clr', 'site', 'sys']
I use the AddReferenceToFile method to enable my current IronPython environment to call into the TwoCardPokerLib DLL. Python is case sensitive, so if I had typed addreferencetofile, for example, I would have gotten a no-such-attribute (method) error.
After adding a reference to my module under test, I need to use the import statement to make the module available. I could have typed import TwoCardPokerLib but I type from TwoCardPokerLib import * instead. If I use the first, simpler form, I would have to fully qualify everything in my module, for example, TwoCardPokerLb.Hand instead of just typing Hand. The from-<module>-import-<classes> form of the import command allows me to omit the parent module name when I type. Notice that after I do a dir() command, I see that the Card and Hand classes inside the TwoCardPokerLib module are now available to me. Now I can exercise my module under test by creating two Card objects:
>>> c1 = Card()
>>> c2 = Card("9d")
>>> print c1,c2
As 9d
I use the default Card constructor to instantiate an object named c1. Recall from the previous section that the default Card constructor creates an Ace of spades object. I use the non-default Card constructor to create an object representing a Nine of diamonds. Observe that I do not use a keyword like "new" to instantiate objects as I would in some languages. If I had used the short "import TwoCardPokerLib" statement earlier, I would have called the constructor as "c1 = TwoCardPokerLib.Card()". I use the intrinsic Python print statement to display my two Card objects. Behind the scenes, the IronPython print statement is actually calling the Card.ToString() method I implemented in my class library. Now I instantiate two Hand objects and call the Hand.Compare method:
>>> h1 = Hand(c1,c2)
>>> h2 = Hand("Ah","8c")
>>> expected = 1
>>> actual = h1.Compare(h2)  
I pass the two Card objects I just created into a Hand constructor to create a first hand, and then I use the string-parameter version of constructor to instantiate a second hand. Because a hand of Ace-Nine beats a hand of Ace-Eight, I expect the Compare method to return 1 so I store that value in a variable called expected. Notice that Python is a dynamically typed language, so I don't declare data types. I call the Hand.Compare method and store the result into a variable named actual. I can view the available methods in the Hand class by typing Hand. on the command line and then hitting the <tab> key. I would see the public methods such as Compare and ToString but I would not see private methods such as Beats and Ties. Now I can determine a pass/fail result for my ad hoc interactive test:
>>> if actual == expected: print "Pass\n",
... else: print "Fail\n"
...
Pass
>>>
The Python if-then syntax on a command line is a bit unusual, so I'll explain it in the next section. But essentially I check to see if the value in the variable called actual is equal to the value in the variable called expected. If so, I display a "Pass" message; otherwise I print "Fail". Notice I 've included a newline character in each string. The intermediate "..." response from the IronPython interpreter indicates that I have an incomplete command and the interpreter is waiting for me to complete the command.

Lightweight Module Test Automation
Now let's see how to write lightweight IronPython scripts to test .NET class libraries. The script in Figure 6 produced the output shown in Figure 2.
# harness.py
# test TwoCardPokerLib.dll using data in TestCases.txt

print "\nBegin test run\n"
import sys
print "Adding location of TwoCardPokerLib.dll to sys.path"
sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug')

import clr
print "Loading TwoCardPokerLib.dll\n"
clr.AddReferenceToFile("TwoCardPokerLib.dll")
from TwoCardPokerLib import *

print "=================================="
fin = open("TestCases.txt", "r")
for line in fin:
  if line.startswith("$"):
    continue
  (caseID,input,method,expected,comment) = line.split(':')
  expected = int(expected)
  (left,right) = input.split(',')
  h1 = Hand(left[0:2],left[2:4])
  h2 = Hand(right[0:2],right[2:4])
  print "Case ID = " + caseID
  print "Hand1 is " + h1.ToString() + "   " + "Hand2 is " + h2.ToString()
  print "Method = " + method + "()"
  actual = h1.Compare(h2)

  print "Expected = " + str(expected) + " " + "Actual = " + str(actual)
  if actual == expected:
    print "Pass"
  else:
    print "** FAIL **"


  print "=================================="

fin.close()
print "\nEnd test run"
# end script
I begin my IronPython test harness script with comments:
# harness.py
# test TwoCardPokerLib.dll using data in TestCases.txt
The # is the comment character for Python scripts. Python is line-based, so comments run through the end of a line. Python also supports multi-line comments using triple single-quotes. Python scripts use a .py file extension and can be created using any text editor, including Notepad. My test harness reads test case data from an external file named TestCases.txt:
0001:AcKc,AdAs:Compare:1:  "royal flush" > pair Aces
0002:Td9d,Th9h:Compare:0:  straight flush diamonds == straight flush hearts
$0003:Ah2c,As9c:Compare:1:   straight > Ace high
0004:9h6h,9d7d:Compare:-1:  flush 9-6 high < flush 9-7
0005:KcJh,As5d:Compare:-1: King high < Ace high
Here I have just five test cases, but in a production scenario I would have hundreds or thousands. There are 7,311,616 (or 524 ) legal inputs to Hand.Compare, which illustrates the impracticality of exhaustively testing even simple modules.
Each line represents a single test case. Each field is delimited by the colon character. The first field is a test case ID. The second field is a string that contains information for two Hand objects, separated by a comma character. The third field indicates which method to test. The fourth field is the expected value. The fifth field is optional and is a test case comment. Notice test case 0003 is preceded by a $ character. I will scan for this character in my test harness and skip over any such test cases. Test case 0004 generates a fail result because of the deliberate logic error (solely for demonstration purposes) that I placed inside the Hand.Ties method that is called by the Hand.Compare method under test. Storing test case data in a text file is quick and easy. I could also have embedded my test case data directly into my harness, or stored my cases in an XML file, and so on. Python can easily handle any test case storage scheme. Next I display a message to the command shell:
print "\nBegin test run\n"
Because Python strings can be delimited by either double quotes or single quotes, and escape characters are evaluated inside both quote styles, the choice between quote types is really a matter of preference. I generally use double-quotes except when I am going to use the r modifier to make my string a literal, in which case I tend to use single quotes. Next my Python test harness script adds the location of my DLL under test to the Python system path:
import sys
print "Adding location of TwoCardPokerLib.dll to sys.path"
sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug')
Here I hardcode the location of the DLL under test. I can pass command-line arguments to my python script and access them with the built-in sys.argv array. For example, if I were to invoke my script as
  > ipy.exe   harness.py   C:\Data   MyLib.dll
then sys.argv[0] would hold the name of my script (harness.py), sys.argv[1] would hold the first argument (C:\Data), and sys.argv[2] would hold the second argument (MyLib.dll). Next I tell my harness to load my module under test:
import clr
print "Loading TwoCardPokerLib.dll\n"
clr.AddReferenceToFile("TwoCardPokerLib.dll")
from TwoCardPokerLib import *
There are several ways to load a .NET-based library using IronPython. The clr.AddReferenceToFile method is simple and effective when your sys.path contains the location of the library to add, but the clr module also contains alternatives including clr.AddReference, clr.AddReferenceByName, clr.AddReferenceByPartialName, and clr.AddReferenceToFileAndPath. Here I use the * wildcard to load all classes from the TwoCardPokerLib library, but I can load specific classes by naming them. Python has several ways to process a text file line-by-line. I use the intrinsic file open function, and pass it the name of my test case file and an r argument to indicate that I am opening the file for reading:
print "=================================="
fin = open("TestCases.txt", "r")
for line in fin:
  # process line here
Other common modes include w for writing and a for appending. The mode argument is optional and defaults to r so I could have left it out. A good reference for Python language features, syntax, and intrinsic functions can be found at docs.python.org. The result of the open function is a file handle I assign to a variable named fin. Then I use a for loop to iterate line by line. I named my line-storage variable "line", but I could have used any legal variable name. Notice a syntactic quirk of Python: instead of using begin-end tokens such as {. .. } or begin. .. end as most languages do, Python uses the colon character in conjunction with indentation to indicate the start and finish of statement blocks. Inside my main processing loop, I use the intrinsic startswith string function to check if the current line from my test case file begins with a dollar sign character:
if line.startswith("$"):
  continue
If I find a $, I use the continue statement to skip over the remaining statements in the for loop, and read the next line from my test case file. Python has a complete set of loop and decision control structures. Next I parse my test case data line into its individual fields:
(caseID,input,method,expected,comment) = line.split(':')
expected = int(expected)
I use a neat feature of Python called a tuple and the intrinsic split string function to quickly parse my data. I could have performed the task using a traditional array approach as you see below, but I think the Python tuple approach is both shorter and easier to understand.
tokens = line.split(':')
caseID = tokens[0]
input = tokens[1]
method = tokens[2]
expected = tokens[3]
comment = tokens[4]
I use the int() function to explicitly convert the variable called expected from type string to type int so that I can compare expected with actual. Other useful type conversion functions include str(), float(), long(), and bool(). Next I perform a second parsing operation to extract the two input hands:
(left,right) = input.split(',')
h1 = Hand(left[0:2],left[2:4])
h2 = Hand(right[0:2],right[2:4])
After my first parse with split, the variable input holds a string value like KcJh,As5d. So, I call split again with a, argument and store the two results into the variables I called left and right. So now the string variable called left holds KcJh and right holds As5d. Unlike many languages, Python does not have a substring method. Instead, Python uses array indexing to extract substrings. Array indexing starts at 0, so if variable left holds KcJh then the expression left[0:2] yields Kc and the expression left[2:4] yields Jh. Notice that to extract a substring from a larger string, you specify the index of the first character in the larger string (as you'd expect) but one more than the index of the ending character. Once I have strings that represent individual cards, I pass them to the Hand constructor. Next I echo my input data to the shell:
print "Case ID = " + caseID
print "Hand1 is " + h1.ToString() + "   " + "Hand2 is " + h2.ToString()
print "Method = " + method + "()"
Python uses the + character to perform string concatenation, so in the three print statements above I am printing three strings. The print function can accept multiple arguments separated by the, character so I could have written statements such as the following to produce the same output:
print "Case ID = " , caseID
Now I'm ready to call the method under test:
actual = h1.Compare(h2)
print "Expected = " + str(expected) + " " + "Actual = " + str(actual)
Notice that because I implemented the Hand.Compare method as an instance method, I call it from the context of the h1 Hand object. If I had coded Compare as a static method I would have called it like so:
actual = Compare(h1,h2)
Notice that at this point the variables actual and expected are of type int because actual is the return value from the Compare method that is defined to return an int, and expected was explicitly cast to an int. Therefore I cast actual and expected to strings so that I can concatenate a single output string. Now I display my test case pass/fail result:
if actual == expected:
  print "Pass"
else:
  print "** FAIL **"

print "=================================="
Python uses indentation to indicate the beginning and end of block statements. If you have a lot of experience programming in other languages, Python's use of indentation may seem a bit odd at first. But most testers I've talked to say that Python's unusual syntax issues become familiar quite quickly.
If I want to keep track of the total number of test cases that pass, I can initialize counters outside my main processing loop like so:
numPass = numFail = 0
and then modify my if-then structure:
if actual == expected:
  numPass += 1
  print "Pass"
else:
  numFail += 1
  print "** FAIL **"
Then I can print my results outside the processing loop:
print "Num pass = " , numPass ,  " num fail = " ,  numFail
Note that Python does not support syntax like numPass++ or ++numPass to increment an int variable. Here I am simply printing results to the command shell, but I can easily write results to a text file, XML file, SQL database, or other storage. Now I finish my test harness up:
fin.close()
print "\nEnd test run"
# end script
I close my test case file reference to free up its file handle, and print a message stating that the test is complete. My script structure is very simple but Python has features that allow you to manage complex scripts. For example, in a production environment I would definitely wrap my entire script in an exception handler:
try
  # harness code here
except
  # handle any exceptions here 
Additionally, Python supports programmer-defined functions, so I could have structured my harness as a main function plus helper functions.

Wrapping Up
Automated module testing is arguably the most fundamental kind of software test automation. Whenever I want to evaluate a programming language's suitability for test automation, I first check how well the language deals with module testing. In my opinion, IronPython passes this litmus test with flying colors. No programming language is perfect for all testing tasks and scenarios. That said, IronPython has many features that make it an excellent choice as a module testing language.
Let me finish by mentioning how lightweight module test automation with IronPython relates to unit testing. Unit tests with frameworks such as NUnit are most often placed directly inside your module code. Test driven development with unit testing is primarily a developer activity. Using a unit testing approach during development does not absolve you from the responsibility of performing thorough, dedicated module testing. This is where Python comes in. In other words, module testing with Python is a complement to, not a substitute for, unit testing. When the two approaches are used together you can produce better, more reliable software.

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

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

Esecuzione dei test
Modulo .NET test con IronPython
James McCaffrey
Il linguaggio di scripting Python dispone di funzionalità che rendono una scelta eccellente per eseguire diversi tipi di test del software. Vi sono diverse implementazioni di Python disponibili inclusi CPython, l'implementazione più comune su computer che eseguono sistemi operativi simile a UNIX e IronPython, un'implementazione rilasciato in ritardo 2006 che le destinazioni in Microsoft .NET Framework. Nell'articolo di mese questo verrà illustrato come utilizzare IronPython per verificare i moduli basati su .NET dalla entrambi Python riga di comando e negli script Python lightweight. È possibile utilizzare avere esperienza con un linguaggio script come JavaScript, Windows PowerShell, VBScript, Perl, PHP oppure Ruby, ma È non presupporre che conoscenza qualsiasi Python. Dare un'occhiata alla Figura 1 per ottenere un'idea di in cui sono diretto. STO utilizzando IronPython eseguire un test ad hoc e rapido di un metodo che confronta due mani scheda in un modulo standard .NET denominato TwoCardPokerLib. È possibile descrivere i comandi mostrati nella Figura 1 in dettaglio più avanti in questa colonna, ma per ora è possibile osservare che è possibile in modo interattivo aggiungere un riferimento a una DLL che contiene il modulo di .NET, creare istanze di due oggetti di magazzino da tale modulo e chiamare un metodo di confronto per determinare se una mano battute altri mano. Ora esaminiamo rapido nella Figura 2 . Tale schermata è raffigurato l'esecuzione di uno script IronPython leggero che impiegato di minuti solo da creare. Anche in questo caso, passare su tutti i dettagli più avanti, ma è possibile osservare che è possibile eseguire modulo classico test mediante la lettura dei dati di test case da un file di testo, creare istanze di due oggetti di disponibilità dal modulo TwoCardPokerLib, chiamare il metodo di confronto e quindi confrontare i risultati effettivi con i risultati previsti per determinare i risultati di test case passare o non riuscita.
>>> import sys
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib']
>>> sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\  Debug')
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib', 'C:\\ModuleTestingWithPython\\  TwoCardPokerLib\\bin\\Debug']
>>>
>>> import clr
>>> dir()
['_', '__builtins__', '__doc__', '__name__', 'clr', 'site', 'sys']
>>>
>>> clr.AddReferenceToFile("TwoCardPokerLib.dll")
>>> from TwoCardPokerLib import *
>>> dir()
['Card', 'Hand', '_', '__builtins__', '__doc__', '__name__', 'clr',   'site', 'sys']
>>>
>>> c1 = Card()
>>> c2 = Card("9d")
>>> print c1,c2
As 9d
>>>
>>> h1 = Hand(c1,c2)
>>> h2 = Hand("Ah","8c")
>>> expected = 1
>>> actual = h1.Compare(h2)
>>>
>>> if actual == expected: print "Pass\n",
... else: print "Fail\n"
...
Pass
>>>
>>> ^Z

C:\IronPython>
Nella figura 2 automazione di test con uno script IronPython
Nelle sezioni che seguono, descriverò la libreria di classi TwoCardPokerLib testare in modo da comprendere esattamente ciò che è da testare. Quindi tratterò i comandi IronPython interattivi che HO utilizzato nella Figura 1 . Successivamente è possibile presentare e spiegare in dettaglio lo script Python breve che ha prodotto l'output nella Figura 2 . Il codice sorgente completo per il harness di test e la libreria sottoposti a test sono download che accompagna questo articolo.
Verrà disposto i con una breve descrizione di come poter adattare ed estendere le idee presentate in base alle specifiche esigenze proprio test. Sono sicuri che si scoprirà che Python test le tecniche qui presentate sarà un'aggiunta grande per il software verifica set di competenza.

Il modulo in test
Ora esaminiamo la raccolta in seguito a verifica. HO deciso di creare una .NET libreria di classi per il testing che dispone di sufficiente funzionalità per visualizzare i tipi di problemi di che affrontare quando si esegue modulo reale test in un ambiente di produzione, ma non in modo molto complessità che i dettagli della raccolta nascondere i test problemi. È possibile immaginata una partita ipotetica poker due scheda in cui ogni giocatore riceve solo due schede da quattro, deck standard e 52 scheda. Naturalmente questo porta a una progettazione orientata con una classe della scheda, una classe di disponibilità e un metodo di confronto per determinare quale dei due oggetti di magazzino è migliore. HO deciso che ogni mano due scheda sarebbe essere classificata come scaricamento una seria consecutivi schede dello stesso seme, una coppia di due schede con la stessa classificazione, un scaricamento due schede dello stesso seme, una seria due schede di consecutivi e quindi ACE alto verso il basso su quattro elevato. Si noti che una mano tre elevato non è possibile perché tre due è una seria e ACE 3 è un elevato di ACE. Anche tali un semplice esempio è piuttosto interessante da implementare. IronPython può essere utilizzato per verificare in modo efficace librerie .NET indipendentemente dal linguaggio implementazione di e IronPython può essere utilizzato per verificare le librerie COM classiche. Il codice sorgente per la classe scheda nel catalogo multimediale TwoCardPokerLib è elencato nella Figura 3 .
public class Card
{
  private string rank;
  private string suit;
  public Card() {
    this.rank = "A"; // A,2, . . ,9,T,J,Q,K
    this.suit = "s"; // c,d,h,s
  }
  public Card(string s) {
    this.rank = s[0].ToString();
    this.suit = s[1].ToString();
  }
  public override string ToString() {
    return this.rank + this.suit;
  }
  public string Rank {
    get { return this.rank; }
  }
  public string Suit {
    get { return this.suit; }
  }
  public static bool Beats(Card c1, Card c2) {
    if (c1.rank == "A") {
      if (c2.rank != "A") return true;
      else return false;
    }
    else if (c1.rank == "K") {
      if (c2.rank == "A" || c2.rank == "K") return false;
      else return true;
    }
    else if (c1.rank == "Q") {
      if (c2.rank == "A" || c2.rank == "K" || c2.rank == "Q") return false;
      else return true;
    }
    else if (c1.rank == "J") {
      if (c2.rank == "A" || c2.rank == "K" || c2.rank == "Q" || c2.rank == "J")
        return false;
      else
        return true;
    }
    else if (c1.rank == "T") {
      if (c2.rank == "A" || c2.rank == "K" || c2.rank == "Q" ||
        c2.rank == "J" || c2.rank == "T")
        return false;
      else return true;
    }
    else { // c1.rank is 2, 3, . . 9
      int c1Rank = int.Parse(c1.rank);
      int c2Rank = int.Parse(c2.rank);
      return c1Rank > c2Rank;
    }
  } // Beats()

  public static bool Ties(Card c1, Card c2) {
    return (c1.rank == c2.rank);
  }
} // class Card
Per mantenere il codice breve e le idee principali deselezionare, eseguita alcuni collegamenti che è non scrivere in un ambiente di produzione, ad esempio se tutti i controlli di errore. La classe della scheda è stata progettata intenzionalmente per illustrare molte delle funzionalità comuni di un modulo .NET tipica. Si noti vi è un costruttore di scheda predefinito, che consente di creare un Asso di picche scheda oggetto. È un costruttore che accetta una stringa come TD per creare un oggetto che rappresenta un dieci della scheda di diamanti. È possibile implementare proprietà get per esporre il rango e il seme di un oggetto scheda. Ed è possibile implementare metodi Beats e riferimenti statici che possono essere utilizzati per determinare se un oggetto scheda battute o collega un altro oggetto scheda. La classe di magazzino è troppo lunga per presentare nella sua interezza, in modo descriverò solo le parti chiave della classe. La classe di magazzino dispone solo due campi di membro:
public class Hand {
  private Card card1;
  private Card card2;
  // constructors, methods, etc.
}
SI crea il presupposto importante card1 è la maggiore di (o probabilmente uguale a) i due oggetti scheda. Questo semplifica notevolmente la logica del mio metodo Hand.Compare. La classe di magazzino dispone di costruttori più, una situazione tipica, è necessario considerare quando si esegue il test del modulo. Il costruttore di disponibilità predefinito crea una mano con due schede di picche ace–of:
public Hand() {
  this.card1 = new Card(); 
  this.card2 = new Card();
}
È possibile implementare due altri costruttori di disponibilità Consenti a entrambi passare in due oggetti scheda o due stringhe:
public Hand(Card c1, Card c2) {
  this.card1 = new Card(c1.Rank + c1.Suit);
  this.card2 = new Card(c2.Rank + c2.Suit);
}
public Hand(string s1, string s2) {
  this.card1 = new Card(s1);
  this.card2 = new Card(s2);
}
Nella Figura 4 è possibile implementare quattro metodi helper privati. Come si vedrà metodi privati non sono esposti a uno script di test IronPython.
private bool IsPair() { return this.card1.Rank == this.card2.Rank; }
private bool IsFlush() { return this.card1.Suit == this.card2.Suit; }
private bool IsStraight() {
  if (this.card1.Rank == "A" && this.card2.Rank == "K") return true;
  else if (this.card1.Rank == "K" && this.card2.Rank == "Q") return true;
  // etc: Q-J, J-T, T-9, 9-8, 8-7, 7-6, 6-5, 5-4, 4-3, 3-2
  else if (this.card1.Rank == "A" && this.card2.Rank == "2") return true; 
  else return false;
}
private bool IsStraightFlush() { return this.IsStraight() &&
  this.IsFlush();
}
Come una generale regola empirica, quando esegue il modulo che si verifica non in modo esplicito verifica metodi privati. Il concetto è che gli eventuali errori nei metodi di supporto verranno esposte quando si verifica il metodo pubblico che utilizza l'Assistente. Il metodo Hand.Compare è ovviamente difficile. È codificato privati metodi di supporto Beats e riferimenti e quindi utilizzarli implementare il metodo pubblico di confronto:
public int Compare(Hand h) {
  if (this.Beats(h))
    return 1;
  else if (this.Ties(h))
    return 0;
  else if (h.Beats(this))
    return -1;
  else
    throw new Exception("Illegal path in Compare()");
}
È possibile utilizzare il vecchio paradigma di funzione strcmp(s,t) di linguaggio C per il "left disponibilità parametro (il "this" oggetto) Hand.Compare—if battute il parametro "right (l'esplicita disponibilità parametro di input), il confronto restituisce 1. Se il parametro destra battute il parametro sinistro Confronta restituisce-1. Se due oggetti di magazzino sono uguali in base delle regole, Confronta restituisce 0. La maggior parte delle operazioni viene eseguita tramite il metodo Beats privato, che è illustrato nella Figura 5 .
private bool Beats(Hand h) {
  if (this.IsStraightFlush()) {
    if (!h.IsStraightFlush()) return true;
    if (h.IsStraightFlush()) {
      if (Card.Beats(this.card1, h.card1)) return true;
      else return false;
    }
  } // this.IsStraightFlush()
  else if (this.IsPair())
    // code  
  else if (this.IsFlush())
   //  code
  else if (this.IsStraight())
    // code    
  else 
    // code for Ace-high down to Four-high
  }

  return false;
}
Il codice per Beats è su una pagina lunga ed è possibile estrarre i dettagli se sta esaminando il relativo download del codice. Deliberatamente inserito un errore di logica metodo angosciate privato:
else if (this.IsFlush() && h.IsFlush() &&  
  this.card1.Rank == h.card1.Rank)          // error
    return true;
Se i due oggetti disponibilità confrontati sono entrambi cancellazioni, quindi è possibile verificare se entrambe le mani necessario alla stessa classificazione per la scheda elevata. Tuttavia non verificare la seconda scheda di ogni mano È necessario disporre:
else if (this.IsFlush() && h.IsFlush() &&  
  this.card1.Rank == h.card1.Rank &&
  this.card2.Rank == h.card2.Rank)         // correct
    return true;
Questo errore di logica genera l'errore di test case illustrato nella Figura 2 . HO creato multimediale utilizzando Visual Studio per creare un progetto di libreria denominato TwoCardPokerLib in TestingWithPython C:\Module, in un file TwoCardPokerLib.dll a C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug.

Ad hoc modulo interattivo test
Ora vediamo come è possibile esaminare e testare librerie .NET utilizzando IronPython. In particolare, esaminiamo ciascuno dei comandi mostrati nella schermata nella Figura 1 . La prima parte dell'output mostrato nella Figura 1 indica che STO utilizzando versione 1.1.1 di IronPython.
IronPython è un download gratuito disponibile in CodePlex, il progetto open source sponsorizzato da Microsoft, in codeplex.com/IronPython. Esso richiede .NET Framework 2.0 e viene eseguito in qualsiasi computer che supporta tale versione di Framework. L'utilizzo di Windows Vista. È effettivamente non installare IronPython, piuttosto semplicemente scaricare un unico file compresso nel computer ed estrarre del contenuto in qualsiasi directory pratico. Nel mio caso È possibile memorizzati tutti i file IronPython e sottodirectory in C:\IronPython. Richiamato l'interprete della riga di comando Python (ipy.exe) e specificata una facoltativa - X: argomento TabCompletion per attivare il completamento della scheda. Se si digita ipy.exe -h verrà visualizzato un elenco di tutte le opzioni. All'avvio IronPython eseguirà uno script di avvio speciali, denominato site.py, se esiste uno script di questo tipo. HO utilizzato il file di site.py standard fornito con IronPython, senza alcuna modifica.
Dopo IronPython inizializzato, esaminare le informazioni di percorso di sistema IronPython correnti:
>>> import sys
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib']
Innanzitutto, è possibile inviare un comando sys di importazione in modo che abbia accesso ai metodi all'interno del modulo di sys IronPython speciale. Successivamente è possibile visualizzare l'elenco dei percorsi IronPython esaminerà durante la ricerca di file che non sono nella directory corrente. È possibile considerare questo come meccanismo di IronPython locale è piuttosto simile alla variabile di ambiente PATH del sistema operativo Windows. Poiché richiamato IronPython con la caratteristica TabCompletion, se HO voluto sapere quali proprietà e metodi sono disponibili all'utente corrente dal modulo sys, È stato immesso sys. e premere ripetutamente il tasto <tab>. Successivamente È stabilire IronPython in cui si trova il modulo di .NET sottoposti a test:
>>> sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug')
>>> sys.path
['C:\\IronPython', 'C:\\IronPython\\Lib', 'C:\\ModuleTestingWithPython\\TwoCardPokerLib\\bin\\Debug']
È possibile utilizzare il metodo path.append del modulo sys per aggiungere una nuova directory all'elenco di ricerca IronPython. Si noti il carattere r in prima dell'argomento stringa da accodare. In Python è possibile utilizzare le offerte di sola o offerte doppia attorno a stringhe. Tuttavia, diversamente da alcuni linguaggi, Python valuterà caratteri di escape, ad esempio \n all'interno di stringhe virgolette e racchiuso tra virgolette doppie. Per garantire che una stringa viene interpretata esclusivamente come valore letterale (una stringa elaborata nella terminologia Python) è possibile utilizzare il modificatore r come HO fatto in precedenza. Dopo aver accodato la nuova directory È possibile verificare che non sono state apportate eventuali errori di digitazione inviando un comando sys.path. Successivamente è possibile preparare caricare la DLL testare dai metodi di attivazione primo che possono caricare i moduli di .NET:
>>> import clr
>>> dir()
['_', '__builtins__', '__doc__', '__name__', 'clr', 'site', 'sys']
È possibile inviare un comando di clr (per common language runtime) importazione e ora è possibile avere accesso al modulo di clr, che dispone di numerosi metodi che possono caricare assembly .NET. Quindi è possibile utilizzare dir() Python comando per visualizzare dei moduli È attualmente disponibile. Si noti che non attualmente hanno accesso alla raccolta TwoCardPokerLib. Ora È possibile utilizzare il modulo di clr per accedere a multimediale sottoposti a test:
>>> clr.AddReferenceToFile("TwoCardPokerLib.dll")
>>> from TwoCardPokerLib import *
>>> dir()
['Card', 'Hand', '_', '__builtins__', '__doc__', '__name__', 'clr', 'site', 'sys']
È possibile utilizzare il metodo AddReferenceToFile per attivare l'ambiente IronPython corrente chiamare la DLL TwoCardPokerLib. Se È stato immesso addreferencetofile, ad esempio, non sarebbe hanno utilizzato un errore ad-attributo (metodo) viene fatta distinzione tra maiuscole e minuscole, Python.
Dopo aver aggiunto un riferimento per il modulo sottoposti a test, È necessario utilizzare l'istruzione import per rendere disponibile il modulo. Impossibile è tipizzato importazione TwoCardPokerLib ma si digita dall'importazione TwoCardPokerLib * invece. Se si utilizza il modulo primo, più semplice, È necessario completo tutto il modulo, TwoCardPokerLb.Hand, ad esempio, invece di digitare solo magazzino. Mi omettere il nome del modulo padre quando si digita consente di forma da <module>-importazione-<classes> di comando di importazione. Si noti che dopo eseguire un comando dir(), è possibile visualizzate le classi di scheda e disponibilità all'interno del modulo TwoCardPokerLib saranno disponibili all'utente. Ora possibile esercitare il modulo in seguito a verifica creando due oggetti scheda:
>>> c1 = Card()
>>> c2 = Card("9d")
>>> print c1,c2
As 9d
È possibile utilizzare il costruttore di scheda predefinito per creare un'istanza un oggetto denominato c1. Ricordare che il costruttore di scheda predefinito crea un Asso di picche oggetto dalla sezione precedente. È possibile utilizzare il costruttore scheda non predefinito per creare un oggetto che rappresenta un nove di diamanti. Osservare che è possibile non utilizzare una parola chiave come "New" per creare istanze di oggetti come farebbe in alcuni linguaggi. Se È stato utilizzato il breve "importare TwoCardPokerLib" istruzione precedente, sarebbe stato chiamato il costruttore come " c1 = TwoCardPokerLib.Card() ". È possibile utilizzare l'intrinseco istruzione di stampa Python per visualizzare i due oggetti scheda. Dietro le quinte, l'istruzione stampa IronPython effettivamente chiama il metodo Card.ToString() che HO implementato nel Catalogo multimediale di classe. Ora creare istanze di due oggetti di disponibilità e chiamare il metodo Hand.Compare:
>>> h1 = Hand(c1,c2)
>>> h2 = Hand("Ah","8c")
>>> expected = 1
>>> actual = h1.Compare(h2)  
È possibile passare i due oggetti scheda che appena creato in un costruttore di disponibilità per creare una prima mano e quindi è possibile utilizzare la versione di parametro di stringa del costruttore per creare un'istanza lancetta dei secondi. Poiché una mano di 9 ACE battute una mano di otto ACE, è possibile che il metodo di confronto per restituire 1 è possibile archiviare tale valore in una variabile denominata prevista. Si noti che Python è un linguaggio tipizzato in modo dinamico, pertanto è possibile non dichiarare tipi di dati. È possibile chiamare il Hand.Compare metodo e archivio il risultato in una variabile denominata effettivo. È possibile visualizzare i metodi disponibili nella classe di disponibilità digitando magazzino. la riga di comando e quindi premendo il tasto <tab>. Sarebbe vedere i metodi pubblici, ad esempio confrontare e ToString ma non si visualizzate metodi privati quali Beats e riferimenti. A questo punto È possibile determinare un risultato del passaggio e non riuscita per il test interattivo ad hoc:
>>> if actual == expected: print "Pass\n",
... else: print "Fail\n"
...
Pass
>>>
La sintassi di quindi se Python in una riga di comando è un po'inusuale, pertanto verrà spiegato nella sezione successiva. Ma essenzialmente è possibile verificare se il valore nella variabile denominata effettivo è uguale al valore della variabile denominata previsto. Se in questo caso, visualizzato un messaggio di "passaggio"; in caso contrario è possibile stampare "errori. Si noti che HO incluso un carattere di nuova riga in ciascuna stringa. Intermediate "..." risposta dall'interprete IronPython indica che È necessario un comando non completato e l'interprete è in attesa di completare il comando.

Automazione di test Lightweight modulo
A questo punto seguito illustrato scrivere script IronPython leggero per testare alle librerie di classe. NET. Lo script in figura 6 viene prodotto l'output mostrato nella Figura 2 .
# harness.py
# test TwoCardPokerLib.dll using data in TestCases.txt

print "\nBegin test run\n"
import sys
print "Adding location of TwoCardPokerLib.dll to sys.path"
sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug')

import clr
print "Loading TwoCardPokerLib.dll\n"
clr.AddReferenceToFile("TwoCardPokerLib.dll")
from TwoCardPokerLib import *

print "=================================="
fin = open("TestCases.txt", "r")
for line in fin:
  if line.startswith("$"):
    continue
  (caseID,input,method,expected,comment) = line.split(':')
  expected = int(expected)
  (left,right) = input.split(',')
  h1 = Hand(left[0:2],left[2:4])
  h2 = Hand(right[0:2],right[2:4])
  print "Case ID = " + caseID
  print "Hand1 is " + h1.ToString() + "   " + "Hand2 is " + h2.ToString()
  print "Method = " + method + "()"
  actual = h1.Compare(h2)

  print "Expected = " + str(expected) + " " + "Actual = " + str(actual)
  if actual == expected:
    print "Pass"
  else:
    print "** FAIL **"


  print "=================================="

fin.close()
print "\nEnd test run"
# end script
È possibile iniziare lo script di harness IronPython test con i commenti:
# harness.py
# test TwoCardPokerLib.dll using data in TestCases.txt
Il # è il carattere Commenti per script Python. Python sia riga-base, commenti eseguire fino alla fine di una riga. Python supporta inoltre multiriga commenti utilizzando singolo triplo offerte. Script Python utilizzare un'estensione di file .PY e possono essere create utilizzando qualsiasi editor di testo, compresi il blocco note. Mio harness test legge i dati di test case da un file esterno denominato TestCases.txt:
0001:AcKc,AdAs:Compare:1:  "royal flush" > pair Aces
0002:Td9d,Th9h:Compare:0:  straight flush diamonds == straight flush hearts
$0003:Ah2c,As9c:Compare:1:   straight > Ace high
0004:9h6h,9d7d:Compare:-1:  flush 9-6 high < flush 9-7
0005:KcJh,As5d:Compare:-1: King high < Ace high
In questo campo è solo cinque test case, ma in uno scenario di produzione avrebbe centinaia o migliaia. Esistono 7,311,616 o 524 gli input validi per Hand.Compare, illustra impracticality di testing con attenzione anche semplici moduli.
Ogni riga rappresenta un singolo test case. Ogni campo è delimitato dal carattere due punti. Il primo campo è un ID di test case. Il secondo campo è una stringa che contiene informazioni per due oggetti di disponibilità, separati da un carattere punto e virgola. Il terzo campo indica quale metodo da verificare. Il quarto campo è il valore previsto. Quinto campo è facoltativo e un commento di test case. Avviso test case 0003 è preceduta da un carattere $. Verrà Cerca il carattere nel mio harness di test e saltare questi casi di test. Test case 0004 genera un risultato di errore causa dell'errore logica deliberatamente (esclusivamente per scopi dimostrativi) che è possibile inserita all'interno del metodo di Hand.ties chiamato dal metodo Hand.Compare sottoposti a test. L'archiviazione dei dati di test case in un file di testo è semplice e rapido. Potrebbero inoltre sono incorporati i dati di test case direttamente nel mio harness, oppure memorizzati i casi in un file XML e così via. Python facilmente possibile gestire qualsiasi schema di archiviazione di test case. Successivamente è possibile visualizzare un messaggio la shell di comando:
print "\nBegin test run\n"
Poiché Python stringhe possono essere delimitate da double offerte o virgolette e caratteri di escape vengono valutati in entrambi gli stili offerta, la scelta tra i tipi di offerta è effettivamente una questione di preferenze. In genere utilizzare offerte doppia tranne quando sono intendo per utilizzare il modificatore r per rendere la stringa di un valore letterale, nel qual caso tendono a utilizzare virgolette singole. Successivamente lo script di harness Python test aggiunge la posizione del mio DLL testare al percorso di sistema Python:
import sys
print "Adding location of TwoCardPokerLib.dll to sys.path"
sys.path.append(r'C:\ModuleTestingWithPython\TwoCardPokerLib\bin\Debug')
Qui è possibile impostare come hardcoded la posizione di DLL in seguito a verifica. È possibile passare gli argomenti della riga di comando al mio script python e accedervi con la matrice sys.argv incorporato. Ad esempio, se fosse necessario richiamare il mio script come
  > ipy.exe   harness.py   C:\Data   MyLib.dll
quindi sys.argv[0] sarebbe contenente il nome del mio script harness.py, sys.argv[1] potrebbe contenere il primo argomento (C:\Data) e sys.argv[2] potrebbe contenere il secondo argomento (MyLib.dll). Quindi stabilire mio harness per caricare il modulo di test:
import clr
print "Loading TwoCardPokerLib.dll\n"
clr.AddReferenceToFile("TwoCardPokerLib.dll")
from TwoCardPokerLib import *
Caricare una libreria basata su .NET utilizzando IronPython in diversi modi. Il metodo di clr.AddReferenceToFile è a semplice ed efficace a quando il sys.path contiene la posizione della raccolta per aggiungere, ma il modulo di clr contiene inoltre inclusi clr.AddReference, clr.AddReferenceByName, clr.AddReferenceByPartialName e clr.AddReferenceToFileAndPath alternative. Qui utilizzerò la * caratteri jolly per caricare tutte le classi dalla raccolta TwoCardPokerLib, ma È possibile caricare classi specifiche da loro nomi. Python dispone di numerosi metodi per elaborare un testo file riga per riga. È possibile utilizzare funzione di apertura file intrinseco e passare è il nome di un file di test case e un argomento r per indicare che l'apertura del file per la lettura:
print "=================================="
fin = open("TestCases.txt", "r")
for line in fin:
  # process line here
Altre modalità più comuni includono w per la scrittura e una per l'accodamento. L'argomento modalità è facoltativo e per impostazione predefinita r in modo che Impossibile hanno lasciato fuori. È possibile trovare un riferimento valido per funzionalità di linguaggio, sintassi e le funzioni intrinseche Python in docs.python.org. Il risultato della funzione aperto è un handle di file che è possibile assegnare a una variabile denominata fin. Quindi è possibile utilizzare un ciclo scorrere la riga dalla riga for. È denominato mio campo-archiviazione variabile "riga", ma Impossibile hanno utilizzato qualsiasi nome di variabile valido. Si noti che un capriccio sintattica di Python: invece di utilizzare, ad esempio Inizio, Fine token {. .. } o iniziale. .. fine come la maggior parte delle lingue, Python utilizza il carattere due punti in combinazione con rientri per indicare l'inizio e fine dei blocchi di dichiarazione. All'interno il ciclo di elaborazione principale, è possibile per utilizzare la startswith intrinseco stringa funzione per verificare se la riga corrente dal file test case inizia con un carattere di segno di dollaro:
if line.startswith("$"):
  continue
È possibile trovare un $, è possibile utilizzare la continue istruzione per saltare le istruzioni rimanenti nel per ciclo e di lettura la riga successiva dal file test case. Python dispone di un completo insieme di strutture di controllo ciclo e decisioni. È possibile analizzare la riga di dati test case nei relativi campi singoli:
(caseID,input,method,expected,comment) = line.split(':')
expected = int(expected)
È possibile utilizzare una caratteristica brillante di Python una tupla e la funzione stringa divisa intrinseco per analizzare rapidamente i dati. È stato sono eseguire l'attività utilizzando un approccio di matrice tradizionali come vedere di seguito, ma RITENGO che l'approccio tupla Python sia più breve di comprensione più immediata.
tokens = line.split(':')
caseID = tokens[0]
input = tokens[1]
method = tokens[2]
expected = tokens[3]
comment = tokens[4]
È possibile utilizzare la funzione int() per convertire la variabile denominata prevista da stringa di tipo nel tipo int in modo che È possibile confrontare in effettivo. Altre funzioni di conversione di tipo utili includono str(), float(), long() e bool(). Successivamente eseguire un'operazione di analisi seconda per estrarre le mani di input due:
(left,right) = input.split(',')
h1 = Hand(left[0:2],left[2:4])
h2 = Hand(right[0:2],right[2:4])
Dopo il primo analisi con divisione, l'input variabile contiene un valore stringa come KcJh, As5d. Pertanto, è possibile chiamare dividere nuovamente con un argomento e memorizzare i risultati di due variabili chiamato a sinistra e destra. A questo punto la variabile di stringa denominata sinistra contiene KcJh e destra contiene As5d. A differenza di molti linguaggi, Python non dispone di un metodo della sottostringa. Al contrario, i Python utilizza l'indicizzazione matrice per estrarre sottostringhe. Matrice di indicizzazione inizia da 0, in modo da Se a sinistra la variabile contiene KcJh quindi all'espressione di sinistra [0: 2] kc e l'espressione left [2:4] raccolti Jh raccolti. Si noti che per estrarre una sottostringa da una stringa più grande, specificare l'indice del primo carattere in stringa di dimensioni maggiore (come prevedibile) ma uno più l'indice del carattere finale. Una volta ottenuto stringhe che rappresentano schede individuali, li è possibile passare al costruttore magazzino. Successivamente È visualizzato i dati di input per la shell:
print "Case ID = " + caseID
print "Hand1 is " + h1.ToString() + "   " + "Hand2 is " + h2.ToString()
print "Method = " + method + "()"
Utilizza Python il + il carattere a eseguire la concatenazione di stringhe, in modo che nelle istruzioni di stampa tre precedente sono stampa tre stringhe. La funzione di stampa può accettare più argomenti separati dal, carattere, pertanto Impossibile immesse istruzioni, ad esempio per produrre lo stesso output:
print "Case ID = " , caseID
A questo punto sono pronto per chiamare il metodo sottoposti a test:
actual = h1.Compare(h2)
print "Expected = " + str(expected) + " " + "Actual = " + str(actual)
Si noti che perché implementato il metodo Hand.Compare come un metodo di istanza, è possibile chiamarlo dal contesto dell'oggetto disponibilità h1. Se È stato codificato compare come metodo statico sarebbe stato chiamato in questo modo:
actual = Compare(h1,h2)
Si noti che a questo punto le variabili effettive e previste sono di tipo int essendo effettivamente il restituito valore dal metodo di confronto definito per restituire un tipo int e previsto che è stato eseguire il in modo esplicito cast su un int. Di conseguenza cast effettivo e alle stringhe in modo che È possibile concatenare una stringa unico output. Ora è possibile visualizzare il risultato di test case passare o non riuscita:
if actual == expected:
  print "Pass"
else:
  print "** FAIL **"

print "=================================="
Python utilizza rientri per indicare all'inizio e alla fine di blocco di istruzioni. Se si dispone numerose esperienza di programmazione in altre lingue, utilizzo Dell'python di rientro può sembrare un po'dispari inizialmente. Ma la maggior parte dei tester È stato descritto dire che problemi insoliti sintassi del Python acquisire familiari abbastanza rapidamente.
Se desidera tenere traccia del numero totale di test case che passano, È possibile inizializzare i contatori all'esterno il ciclo di elaborazione principale in questo modo:
numPass = numFail = 0
e quindi modificare la struttura quindi se:
if actual == expected:
  numPass += 1
  print "Pass"
else:
  numFail += 1
  print "** FAIL **"
Quindi possibile stampare i risultati all'esterno del ciclo di elaborazione:
print "Num pass = " , numPass ,  " num fail = " ,  numFail
Si noti che Python non supporta sintassi simile numPass ++ o ++ numPass incremento di una variabile di int. Qui sono semplicemente stampa i risultati la shell di comando, ma È possibile scrivere facilmente risultati in un file di testo, file XML, database SQL o altro archivio. Ora fine harness il test dei:
fin.close()
print "\nEnd test run"
# end script
È possibile chiudere il riferimento file di test case per liberare il punto di manipolazione di file e stampare un messaggio indicante che il test è completo. La struttura di script è molto semplice ma Python include caratteristiche che consentono di gestire lingue con alfabeti complessi. Ad esempio, in un ambiente di produzione sarebbe disposto in modo definito mio intero script in un gestore eccezioni:
try
  # harness code here
except
  # handle any exceptions here 
Inoltre, Python supporta funzioni definiti dal programmatore, pertanto Impossibile avere strutturato mio harness come una funzione principale più funzioni di supporto.

Disposizione dei
Test automatizzato modulo è probabilmente il tipo più fondamentale di automazione di test del software. Ogni volta che desidera valutare l'idoneità di un linguaggio di programmazione per l'automazione di test, è possibile verificare innanzitutto come la lingua riguarda test del modulo. Secondo me, IronPython supera questo test litmus con flying colors. Linguaggio di programmazione è perfetto per tutte le attività e scenari di test. Ciò detto, IronPython dispone di numerose funzionalità che renderla una scelta eccellente come linguaggio di test del modulo.
Consenti all'utente di fine per accennando come lightweight modulo test di automazione con IronPython si riferisce a unit test. Unit test con Framework quali NUnit vengono spesso inseriti direttamente all'interno del codice di modulo. Fase di sviluppo basato su test e unit test è principalmente un'attività di sviluppo. Utilizzo di un approccio di test unità durante la fase di sviluppo non absolve è da responsabilità dell'esecuzione di test del modulo completa e dedicato. Si tratta di cui Python proviene in. In altre parole, il modulo test con Python è completa una, non un sostituto per unit test. Quando vengono utilizzati insieme due approcci diversi è possibile creare software migliori e più affidabili.

Inviare domande e commenti per John a testrun@Microsoft.com .

Dr. James McCaffrey funziona per Volt Information Sciences, Inc., in cui ha gestisce formazione tecnica per ingegneri software di utilizzo di Microsoft. Ha lavorato di numerosi prodotti Microsoft inclusi Internet Explorer e MSN Search. John è autore di .NET Test Automation Recipes. È possibile contattarlo all' jmccaffrey@volt.com o v-jammc@microsoft.com .

Page view tracker