| James's profileJames McCaffreyBlogLists | Help |
|
December 29 Testing SQL Stored Procedures using LINQLINQ (Language Integrated Query) is new set of technologies that ship with the .NET Framework 3.5 (which is included as part of Visual Studio 2008) which allow you to access and manipulate data sources directly from a .NET language in a way that is consistent across different data sources. I've been looking at LINQ and have discovered that using LINQ is a great way to test SQL stored procedures. For example, suppose you have a database named dbMovies which contains a stored procedure named usp_GetMoviePrice. The stored procedure accepts a movie ID as input and returns the price of the movie. Testing such a stored procedure using normal ADO.NET techniques is possible but somewhat awkward. But here's a glimpse of how easy the task becomes using LINQ:
Console.WriteLine("\nBegin experiment");
var dbc = new dbMoviesDataContext();
decimal? actualPrice = null; string movieID = "002"; decimal expectedPrice = 22.22M; dbc.usp_GetMoviePrice(movieID, ref actualPrice); if (actualPrice == expectedPrice) Console.WriteLine("Pass"); else Console.WriteLine("FAIL"); Console.WriteLine("\nEnd experiment");
Very, very clean compared to alternate techniques. I am writing up an article which fully explains testing SQL stored procedures using LINQ for Microsoft's MSDN Magazine, and the article should appear in my Test Run column (http://msdn.microsoft.com/msdnmag/find/default.aspx?phrase=Test%20Run) in early 2008.
December 22 Installing Team Foundation Server 2008I've spent the past few days setting up a Visual Studio 2008 Team Foundation Server machine in a small network I use for training classes and to research topics for articles I write for Microsoft's MSDN Magazine. The installation process was not as smooth as it could have been so I figured I'd jot down some of my experiences to help out anyone who might be installing TFS 2008, and to record some of the details for myself to refer to when I install TFS 2008 again. To summarize, the installation document, which is embedded into the TFS 2008 setup.exe (if you launch setup you get a menu where one of your options is to view the installation guide), is fairly well-done but there are a couple of glitches that could catch you. I strongly suggest you go through the entire installation document before you begin installing because many mistakes are not recoverable in practice.
My environment is a small-scale TFS 2008 scenario where everything (TFS 2008, the backend SQL Server 2005, and SharePoint Services 3.0) is on a single machine, and security is not a major concern. First, start with a blank machine. Do not even think of trying to install TFS on a machine with any existing software. My machine is an old Dell Optiplex GX280 desktop with 512MB of RAM. This is severely under-powered and you really need at least 1.0GB of RAM for a realistic small-scale scenario. I installed Windows Server 2003, and associated Dell drivers (audio, video, network, chipset). Then I configured IIS 6.0 and ASP.NET on the server. After a reboot, I applied Windows Server 2003 Service Pack 1. Finally I joined the machine to my little research network, and then logged onto the machine as a domain administrator. No problems so far.
One of the more confusing parts of the installation instructions revolves around accounts and permissions. The documentation is inconsistent in several minor spots, but just enough to cause confusion. Anyway, I created domain accounts TFSSETUP and TFSSERVICE, and assigned them to the Administrators group on the local machine. The documentation says not to do this for account TFSSERVICE but security is not a major concern for me and security access problems can be nightmarish. This allowed me to skip going to Local Security Policy, User Rights Assignment, and messing around with log-on-as-a-service and allow-log-on-locally entries because Administrators get all that (and more of course). Also, forget about a separate TFSREPORTS account for SQL reporting services, and I didn't need a TFSPROXY account because I'm putting everything on one machine. I logged off the server and logged back on as TFSSETUP.
The next step is to install SQL Server 2005. The installation guide is very good here. For Components I selected everything except for Integration Services and Notification Services. For Instance Name I accepted the default. For Service Account I used TFSSERVICE. For the Startup Services I selected all five of them. For Authentication Mode I selected Windows Authentication. For Collation Settings I accepted the default. Now in the Report Server Options be sure to select the non-default "Install But Do Not Configure". I did not select any of the Error Report settings. After SQL Server installation completed, I rebooted and applied SQL Server SP2 -- I had very bad experiences with SP1 and strongly recommend SQL SP2 instead. After another reboot I was ready to install TFS 2008.
After installing SQL, I downloaded the KB925673 patch for MSXML 6.0 parser, but when I started its installation, the setup program told me I didn't need the patch. (I think SQL SP2 upgraded the MSXML parser).
I was logged on as account TFSSETUP. After the splash screen and license agreement stuff, for Destination Folder I accepted the default. For Database Server I accepted the default, which was the name of the local machine and was automatically filled in. The System Health flagged my machine as being under-RAMed but didn't block setup from continuing. For the Service Account I selected the non-default "Specify an Account" option and entered mydomain\TFSSERVICE and its password. For Reporting Services I selected the "Use Team Foundation Server account" option. For Windows SharePoint Services, I selected "Install SharePoint Services 3.0 on this computer". For the SharePoint Account I selected the "Use Team Foundation Server account" option. For Alert Settings, I did not check anything. With my fingers crossed, I clicked Install, and after some time got a Success message.
Next, after a reboot for safety, I installed Team Foundation Build. Here I followed the installation document exactly. After the Welcome, License, and System Check, for the Destination Folder I accepted the default. For Service Logon Account I typed in mydomain\TFSSERVICE and its password. After clicking Install, I eventually got a Success message. But on a reboot I received a One or More Services Failed to Start error message. By checking the System Event Log I noticed the error was something to the effect that a Server Scheduler could not contact the remote database. I tried various things without success. I suspect that, because both SQL and TFS are on the same machine, after a reboot TFS is looking for SQL which hasn't started up yet. My work-around was to go to Services, and change the "Visual Studio Team Foundation Server Scheduler" service startup type from Automatic to Manual. Next I installed Team Explorer without incident so I can manage the TFS machine locally.
Well, there you have it. Once the TFS was up and running, I then created a Team Project, configured overall server group membership, added a Visual Studio Web Application Solution to the project (typically forgetting to do a View | Other Windows | Pending Changes and checking in the new files,) configured project membership, and manually created Workspace mappings on all the client machines in the training lab. But that's another blog story.
== Addendum: Notes for Setting up a Web Project on TFS
Log on to the TFS server machine with the account used to install TFS. Connect to TFS using Team Explorer (Tools | Connect to Team Foundation Server). Add a few additional Server Administrators by right-clicking on the TFS server name in TE, and selecting Group Membership. Right-click on the TFS Server name and chose New Project. Accept all the defaults. The project takes about 5 minutes to create. Note: You can delete demo projects using the command-line utility tfsdeleteproject.exe with "/force /server:vtexxx ProjectName" arguments. After the project has been created, right-click on the project name and add a few accounts to the Project Administrators group, and add Domain Users to the Contributors group. Now go to some client machine and create a file system based Web project. Build the project. In general you'll have files Default.aspx, Web.config, and a .sln file. You will have a Default.aspx.cs file unless you created a blank project and then manually added code and de-selected the default "place code in separate file" option. Connect to the TFS and select the appropriate project. Now do a File | Add Solution to Source Control. After that process be sure to do a View | Other Windows | Pending Changes and then Check In the files. Now, for each client machine, including the machine you used to create the Web project, you must create a Workspace. Note: a bogus Workspace will be created on the development machine. Go to File | Source Control | Workspaces. Delete any existing Workspaces because if they are mapped to the local directory you want to use you'll be blocked. Note: this means that if you're ever going to retire a TFS machine, you want to connect each client to the project and Remove any Workspaces before blasting the TFS machine away. In the source path, select the root of your project, not any sub-directories. In the destination path, select any convenient directory. Now you should be good to go. You can do a File | Source Control | Open From Source Control. TFS will warn you that you are going to overwrite existing files in your destination directory, but that is usually what you want.
December 17 LINQI have been looking at LINQ (Language Integrated Query) over the past few weeks. LINQ ships with .NET Framework 3.5 which is part of Visual Studio 2008. I think of LINQ as a set of language extensions to C# (or VB.NET, etc.) that allow you to access and manipulate data from different types of data stores (SQL, XML, etc.) in a consistent way. For example, if I have a SQL database which contains a table named tblEmployees which in turn contains columns empid, last, first, and dob, then this code will populate object "results" with the data for employees whose ID is greater than or equal to 002:
var db = new DataContext("Server=.; Database=dbEmployees; Trusted_Connection=true");
var results = from emp in db.GetTable<TblEmployees>() where emp.Empid.CompareTo("002") >= 0 select new { id = emp.Empid, last = emp.Last, first = emp.First, dob = emp.Dob }; However, before executing these statements I have to create a mapping class which tells my program about the structure of the SQL database. There is a command-line utility named sqlmetal.exe that can do this. There is a lot gong on in the code above including anonymous data typing (var), lambda expressions, and a new object constructor syntax. Now suppose I have similar employee data but in a text file. I could extract data with code that looks like:
List<Employee> employees = new List<Employee>{};
// fill object employees
var results = from emp in employees
where emp.EmpID.CompareTo("002") >= 0 select new { id = emp.EmpID, last = emp.Last, first = emp.First, dob = emp.Dob }; Notice the LINQ syntax is very similar. Again though, I have to create some mapping code, in this case an Employee class, before I can use LINQ.
Several things might happen with LINQ. LINQ may become almost universally used and replace older techniques for the most part. Or, LINQ may be one of those technologies that just never catch on and quickly fade into obscurity. Or, LINQ may be used only in somewhat complex coding scenarios where the overhead in using LINQ pays off with better error-checking and maintainability.
December 10 Testing Windows WPF ApplicationsI've been looking at WPF (Windows Presentation Foundation) applications. Although the ability to create WPF apps has been available since the release of the .NET Framework version 3.0 about a year ago, there hasn't much WPF development because the supporting tools weren't there. But with the release of Visual Studio 2008 and the associated .NET Framework version 3.5 a few weeks ago, I expect WPF applications will gain traction quickly. A major characteristic of WPF applications is the ability to separate UI display code from logic code; Windows applications based on .NET Framework 2.0 intermingle UI code and logic code, but with WPF applications you can have two completely separate files. The UI portion of WPF applications is based on XAML (Extended Application Markup Language) which is a type of XML. For example, consider this file named MainWindow.xaml:
<Window x:Class="SomeNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="The Main Window" Height="250" Width="250"> <StackPanel Margin="60"> <Button Name="button1" Width="90" Click="button1_Click" Background="Wheat"> Click Me </Button> <TextBox Name="textBox1" Width="120"/> </StackPanel> </Window> This file defines a simple main Window which contains a Button control and a TextBox control. This XAML file also specifies that the logic code for this application resides in a C# class named MainWindow which is part of a namespace named SomeNamespace. I put the C# code in a file named MainWindow.xaml.cs and compiled the app using the MSBuild.exe program. You can see a screenshot of the resulting app. This is a very simple but powerful concept. The separation of UI code from logic code means, among other things, that UI development can be turned over to a graphics specialist. The specialist can use Visual Studio or more powerful design tools such as Expression, save the result as a XAML file, pass the XAML file to a developer who can then write logic code. Now from a testing point of view, the resulting application uses the new WPF-based graphics subsystem instead of the normal Win32 based subsystems, which means that normal UI test automation techniques do not work. I haven't tried it out yet but I believe that the new MUIA test automation library can deal with WPF applications. December 02 Parsing XML Files with PowerShellIn the context of using Windows PowerShell for lightweight software test automation, one of the most common tasks you need to perform is parsing data from XML files. For example, you may want to extract test case input and expected result data from an XML test cases file, or you might want to pull out results data from an XML test results file. Compared to parsing a flat text file, parsing most XML files is a bit tricky because of XML's hierarchical structure. There are several approaches you can take when parsing XML with PowerShell. In general, the most flexible technique is to read the entire XML file into memory as an XmlDocument object and then use methods such as SelectNodes(), SelectSingleNode(), GetAttribute(), and get_InnerXml() to parse the object in memory. Let me demonstrate with typical example. Suppose you want to parse this dummy XML test case data file:
<?xml version="1.0" ?>
<testCases> <testCase id="001">
<inputs> <arg1 optional="no">3</arg1> <arg2>4</arg2> </inputs> <expected>7</expected> </testCase> <testCase id="002">
<inputs> <arg1 optional="yes">5</arg1> <arg2>6</arg2> </inputs> <expected>11</expected> </testCase> </testCases>
The dummy file represents test case data for a hypothetical Sum() method. Listed below is a PowerShell script which parses the XML file and produces as output: PS C:\XMLwithPowerShell> .\parseXML.ps1
Parsing file testCases.xml
Case ID = 001 Arg1 = 3 Optional = no Arg2 = 4 Expected value = 7
Case ID = 002 Arg1 = 5 Optional = yes Arg2 = 6 Expected value = 11 End parsing
The complete script is:
# parseXML.ps1
write-host "`nParsing file testCases.xml`n"
[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument $file = resolve-path("testCases.xml") $xd.load($file) $nodelist = $xd.selectnodes("/testCases/testCase") # XPath is case sensitive
foreach ($testCaseNode in $nodelist) { $id = $testCaseNode.getAttribute("id") $inputsNode = $testCaseNode.selectSingleNode("inputs") $arg1 = $inputsNode.selectSingleNode("arg1").get_InnerXml() $optional = $inputsNode.selectSingleNode("arg1").getAttribute("optional") $arg2 = $inputsNode.selectSingleNode("arg2").get_InnerXml() $expected = $testCaseNode.selectSingleNode("expected").get_innerXml() #$expected = $testCaseNode.expected write-host "Case ID = $id Arg1 = $arg1 Optional = $optional Arg2 = $arg2 Expected value = $expected" } write-host "`nEnd parsing`n"
The first three statements of the script load file testCases.xml into memory as an XmlDocument object:
[System.Xml.XmlDocument] $xd = new-object System.Xml.XmlDocument
$file = resolve-path("testCases.xml") $xd.load($file) I could have loaded the XML file in a single line like so:
[xml] $xd = get-content ".\testCases.xml"
Using the three-statement approach in the script has no technical advantage but is somewhat more readable by an engineer with C# coding experience. Next I fetch the all testCase nodes into memory:
$nodelist = $xd.selectnodes("/testCases/testCase")
The SelectNodes() method accepts an XPath string which is case-sensitive. With the testCase nodes now in memory I can iterate through each node with a foreach loop. Alternatively I could have iterated using a for loop with an index variable (say $i) in conjunction with the Item() method. For each node, I first fetch the test case ID attribute:
$id = $testCaseNode.getAttribute("id")
I use the GetAttribute() method of the XmlElement class. Interestingly I could have written this instead:
$id = $testCaseNode.id
This alternative illustrates an important point. In an effort to make parsing XML with PowerShell easier than with C# or VB.NET, the designers of PowerShell decided to directly expose attributes and values of XML elements in the form of properties. But since arbitrary XML data is available as properties, PowerShell does not expose standard .NET Framework properties (such as InnerXml) because there could be a name conflict. Note that PowerShell does expose standard .NET Framework methods such as GetAttribute(). Continuing in my script, next I grab the values of arg1:
$inputsNode = $testCaseNode.selectSingleNode("inputs")
$arg1 = $inputsNode.selectSingleNode("arg1").get_InnerXml() $optional = $inputsNode.selectSingleNode("arg1").getAttribute("optional") I use the SelectSingleNode() method to grab the single <input> node. Now instead of using the standard InnerXml property, which PowerShell does not expose, I use the underlying PowerShell get_InnerXml() method which corresponds to the non-exposed InnerXml property. OK, but just how did I know about this get_InnerXml() method? As with many PowerShell scripting tasks, before writing my script I had previously experimented by issuing interactive commands at the PowerShell prompt. For example, after interactively loading the XML file into memory (by typing the first three statements in my script), I typed commands such as:
> $nodelist = $xd.selectnodes("/testCases/testCase")
> $firstnode = $nodelist.item(0) > $inputs = $firstnode.selectSingleNode("inputs") > $arg1 = $inputs.selectSingleNode("arg1") > $arg1 | get-member | more Using the get-member cmdlet is the key to discovering exactly what properties and methods are available to an object. Anyway, the rest of the script should be reasonably self-explanatory because I use the same coding techniques. To summarize, although there are several ways to parse an XML file using PowerShell, a flexible approach is to use the XmlDocument class. After reading an XML file into memory as an XmlDocument object, you can select multiple nodes into a collection using the SelectNodes() method, grab a single node using the SelectSingleNode() method, retrieve an attribute using either the standard GetAttribute() method or the name of the attribute which PowerShell exposes as a property, and you can obtain an element value using the special get_InnerXml() PowerShell method.
|
|
|