June 2009

Volume 24 Number 06

Test Run - .NET Module Testing with IronPython

By James McCaffrey | June 2009

Code download available

Contents

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

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 1to 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 1in 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.

Figure 1 Ad Hoc Testing with IronPython on the Command Line

>>> 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>

fig02.gif

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.

Figure 3 The Card Class of the Library under Test

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 4I implement four private helper methods. As you'll see, private methods are not exposed to an IronPython test script.

Figure 4 Private Methods

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.

Figure 5 The Beats Method

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 1indicates 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 6produced the output shown in Figure 2.

Figure 6 File harness.py

# 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.comor v-jammc@microsoft.com.