Monday, August 28, 2006
One question I get a lot when teaching the VSTS course is "How can I move tasks or bugs from our existing system into TFS?"

The answer is quite simple: write a program that utilizes the Team Systems Object Model.

The Object Model is documented partially in the VSIP SDK -- if you intend to work with it, I highly recommend you go and grab that SDK from Microsoft, but here is a simple example of creating a bug in the first Team Project available on the server named TFSServer (you can imagine the rest which uses ADO.NET to pull your existing tracking tickets from whatever system you have):

   1:  using System;
   2:  using System.Collections;
   3:  using System.Collections.Generic;
   4:  using System.Text;
   5:  using Microsoft.TeamFoundation.Client;
   6:  using Microsoft.TeamFoundation.WorkItemTracking.Client;
   7:   
   8:  namespace EnterWorkItems
   9:  {
  10:      class Program
  11:      {
  12:          static void Main(string[] args)
  13:          {
  14:              TeamFoundationServer tfs = new TeamFoundationServer("TFSServer");
  15:              WorkItemStore wis = (WorkItemStore) tfs.GetService(typeof(WorkItemStore));
  16:   
  17:              Project teamProject = wis.Projects[0];
  18:              foreach (WorkItemType wit in teamProject.WorkItemTypes)
  19:                  Console.WriteLine(wit.Name);
  20:   
  21:              WorkItemCollection wic = wis.Query("Project='Test' AND Type='Bug'");
  22:              foreach (WorkItem wiEntry in wic)
  23:              {
  24:              }
  25:   
  26:              WorkItemType witBug = teamProject.WorkItemTypes["Bug"];
  27:              if (witBug != null)
  28:              {
  29:                  Console.WriteLine("Adding new bug to Team Project {0}", teamProject.Name);
  30:   
  31:                  WorkItem wi = new WorkItem(witBug);
  32:                  wi.Description = "This is a sample bug which was added through the object model";
  33:                  wi.Reason = "New";
  34:                  wi.Title = "You have Bugs! [Ding]";
  35:   
  36:                  wi.Save();
  37:                  Console.WriteLine("Added Work Item # {0} created by {1}", wi.Id, wi.CreatedBy);
  38:              }
  39:          }
  40:      }
  41:  }


Breaking this down a bit, the first main piece of code is the connection to the Team Server itself. This is accomplished on line 14 by creating a new TeamFoundationServer object. An optional constructor allows for credentials to be provided, otherwise it logs on as the current principle.

Next, we retrieve the WorkItemStore. Almost everything in the object model is accessed through the IServiceProvider interface which provides a GetService method where you pass in the System.Type object you want to work with. This is a nice versioning technique that is utilized in many other Microsoft technologies as well.

With the WorkItemStore, you can then query work item type definitions, one of which is necessary to create a WorkItem. You fill in the details for the Work Item you want to create and call the Save method (line 36) to commit the changes to the TFS store. The WorkItem id will then be valid and could be added to the existing bug tracking system as a forward link to the new information if you wanted.
posted on 8/28/2006 5:45:13 AM (Central Standard Time, UTC-06:00)  #   
 Monday, July 17, 2006
I was having lunch with an associate a while back and he mentioned a need to move a work item from one team project to another. While there isn't any direct support for this from the Team Explorer interface, I figured it couldn't be too hard to manipulate the underlying database and achieve the results - it's just SQL Server right? I hadn't actually looked at the schema mind you, I'm an optimist.

It wasn't quite as simple as I thought - it felt like the database had been designed by the security group and intentionally obscured for privacy. The TFS database is fairly generic (so that it can add arbitrary items into the schema for custom project types) and has been somewhat secured to protect the code itself - the stored procedures are encrypted and SQL Profiler doesn't give much information on what's happening.

So, I spent a week or so looking at the schema for the Work Item system and a lot of trial and error. End result is that I got a simple application to move work items around. This application does a couple of other things as well - lists projects, active web services, etc. I was mostly playing with the schema and trying to figure things out. It's just a console application but it does the job.

Here's the code, use at your own risk (a.k.a. if it screws up your system, I can't help you). The source for the simple test project is included so you can see what was done and incorporate it into your own code base if you like.

The program is fairly easy to use - a binary is included with the release. Issuing the command with no parameters generates a simple "Help" page --

C:\Work\TfsCmd>tfscmd
TfsCmd [command] [/t tfsserver] [/u user] [/p password] [params]
ListProjects - Lists the active projects on the TF server.
TfsCmd ListProjects
ListServices - Displays the web services exposed by the TF server.
TfsCmd ListWebServices
WQL - Execute a WorkItem Query.
TfsCmd Wql [query]
MoveWorkItem - Moves a work item from one Team Project to another
TfsCmd MoveWorkItem [id] [ProjectName]

Executing a command will use your logged on user/password (domain credentials) unless you supply a /u and /p parameter. For example, running the ListProjects command generates something like:

C:\Work\TfsCmd>tfscmd ListProjects
Project Name: Test
Status: WellFormed
Guid: 8c5b175b-b0ef-46bb-9a9e-dde05bb145ac
Process Template: MSF for Agile Software Development - v4.0
Defined Properties: MSPROJ

Listing the web services just hits the underlying database to get the information:

C:\Work\TfsCmd>tfscmd ListServices
BuildStoreService = http://(local):8080/Build/v1.0/BuildStore.asmx
BuildControllerService = http://(local):8080/Build/v1.0/BuildController.
IBISEnablement = http://(local):8080/Build/v1.0/Integration.asmx
LinkingProviderService = http://(local):8080/Build/v1.0/Integration.asmx
IProjectMaintenance = http://(local):8080/Build/v1.0/Integration.asmx
PublishTestResultsBuildService = http://(local):8080/Build/v1.0/PublishT tsBuildService.asmx

Moving work items is fairly easy - just provide the work item id (the numeric identifier) and the new project name - this must match an existing project in your TFS system. The tool will attempt to match up the iteration to a value in the new project - if it cannot, it will default to the first iteration available.


C:\Work\TfsCmd>tfscmd MoveWorkItem 1 "Mark's New Project"
Work Item #1: "Create Project Definition" is currently located in project "Test Project (Iteration 0)"
Moving work item to "Mark's New Project (Iteration 0)"

If you have the TFS client open, then you should shut it down and reopen it before modifying the moved work item - the client appears to cache bits of information and I had some issues with moved items if I didn't clear the cache by closing the work item window and reopening it.

Have fun!
posted on 7/17/2006 12:05:53 PM (Central Standard Time, UTC-06:00)  #   
 Monday, May 01, 2006

A colleague of mine, Kev Jones, has posted some information on using a detached SQL Server database for driving VSTS unit tests which works great if you need a full blown SQL implementation.  However, if all you want is a simple data feed, you can also use an Excel file, and as it turns out, it's pretty easy to do.

Let's start by stealing Kev's sample, hopefully he won't mind -- say I have a starship class with a FirePhotonTorpedo method:

public class Enterprise
{
   private int torpedosLeft = 10;

   public int FirePhotonTorpedo(int count)
   {
      if (torpedosLeft < count)
         throw new ArgumentException("count");

      torpedosLeft -= count;
      // Instruct bridge officer to "Fire!"
      return torpedosLeft;
   }
}

The equivalent unit test might look something like:

[TestMethod]
public
void TestFirePhotonTorpedo()
{
   Enterprise target = new Enterprise();
   int torpedoCount = 5;
   int expected = 5;

   int actual = target.FirePhotonTorpedo(torpedoCount);
   Assert.AreEqual(expected, actual, "FirePhotonTorpedo did not return the expected value.");
}

This code just tests a specific case -- I would also need to write other unit tests for edge cases and exceptional cases.  I can do this several different ways I could do this:

1) Write a unit test for each specific case passing each value and testing the expected result.
2) Put all the tests into this one unit test function -- asserting each validation as we go.
3) Pull the input and expected out of a database and run them through the function.

This last step, as Kev details can be done from a SQL database - either one reachable by all the people who might run the unit tests, or as his blog shows from a .MDF file you check in with the project and then locally attach prior to running the unit tests.  Hit the link above for the details on that. 

So, to make an Excel data-driven test, the first step is to create an Excel document with columns for each of our pieces of data.  My sample Excel document has the following columns:

ID A unique number identifying the row so we can use the Random data driven test
torpedoCount The input for our unit test
expected The expected result coming out of the function
shouldFail Whether the function will throw an exception.

I can then enter a single test on each row of the given worksheet.  Here's a screen shot --

You can create multiple "tables" by adding additional sheets to the worksheet.  Each sheet can be named as appropriate; I'm naming this one "TorpedoData".

Now, I need to add the appropriate attribute to my test case to indicate that it should pull the data from a data source and run the method once per row found.  The key is that any ADO.NET data source can be used.  Here I will specify my Excel file which is named "UnitTests.xls" and indicate that the table itself is a particular sheet within the Excel document "TorpedoData":

[TestMethod]
[DeploymentItem("Tests.xls")] // Copies the file to the deployment directory
[DataSource(
"System.Data.OleDb", // The provider
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source='Tests.xls';Persist Security Info=False;Extended Properties='Excel 8.0'",
"TorpedoData$",      // The table name, in this case, the sheet name with a '$' appended.
DataAccessMethod.Sequential)] 

public
void TestFirePhotonTorpedo()
{

}

I also need to update the test case itself to use the data -- we do that through the TestContext.DataRow property that gives us access to the current row of data from our data source:

public void TestFirePhotonTorpedo()
{

   Enterprise target = new Enterprise();
  
int torpedoCount = Convert.ToInt32(TestContext.DataRow["torpedoCount"]);
   int expected = Convert.ToInt32(TestContext.DataRow["expected"]);
   bool shouldFail = (bool)TestContext.DataRow["shouldFail"];

   TestContext.WriteLine("Running test with TorpedoCount={0}, ExpectedCount={1}, ShouldFail={2}",
            torpedoCount, expected, shouldFail);

   try
   {
      int actual = target.FireTorpedos(torpedoCount);
      Assert.AreEqual(expected, actual, "FireTorpedos did not return the expected value.");
   }
   catch (Exception ex)
   {
      if (!shouldFail)
         Assert.Fail(string.Format("FireTorpedo threw exception {0}", ex.Message));
   }
}

So, here we will grab the data from the current row, converting it as necessary to the appropriate types, output a test line just to prove that we executed the method more than once and then run the test.  The test will compare the result with the expected database result and output a failure if they aren't the same.  If an exception is thrown, then the shouldFail must be true or that will be considered a failure.

This approach allows me to run through different scenarios very easily and I can just store the Excel worksheet right with my unit tests - make sure it's deployed to the target deployment directory (either through a DeploymentItem attribute, or through the test run configuration).  The size of my table here is about 14K, compared to the equivalent .MDF file which is over 2M for the same data.  If I didn't want to hardcode the filenames and such, I can also use an app.config file for the unit test and put the information there - just as Kev details.

Cool stuff indeed.

posted on 5/1/2006 10:55:40 AM (Central Standard Time, UTC-06:00)  #   
 Wednesday, August 24, 2005
Installing VSTS in VMWare
posted on 8/24/2005 2:37:19 PM (Central Standard Time, UTC-06:00)  #