Whenever more than one person works on a software development project, introducing some process to coordinate the activities of the team members is a priority.

The larger the team, the harder it is to manage. To meet this need, Microsoft created Visual Studio Team System (VSTS). VSTS is a state-of-the-art Software Development Life Cycle tool suite that is tightly integrated into Microsoft Visual Studio 2005. VSTS provides deep support for .NET projects; however, whenever a software solution includes components developed on a platform other than .NET, such as Microsoft Visual FoxPro (VFP), VSTS loses some of its value because the projects aren’t integrated into VSTS. Leveraging the extensibility features of VSTS and VFP, this article will help you integrate VFP projects into VSTS team projects enabling your team to apply a comprehensive process to your entire software development effort.

VSTS is comprised of client applications and a Team Foundation Server (TFS). The client applications are available in several flavors of Visual Studio 2005 targeting a variety of roles including developers, testers, architects, and most recently, database administrators. You can install the client application for accessing the TFS, Team Explorer, in an existing Visual Studio 2005 installation or as a stand-alone application. Team Explorer provides the user interface for all of the TFS features; when installed on top of Visual Studio, Team Explorer is tightly integrated into the Visual Studio IDE.

Whenever a software development effort involves more than one development environment, it significantly complicates the software development lifecycle.

TFS is composed of a logical application tier and a logical data tier. The logical application tier exposes a Web service API to the Team Explorer client and any other client that writes to the API. The logical data tier leverages SQL Server 2005 for storage as well as SQL Server Reporting Services and SQL Analysis Service for reporting. Additionally, TFS provides an intranet portal built on Windows SharePoint Services, which provides a file repository and a collaboration tool.

Software Development the Team System Way

While VSTS can support any development process, the out-of-the-box feature set supports a development life-cycle as shown in Figure 1. The process starts with the creation of a work item.

![Figure 1: Common development life cycle supported by VSTS.](https://codemag.com/Article/Image/0703102/Figure 1.TIF)

The work item, a Scenario in this example, represents some capability the users would like the software to support. Once the Scenario is assigned to a developer for implementation, they write the code and one or more unit tests. You may write the tests first if you’re using Test Driven Development.

Once all of the unit tests pass and the developer determines the code is ready for further testing, they check the code into TFS version control.

A build is initiated as a result of the check-in event (Continuous Integration), a scheduled event (Daily Build), or a user action. The build retrieves all of the source code from the version control repository, builds each executable, and places the executables in a drop folder, typically on a network share.

In addition to building the binaries, the build process can also run build verification tests, which ensure that the individual units still function properly when integrated. If a project fails to build or if any build verification test fails, the process stops and TFS creates a bug work item. You could then assign this bug to a developer to correct. Once the developer fixes the problem and checks in the changes, you can attempt the build again.

When the build completes successfully and all build verification tests pass, the executables in the drop folder can undergo system testing and user acceptance testing. When these tests pass, the product is ready for wider release.

As indicated in Figure 1, TFS supports the development life cycle by providing the following:

  • Work item tracking
  • A testing framework that supports unit testing, build verification testing, load testing, and manual testing
  • A version-control repository
  • A build service that supports automated build testing

VFP Integration Challenges

Whenever a software development effort involves multiple development environments it complicates the software development life cycle. Integrating VFP with VSTS presents a number of significant challenges.

Work Items

Visual FoxPro’s Task List provides a local repository of tasks and provides limited extensibility. VFP is not designed to support a shared task list so tasks can be managed by others. VFP also doesn’t support workflow to guide tasks through to completion in compliance with team policies.

Work items are the lifeblood of the TFS software development process. You use work items to record what needs to be done, who needs to do the work, and to provide an immutable history of what has been accomplished.

Developers who spend most of their day working in the VFP IDE need convenient access to the work item repository-not only to see and update their assigned work items, but also to create new work items for themselves and others. While Team Explorer provides such access, the context switch of leaving the development environment to edit work items puts VFP developers at a disadvantage.

Automated Testing

Development teams increasingly use automated unit testing and automated build verification testing to both increase the quality of software and to allow software to be easily refactored. It is essential that VFP developers have the ability to create unit tests and build verification tests for VFP applications and to automate the execution of these tests if we are to be first-class members of a multidisciplinary team.

Version Control

In addition to the ability to simply store VFP source code, you need to be able to take advantage of the advanced features of the TFS version-control repository.

For example, it is possible to establish a check-in policy that requires a work item be associated with every check in. This provides a means for identifying all code changes necessitated by a specific work item and provides a way of justifying each code modification. VFP developers need to be able to conform to the policies of their team without being forced to leave the development environment.

Other version-control features are equally important, including shelving, branching, and merging. The challenge is to make these features easily available to VFP developers.

Build Automation

Automating the build process is another technique used to increase the quality of software. By having a consistent build process, a team ensures that they can compile and assemble all the required components and that the executables pass verification testing. Establishing a minimum baseline of quality before further testing begins allows teams to find and fix problems sooner-saving both time and money. The challenge is to automate the building of VFP executables and automate the execution of VFP tests.

Integrating VSTS and VFP

Full integration of the VFP IDE with the TFS API is a lofty goal. VFP is not a “traditional” programming language with a command-line compiler and linker. VFP has always been an integrated development environment with a focus on developer productivity.

Visual Studio Team System is an enormously powerful tool that offers to both simplify and improve the software development process.

However, both VFP and VSTS are extensible and you can leverage this extensibility to facilitate incorporating VFP into the VSTS development cycle. In fact, VSTS extensibility is so deep that there is usually more than one way to get something working. The approaches presented here are usually the simplest possible, but you might create other approaches that are more suitable to your situation.

Version Control Integration

Team Foundation Server Version Control (TFSVC) is an all-new, version-control repository for any project artifacts including source code, graphics, and documentation. TFSVC is built on top of SQL Server 2005, and you access it through Web services. This combination makes it robust and scalable while providing remote access in the core product. TFSVC is a more capable solution for version control than Visual SourceSafe (VSS). Internally, Microsoft’s developers are gradually moving their source code to an internal repository built on TFSVC.

Three options exist for integrating VFP with TFSVC. Use the MSSCCI provider for TFSVC, use TFSVC Web services, or use the VSTS Team Explorer as a stand-alone, version-control application.

Microsoft developed the Microsoft Source Code Control Interface (MSSCCI) when they integrated SourceSafe into their development environments. The MSSCCI API is supported in Visual Studio 2003, Visual Basic 6, and Visual FoxPro. Originally, the team responsible for TFS ignored all legacy development tools and focused exclusively on the new source code control API for Visual Studio 2005. At Tech Ed 2005 in Orlando, the team announced they would provide an unsupported MSSCCI provider for legacy tools.

Initially the tool supported only the basic source control functionality, but as the team realized the popularity of this unsupported add-on, they added additional features. Currently, the MSSCCI provider for TFS is an officially supported product. MSSCCI also provides limited ability to edit work items through the source control, check-in dialog box.

This ability to edit work items is good news for developers working with Microsoft technologies outside of Visual Studio 2005, such as Visual FoxPro. If you have ever worked with VSS integration, you will find the new provider for TFS familiar. The level of functionality available is enough to continue to develop VFP applications.

The MSSCCI provider does have some significant drawbacks you need to consider if you decide to use it. Operations on large groups of files can be unacceptably slow. This slowness is a significant problem for large projects.

When checking in multiple files using the MSSCCI provider, VFP checks in each file individually. This individual check-in creates multiple change sets of one file each, rather than a single change set containing multiple files. The primary unit for versioning in TFS is the change set and ideally you want a change set to contain all of the changes required to implement a unit of functionality.

In addition, you have to come up with a strategy for managing changes to the project file. If different developers add or delete files at the same time, you have to find a way to merge these changes into a new project file that you can then propagate to the rest of the team.

Once you get the hang of it, the VFP Update Project List feature does a decent job of managing this for you by placing a text representation of the project under source control and merging changes into the text representation before recreating the local project from the text representation.

You can use a hybrid approach. Use the MSSCCI provider to update the project list, get latest, and check out individual files and use Team Explorer for everything else. This approach requires some discipline from the team but is workable.

Most importantly the MSSCCI provider does not provide access to the more powerful and innovative TFSVC capabilities, such as the new shelving feature.

You can use the source-control explorer in Team Explorer to manually manage your files, just as you were able to use the Visual SourceSafe client outside of the VFP IDE. While using Team Explorer is technically not integration, it does provide full access to the TFSVC feature set. Of course it’s possible to use some of the features of the MSSCCI provider and use Team Explorer for everything else.

What is new, however, is the ability to pursue a third route. The Team Explorer client integrates directly with the Visual Studio IDE through an API that is exposed to developers as .NET classes. This API is published in an SDK and anyone interested can write their own front end to the TFS. For instance, this approach has been used by Teamprise (http://www.teamprise.com) to integrate the Eclipse IDE with TFS; DevBiz (http://www.devbiz.com) created an HTML interface called TeamPlain; and PersonifyDesign (http://personifydesign.com/) developed TeamLook, which is a TFS Outlook add-in.

Because the client API is exposed through .NET assemblies, VFP does not have native access to them. This lack of access forces you to produce a wrapper, or façade, class which presents the .NET API through a COM interface using .NET Interop. This is easier to do than it sounds, and you will easily understand the fundamentals when you review the code in Listing 1.

Microsoft required COM support when they began development of what became the .NET Framework. I encourage anyone interested in studying the details and limitations of interoperability between VFP and .NET to read Rick Strahl’s excellent white papers at http://west-wind.com/Articles.asp. There, you will find a wealth of help in implementing your own interop solutions.

Source control consists of a handful of basic actions such as authentication with the server, getting a file, checking a file out for editing, checking in changes, and reverting changes. I will produce a COM-visible assembly, which will allow you to perform a get action from within the VFP development environment. Once you have these functions working properly, it should be a straight-forward task for an interested reader to produce a functional user interface to interact with this component.

You will need to have the Team Explorer client installed as it installs the .NET assemblies, which comprise the TFS client API. You may also need to correct the references in the sample C# project if your copy of Team Explorer is installed in a nonstandard location.

Integrating VFP Unit Tests

FoxUnit (http://www.foxunit.org/) is a unit testing framework for VFP created by the late, great Drew Speedie and the team at Visionpace. FoxUnit, shown in Figure 2, is a test framework for creating and running unit tests. Listing 2 shows an example of a FoxUnit test.

![Figure 2: The FoxUnit main form.](https://codemag.com/Article/Image/0703102/Figure 2.TIF)

While a detailed explanation of unit testing, testing frameworks, and the FoxUnit test framework is beyond the scope of this article, the concepts are fairly simple. Unit testing frameworks exist for many programming languages. Using a framework simplifies the creation and execution of unit tests, which in turn reduces the barriers to creating automated tests.

By having a consistent build process a team ensures that all the required components can be compiled and assembled correctly and that the executables pass verification testing.

A FoxUnit unit test is derived from a FoxUnit test case class and a typical test would instantiate the object being tested as shown in the following snippet:

function setup 

this.ObjectUnderTest = newobject( ; 
"HelloWorld", ; 
"HelloWorld.prg", ; 
"HelloWorld.exe" ) 

endfunc 

Then test methods of the test class call methods of the object being tested, passing it valid and invalid values and measuring the results to determine if the test passed or failed. Here is a simple example:

function TestObjectHelloWorld 

this.AssertEquals("Object.HelloWorld() did" + ; 
" not return 'Hello World!'", ; 
"Hello World!", ; 
this.ObjectUnderTest.HelloWorld()) 

endfunc 

VSTS has a mechanism for calling external unit testing frameworks. This mechanism is the Generic Test. I’ll discuss the details of VSTS generic tests in a moment. For now there are three important things to know about generic tests: You must be able to call the test framework from a command line, it must return a 0 if the test passes or 1 if the test fails, and it can create summary and detailed results files that you can incorporate into the final test results.

Each of these requirements poses a problem for VFP and FoxUnit. The first problem is that while VFP is callable from the command line, there is no way to return a value to the command line to indicate the results of the test. Addressing this issue requires writing a wrapper around a VFP COM component that will in turn wrap the FoxUnit test framework to execute the test, gather the results, prepare the results files, and return the results of the test.

Listing 3 is the FoxPro code that wraps up FoxUnit and exposes it as a COM component. You drop this code into the main FXU.prg file and add all of the FoxUnit source to a VFP project and compile it into a DLL. Using a DLL has advantages because you are automating the execution of the test and you need to suppress raising any modal dialog boxes. VFP raises modal dialog boxes with very little provocation. Building FoxUnit as a DLL ensures that any attempt to raise a model dialog box will generate an error that will cause FoxUnit to terminate and the test to fail.

FoxUnit is a well-designed application that uses proper layering techniques to isolate the core functionality from the user interface. This layering greatly simplifies the job of turning FoxUnit into a COM component. The code is essentially the same code from the main body of an FXU program, removing the code that instantiates the UI. Code is added to create an object to capture the test results, call FoxUnit to run the test, and create the summary and detailed results files. Other than this no other FoxUnit changes are required.

Before you can call the FoxUnit COM component to run a test, you must address the issue of passing a return value back to the command line. Because FoxUnit is now a COM DLL, you can’t execute it directly from the command line. Listing 4 contains a simple C# program that instantiates the FoxUnit COM component, passes it the command line arguments, and returns the results of the test back to the command line.

Note the use of log files in both applications to capture debugging information. Since you are dealing with a process where Visual Studio is calling a .NET application that, in turn, calls a Visual FoxPro COM object, you can’t launch a debugger to see why the code isn’t working. The logs provide some visibility into the executing code, but the log really helps when the test runs fine in the FoxUnit UI and fails when automated.

Once you are able to run the test from the command line, it needs to be launched from the VSTS test framework. This is done using the Generic Test type, which you can create by selecting New test from the Test menu, and then selecting Generic Test. Be aware that Generic Tests are only available in the Test edition and Team Suite edition of VSTS. Figure 3 shows how VSTS defines a Generic Test.

![Figure 3: Creating a generic test.](https://codemag.com/Article/Image/0703102/Figure 3.TIF)

A Generic Test is a wrapper around the command-line execution of a test executed by another test tool. To get the Generic Test to run the FoxUnit test, tell the Generic Test to call the .NET wrapper, RunFoxUnitTest.EXE, passing in the test class name, the test method name, the directory where test should be run, and the name of the results file that the test should create. These parameters are passed to the .NET application that, in turn, passes them to the FoxUnit COM component, which calls FoxUnit and runs the test.

It is important to indicate that the test will generate a summary results file; make sure that the file location matches the file location specified on the command line. Doing this allows access to the results file from within the Visual Studio IDE. It’s also important to set the test-run duration. The default is six minutes. If you expect your test to take longer than this, increase the duration value. If you are debugging the .NET and COM wrappers, it can be useful to decrease the duration value and let VSTS terminate the test after a few seconds. If you do, be sure to terminate the VFP process started by the test framework.

You can run the test in one of two ways:

  • Select one of the “Start Selected Test Project” options on the Test menu, or
  • Right-click the test in the Test View window (shown in Figure 4) and run the test.

![Figure 4: The Test View window showing the generic test.](https://codemag.com/Article/Image/0703102/Figure 4.TIF)

Figure 5 shows the results of all tests executed in the VSTS Test Results window. If you right-click the generic test and select “View Test Results Details” you see the contents of the summary and detailed results files as shown in Figure 6.

![Figure 5: The Test Results window showing the passing test.](https://codemag.com/Article/Image/0703102/Figure 5.TIF)

![Figure 6: The detailed results of running a FoxUnit test in VSTS.](https://codemag.com/Article/Image/0703102/Figure 6.TIF)

This exercise is a lot of work just to run a VFP unit test-something you can do easily from inside the VFP IDE. What you’ve gained is the ability to run this test from an automated process. I’ll use this automated process later when automating build verification tests. One limitation to this approach is that each FoxPro unit test requires a separate VSTS Generic Test. This requirement will get cumbersome as the number of tests increases. A better approach would be to use a single Generic Test to run many FoxPro Unit tests. The format of the XML results summary file supports this approach and it should be a simple modification to add this approach to a FoxUnit COM wrapper.

There is another approach to unit testing worth considering. If you expose all of your FoxPro classes as COM components you can use .NET unit tests instead of FoxUnit tests. This approach eliminates the need for the Generic Tests, the .NET application, and the FoxUnit COM wrapper. However, for productivity reasons it’s preferable that you do unit testing in the same environment and in the same language as the coding. This approach is probably better suited to build verification tests, particularly if you’re already exposing your FoxPro classes as COM components.

Integrating VFP and Team Build

How you incorporate Visual FoxPro projects into a Team Build depends, to some degree, on how you use TFS Version control with VFP project files. If you’re using the MSSCCI provider, you have to deal with the fact that FoxPro does not store the project files (PJX, PJT) needed to build the project in the version control system. FoxPro rebuilds the project file each time you execute the “Update Project List” command in the Visual FoxPro IDE; there is no way to automate this process so that it does not require user intervention.

Unfortunately VFP does not expose the ability to recreate the project files from the PJM file requiring that you recreate this functionality so that it can be added to the build process.

A related issue that you have to contend with regardless of how you manage the project files is dealing with the Locate File dialog box, shown in Figure 7. VFP presents this dialog box whenever you open the project and the project can’t locate one of the files in the project. Of course, you have to open the project file in order to build the project. If the project has a reference to a file that is not is the version control repository, this dialog box will prevent the Team Build from completing, which requires that you manually stop the VFP process.

![Figure 7: The Locate File dialog box.](https://codemag.com/Article/Image/0703102/Figure 7.TIF)

Dealing with “Locate File” problems requires that you pre-validate the project file before you use VFP to open the project. This pre-validation is preferable to relying on the dialog box to raise an error in a DLL because it is better to report a missing file back to the build process than to report the cause of the problem as “User-interface operation not allowed at this time. (Error 2031)”.

Once you resolve these issues, you must build the project into an executable and compile any errors so that you can include them in the build report.

Listing 5 is a FoxPro program that rebuilds project files from a PJM file, validates that all of the files in the project exist, builds an executable from the project, and creates a result file containing any build or compile errors. Some or all of this program will be useful to you depending on how you build your FoxPro projects.

Rebuilding the project from the PJM file first requires that you parse the PJM file. The program in Listing 5 parses the file into an object that represents the project file and its properties and a collection of project file objects that represent the files contained in the project. The program uses this data to create the project file, which the program creates in four steps.

Files that are not found are logged for inclusion in the build report.

The build process uses the existence of an .ERR file as an indication that the build failed.

You now have the capability to build a VFP project without user intervention. Now you need to incorporate this capability into the Team Build process.

Team Build is a feature of TFS that distributes the process of building software. Using Team Build, any developer can initiate a build by a scheduled process or by a TFS event. The build can run on any properly configured build machine. The build itself is performed by the Microsoft MSBuild build engine and all of the features of MSBuild are available to Team Builds. You can copy the binaries created by the build to any network share. You can also run automated tests to verify the build; Team Build incorporates the results of the build into the TFS data store where build failed events can trigger e-mails and create bug work items.

Team Build encapsulates the procedure for accomplishing a build in a Build Type stored in the Build Types folder of the Team Project. Integrating VFP projects into Team builds requires modifying an existing Build Type. To create a new Build Type, right-click the Build Types folder and select “New Build Type” from the shortcut menu. This command launches the first page of the New Build Type Creation Wizard, shown in Figure 8.

![Figure 8: Creating a new Build Type-Naming the build.](https://codemag.com/Article/Image/0703102/Figure 8.TIF)

On the first page of this wizard you provide a name and description for the build. Figure 9 shows the second page of the wizard where you select the solutions to build. The wizard populates this list with all of the solutions contained in the version-control folder tree associated with the team project. If there is more than one solution and build order is important, you can also reorder solutions in this dialog box.

![Figure 9: Creating a new Build Type-Selecting and ordering the projects.](https://codemag.com/Article/Image/0703102/Figure 9.TIF)

Figure 10 shows the Configuration page where you can select the type of configuration to build. Figure 11 is the Build Location. Here you specify what build machine to use for the build, what directory to use on the build machine that will perform the build, and where on the network to put the build when completed. On the final page, shown in Figure 12, you can specify build verification tests and code analysis options. I cover build verification tests in more detail in the next section.

![Figure 10: Creating a new Build Type-Selecting a configuration to build.](https://codemag.com/Article/Image/0703102/Figure 10.TIF)

![Figure 11: Creating a new Build Type-Selecting build and drop locations.](https://codemag.com/Article/Image/0703102/Figure 11.TIF)

![Figure 12: Creating a new Build Type-Selecting build options.](https://codemag.com/Article/Image/0703102/Figure 12.TIF)

To build a VFP application as part of a Team Build you have to modify the build file to add instructions to build the VFP project. The following XML snippet shows how this is done:

<Target Name="AfterCompile"> 
<Exec Command=""C:\Program Files\Microsoft 
Visual FoxPro 9\vfp9.exe" /t /a 
"c:\foxpro_utils\buildvfpexe\buildexe" 
"$(BuildNumber)" 
"$(SolutionRoot)\examplesolution\vfp\" 
"helloworld"" 
WorkingDirectory="$(SolutionRoot)\examplesolution\
vfp\" Timeout="10000" /> 
<Error Text="Errors updating the VFP Project List.
See: file:\\\$(SolutionRoot)\examplesolution\vfp\
helloworld.err" 
Condition="Exists('$(SolutionRoot)\examplesolution\
vfp\helloworld.err')" /> 
</Target> 

The Target tag with the Name attribute set to “AfterCompile” tells MSBuild to process contained tags after the successful compilation of the project in the solution. The AfterCompile event occurs just before you run any tests so this is a good time to build the VFP project.

Within the Target tag is an Exec tag and an Error tag. The Exec tag has a command attribute that you set to the command line that should be executed. Here it is set to run VFP9.EXE passing in the BuildVFPEXE program to execute, the build number the path where the PJM file should be found, and the name of the project.

If it were possible, you could return a value back from the command line to indicate success or failure. Since you can’t, you can use the MSBuild error tag to look for an ERR file generated by the BuildVFPEXE program and fail the build if it’s found. The Error tag also allows you to insert a link to the ERR file into the build report making it easier to get to the description of the problem.

To manually run the build, right-click the Build Type in the Team Explorer window and then choose “Build Team Project” from the shortcut menu. This opens the Build dialog box, shown in Figure 13. Choosing “Build” here will start the build; when the build finishes you’ll see a screen that looks something like Figure 14.

![Figure 13: The Build dialog box.](https://codemag.com/Article/Image/0703102/Figure 13.TIF)

![Figure 14: The Build Report for a successful build.](https://codemag.com/Article/Image/0703102/Figure 14.TIF)

Automating VFP BVTs

Once the build is working, all that is left is to get the build to run the FoxUnit test. You do this by adding the tests you want the build to run to a Test List, and then adding the Test List to the build script.

Test Lists are only available in Test and Team Suite editions of VSTS. The purpose of Test Lists is to group tests so they can be executed together. To create a Test List you use the Test Manager, shown in Figure 15.

![Figure 15: The Test Manager.](https://codemag.com/Article/Image/0703102/Figure 15.TIF)

Once you add the generic test that runs the FoxUnit test to a Test List, you need to add the Test List to the build and change the build to run tests.

Making these changes requires editing the build script. A build script is an XML file contained in a folder with the name of the build that resides in the TeamBuildTypes folder in the version control folder of the team project. The file name is TFSBuild.proj.

You must check out this file from version control to edit it, and then check in the file again to version control in order for the changes to take effect. To enable a build to run tests, change the value of the RunTest tag to true as shown in the following snippet:

    <!--  TESTING
     Set this flag to enable/disable running
     tests as a post build step.
    -->
    <RunTest>true</RunTest>

Adding the Test List to a build requires two changes to the build script. The first change is to add the solution’s vsmdi file to the script. This file contains the information for all test lists. To associate it with the build, find the MetaDataFile tag containing a TestList tag and change the Include attribute to specify the name of the vsmdi file using the format:

$(SolutionRoot)\SolutionName\SolutionName.vsmdi

Here is the tag for a build that does not run tests:

    <MetaDataFile Include=" ">
      <TestList> </TestList>
    </MetaDataFile>

Here is an example that runs the test list HelloWorldTests from the ExampleSolution solution:

    <MetaDataFile Include = 
"$(SolutionRoot)\ExampleSolution\ExampleSolution.vsmdi
">
      <TestList>HelloWorldTests</TestList>
    </MetaDataFile>

You must address two deployment issues before running the build. You have to install the bits needed to build the application onto each build machine and you have to configure the security on each build machine to allow the bits to execute.

Team Build is a distributed build system. You have to deploy the test environment on each build machine. In addition to the standard Team Build installation, you need to deploy the following:

  • Visual FoxPro
  • The BuildVFPEXE program
  • FoxUnit
  • The FoxUnit COM component
  • The RunFoxUnitTest.EXE console application

Don’t forget to register the FoxUnit COM component on each build machine. The last deployment issue is to grant the appropriate rights on the build machine to run the build.

Each build machine runs the Team Build service. This service listens for TFS calls to start a build. When a build is started, the build is run using a special user account established when TFS was installed. By default, TFS calls this account the TFSService account. This may or may not be the actual name of the account in your project. What is important is that you need to grant rights for the TFSService account to create files and launch executables on the build machine or builds will fail when trying to run VFP and the other executables. The simplest solution to this is to grant the TFSService account administrator rights on the build machine.

The Wrap Party

Visual Studio Team System is an enormously powerful tool that offers to both simplify and improve the software development process. Any team looking to increase their .NET investment would be wise to seriously consider how VSTS could improve their process.

I’ve tried to show in this article that you can adopt VSTS and take advantage of its benefits even if the majority of your code is written using a development environment that isn’t Visual Studio. In all honesty, what I’ve presented is the simplest approach that accomplishes the goal. It certainly isn’t an elegant solution. I’ve written enough wrapping and glue code to decorate a billion binary gifts. Still, I’m excited about including VFP in my Team Projects and I believe that the potential for integration is much greater than what I’ve been able to show. In the sections below, I will present a few ideas of what VFP development with VSTS could be.

MSCCI is Risky

The support for MSCCI provider-based source code control in the VFP IDE has a distinct Visual SourceSafe flavor to it. VFP developers need direct integration with the TFS version-control system that supports the full feature set of TFS version control.

Using project hooks, you can trap attempts to modify source objects and respond with appropriate check out behavior. Check ins and other version-control features would be accomplished through wrapping Team Explorer controls or writing VFP forms that leverage the TFI version-control API.

Text Rules

To facilitate branching and merging, all VFP source objects should be persisted in version control as text files that are automatically reconstituted as binaries when retrieved. This file reconstitution is similar to how VFP and the MSCCI provider currently handle project files.

The text files should be editable/merge-able and when possible, they should be PRG files that are equivalent to the binaries, or when not possible the PRG would create the binary files.

VFP Project Type for VS

Everyone in a mixed development environment that includes VFP needs a VFP project type for Visual Studio. This project type should be able to build a VFP project from within Visual Studio. A VFP Project Type would allow the creation of Test Lists, which could be used to define a check-in policy requiring the tests pass before check in.

Of course, VFP would handle compiling the executable. The VFP Project Type would tell Visual Studio how to compile VFP projects and test them without needing to modify the build script.

FoxUnit Test Type

Testers need a FoxUnit Test Type. This test type would make it easier to execute FoxUnit tests as part of the build and would allow the Visual Studio IDE to display test results during the build.

VFP Code Coverage and Static Code Analysis

If VFP’s Coverage Profiler was integrated with TFS metrics database, TFS reports could include metrics from VFP projects. VFP developers need a static code analysis tool that integrates with Team Build and the VSTS check-in polices so that TFS can verify that all code passes static code analysis before the check in will succeed.

While each of these suggestions are a substantial amount of work, having them would allow VFP to participate in any professional development shop that used TFS for SDLC management.

Listing 1: .NET TFS COM Wrapper-GetLatest.cs

using System;
using System.Runtime.InteropServices;
using Microsoft.TeamFoundation.Client;
using 
  Microsoft.TeamFoundation.VersionControl.Client;

namespace TFS
{
  [Guid("35806ADC-4EB9-4364-8E37-86FD36164C2C")]
  [ComVisible(true)]
  [ClassInterface(ClassInterfaceType.AutoDual)]
  [ProgId("TFS.SCC")]
  class SCC
  {

    private Workspace ws = null;
    private TeamFoundationServer tfs = null;
    private string msg = string.Empty;
    private VersionControlServer TFSVC = null;

    public GetStatus GetLatest()
    {
      // Get a reference to the TFS.
      tfs = 
        TeamFoundationServerFactory.GetServer("TFS");

      // Get a reference to Source Control.
      TFSVC = (VersionControlServer)
        tfs.GetService(typeof(VersionControlServer));

      // Hook up a method to listen for errors
      TFSVC.NonFatalError += OnNonFatalError;

      // Get the TFSVC workspace
      ws = TFSVC.GetWorkspace("FHRPC002");

      // Default message if no errors
      msg = ws.DisplayName;

      // Do the work
      GetStatus gs = ws.Get();

      return gs;

    }

    internal void OnNonFatalError( 
      Object sender, 
      ExceptionEventArgs e)
    {
      if (e.Exception != null)
      {
        msg = e.Exception.Message;
      }
      else
      {
        msg = e.Failure.Message;
      }
    }
  }
}

Listing 2: Example FoxUnit Test-HelloWorldTests.PRG

define class HelloWorldTests as FxuTestCase ;
    of FxuTestCase.prg

  ObjectUnderTest = null

  function setup

  this.ObjectUnderTest = newobject( ;
    "HelloWorld", ;
    "HelloWorld.prg", ;
    "HelloWorld.exe" )

  endfunc

  function TearDown

  this.ObjectUnderTest = null

  endfunc

  function TestObjectWasCreated
  
  this.AssertNotNull("Object was not " + ;
     "instantiated during Setup()", ;
    this.ObjectUnderTest)
    
  endfunc

  function TestObjectHelloWorld

  this.AssertEquals("Object.HelloWorld() did" + ;
     " not return 'Hello World!'", ;
    "Hello World!", ;
    this.ObjectUnderTest.HelloWorld())
    
  endfunc

enddefine

Listing 3: FoxUnit COM Component-FXU.PRG

* Class to expose FoxUnit tests as COM objects
define class fxu as session olepublic

   procedure init
   
   * Textmerge is used for two purposes:
   * To log debugging information and
   * to write out the results files
   * expected by VSTS.

   * Your paths may differ
   erase d:\test\foxunit.debug.txt
   set textmerge to d:\test\foxunit.debug.txt
   set textmerge on noshow

   \Debug Start
   \

   endproc

   * Do the real work
   procedure RunTest( ;
      cTest as string, ;
      cTestMethod as string, ;
      cSourcePath as string, ;
      cResultsFile as string ) as integer

   * Parameter validation
   if empty( cTest ) or ;
      empty( cTestMethod ) or ;
      empty( cSourcePath ) or ;
      empty( cResultsFile )

      error "Missing Parameter"

   endif

   * Write to debug log
   \Parameters:
   \cTest:<< cTest >>
   \cTestMethod:<< cTestMethod >>
   \cSourcePath:<< cSourcePath >>
   \cResultsFile:<< cResultsFile >>

   * Parameter validation
   if ! directory( cSourcePath )

      error "Source Directory not found"

   endif

   cd ( cSourcePath )

   * Your paths may differ
   set path to UnitTests,c:\foxpro_utils\foxunit

   * Write to debug log
   \
   \sys(5):<< sys(5) >>
   \sys(2003):<< sys(2003) >>
   \set("Path"):<< set("Path") >>
   \_vfp.StartMode:<< _vfp.StartMode >>
   \FoxUnitSetup << datetime() >>

   *DO ManageFxuClassFactory

   local goFoxUnitTestBroker

   \Create goFoxUnitTestBroker

   * The test broker runs the test
   goFoxUnitTestBroker = ;
      FXUNewObject("FxuTestBroker")

   local oTestResult

   * Write to debug log
   \Create oTestResult

   * The test result encapsulates the results
   oTestResult = FXUNewObject("FxuTestResult")

   * Write to debug log
   \Set oTestResult properties

   oTestResult.icCurrentTestClass = cTest
   oTestResult.icCurrentTestName = cTestMethod

   * Write to debug log
   \Run Test

   * Here we go!
   goFoxUnitTestBroker.RunTest( ;
      cTest, cTestMethod, oTestResult, .f. )

   \Set lPassed to 
   \\<< oTestResult.ilCurrentResult >>

   * Thumbs up?
   lPassed = oTestResult.ilCurrentResult

   * Write to debug log
   \Set results properties
   \cResults = 
   \\<< iif( lPassed, "Passed", "Failed" ) >>
   \cMessage = << oTestResult.icMessages >>

   cResults = iif( lPassed, "Passed", "Failed" )
   cMessage = oTestResult.icMessages
   cDetailedResultsFile = addbs( justpath( ;
      cResultsFile ) ) + "DetailedResults.txt"

   * Write to debug log
   \Generate Results

   set textmerge to

   * Create results summary file
   
   set textmerge on noshow

   text to cText
<?xml version="1.0" encoding="utf-8" ?>
<SummaryResult>
 <TestName><<cTest>></TestName>
 <TestResult><< cResults >></TestResult>
 <InnerTests>
  <InnerTest>
   <TestName><< cTestMethod >></TestName>
   <TestResult><< cResults >></TestResult>
   <ErrorMessage><< cMessage >></ErrorMessage>
   <DetailedResultsFile>
    << cDetailedResultsFile >>
   </DetailedResultsFile>
  </InnerTest>
 </InnerTests>
</SummaryResult>
ENDTEXT

   set textmerge off

   * Write summary to disk
   strtofile( cText, cResultsFile )

   * Create results detail file

   if isnull( oTestResult.ioExceptionInfo )
      cExceptionInfo = "None"
   else
      cExceptionInfo = oTestResult.ioExceptionInfo.ToString()
   endif


   if isnull( oTestResult.ioTeardownExceptionInfo )
      cTeardownExceptionInfo = "None"
   else
      cTeardownExceptionInfo = ;
         oTestResult.ioTeardownExceptionInfo.ToString()
   endif


   set textmerge on noshow
   
   cFED = oTestResult.icFailureErrorDetails

   text to cText
Test Class:<< oTestResult.icCurrentTestClass >>
Test Name:<< oTestResult.icCurrentTestName >>
Start:<< oTestResult.inCurrentStartSeconds >>
End: << oTestResult.inCurrentEndSeconds >>
Result: << oTestResult.ilCurrentResult >>
Messages: << oTestResult.icMessages >>
Failed Tests: << oTestResult.inFailedTests >>
Failed Error Details: << cFED >>
Exception Info: << cExceptionInfo >>
Teardown Exception: << cTeardownExceptionInfo >>
   ENDTEXT

   set textmerge off

   * Write detail file to disk
   strtofile( cText, cDetailedResultsFile )

   return iif(oTestResult.ilCurrentResult, 0, 1)


   procedure encode( cString )
      cResults = cString
      cResults = strtran( cResults, "\", "%5C")
      cResults = strtran( cResults, "", "%20" )
   return cResults

   procedure error(nError, cMethod, nLine)

   * Write error to debug log
   \
   \Error:<< nError >>
   \Method:<< cMethod >>
   \Line:<< nLine >>

   endproc

enddefine

Listing 4: FoxUnit .NET Wrapper Class-RunFXUTest.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Security.Principal;
using foxunit;

namespace RunFoxUnitTest
{
  class RunFXUTest
  {
    static int Main(string[] args)
    {
      // Grab the passed command line arguments
      string TestClass = args[0].ToString();
      string TestMethod = args[1].ToString();
      string TestSource = args[2].ToString();
      string ResultsFile = args[3].ToString();

      TextWriterTraceListener tr = new 
        TextWriterTraceListener(
          System.IO.File.CreateText(
            "d:\\test\\Output.txt"));

      Debug.Listeners.Add(tr);

      Debug.WriteLine("Test Class:" + TestClass);
      Debug.WriteLine("Test Method:" + TestMethod);
      Debug.WriteLine("Test Source:" + TestSource);
      Debug.WriteLine("Results File:" + ResultsFile);

      // Assume success
      int ReturnValue = 0;

      // A place to hold a reference to VFP
      foxunit.fxu fxu = null;

      Debug.WriteLine("Name: " + 
         WindowsIdentity.GetCurrent().Name);

      try
      {
        Debug.WriteLine("Get reference to fxu");

        // Get the reference to FXU
        fxu = new foxunit.fxuClass();

        Debug.WriteLine("Running Test");

        // Run the test and hope for the best
        ReturnValue = (int)fxu.RunTest(
           TestClass, 
           TestMethod, 
           TestSource, 
           ResultsFile);

      }
      catch (SystemException e)
      {
        // Something bad happened
        Debug.WriteLine("Message:" + e.Message);
        Debug.WriteLine("Stack:" + e.StackTrace);
        Debug.WriteLine(e.ToString());

        ReturnValue = 2;
      }
      finally
      {
        // Get rid of VFP
        Debug.WriteLine("Getting rid of fxu");

        if (fxu != null)
          fxu = null;

        Debug.WriteLine("Returning " +
          ReturnValue.ToString());
        Debug.Flush();

      }

      // Report the results
      return ReturnValue;
    }
  }
}

Listing 5: Automating the VFP Build-BuildEXE.prg

#DEFINE CRLF CHR(13) + CHR(10)

lparameters cBuildName, cProjectName

try         && something dangerous

   cPjmFile = forceext( cProjectName, "pjm" )
   
   ox = createobject( "PjmFile" )

   ox.BuildName = cBuildName
   ox.PjmFile = cPjmFile
   ox.Build()
   
catch to cErr && something nasty

   \Error Updating Project List
   \Error No: << cErr.ErrorNo >>
   \Line Contents: << cErr.LineContents >>
   \Line No: << cErr.LineNo >>
   \Message: << cErr.Message >>
   \Name: << cErr.Name >>
   \Procedure: << cErr.Procedure >>
   \Stack Level: << cErr.StackLevel >>
   
finally

   * Consolidate Error Logs
   if not empty( ox.ErrorLog )
      
      if file( ox.ErrFile )
         ox.ErrorLog = ;
            ox.ErrorLog + CRLF + CRLF + ;
            "---------------" + CRLF + ;
            filetostr( ox.ErrFile )
      endif
      
      
      erase ( ox.ErrFile )
      strtofile( ox.ErrorLog, ox.ErrFile )
      
   endif


endtry


* If in a build exit
if not empty( cBuildName )
   quit
endif


define class PjmFile as session
   
   * Core properties
   ErrorLog = ""
   BuildName = ""
   PjxFile = ""
   PjmFile = ""
   PjmFileName = ""
   PjmPath = ""
   ErrFile = ""
   CurrentDefault = sys(5) + sys(2003)
   dimension ProjectFiles[1]

   * Pjx Properties
   version=  1.20
   Author=""
   Company=""
   Address=""
   City=""
   State=""
   Zip=""
   Country=""
   SaveCode=.t.
   debug=.t.
   encrypt=.f.
   NoLogo=.f.
   CommentStyle=1
   Comments="Project created by " + ;
      proper( program() )
   CompanyName=""
   FileDescription=""
   LegalCopyright= "© 1999-" + ;
      str(year(date()),4) + " FooBar Inc."
   LegalTrademarks="® FooBar is a registered" +;
      " trademark of FooBar Inc."
   ProductName="FooBar"
   Major=""
   Minor=""
   Revision=""
   autoincrement=.f.

   
   * Coerce CommentStyle to a numeric
   procedure CommentStyle_assign(pCommentStyle)
   
   do case
   case vartype( pCommentStyle ) = "N"
      this.CommentStyle = pCommentStyle
   case vartype( pCommentStyle ) = "C"
      this.CommentStyle = val( pCommentStyle )
   endcase
   
   
   endproc
      
   
   * Reload the project object 
   * when assigned a new PJM
   procedure PjmFile_assign( pcPjmFile )

   if file( pcPjmFile )

      this.PjmFile = pcPjmFile
      this.PjmFileName = justfname( pcPjmFile )
      this.PjmPath = justpath( pcPjmFile )
      
      this.ErrFile = addbs( this.PjmPath ) + ;
         forceext( this.PjmFileName, "err" )
      erase ( this.ErrFile )


      this.ParsePjm()

      * Start the error log
      set textmerge to memvar ;
         this.ErrorLog noshow additive
      set textmerge on

   endif
   

   endproc


   procedure Build

   this.CreatePjx()
   this.TweakPjx()
   
   set textmerge to
   
   if empty( this.ErrorLog )
      
      lcPJX = addbs( this.PjmPath ) + ;
         forceext( this.PjmFileName, "pjx" ) 
      lcEXE = addbs( this.PjmPath ) + ;
         forceext( this.PjmFileName, "exe" ) 
      
      build exe ( lcEXE ) ;
         from ( lcPJX ) recompile
      
   endif
   
   
   endproc


   * Update the project with data from the PJM
   procedure TweakPjx
   
      use ( this.PjxFile ) alias PJX 
      
      * Update project properties
      locate for type = "H"
      replace ;
         SaveCode with this.SaveCode, ;
         Debug with this.Debug, ;
         Encrypt with this.Encrypt, ;
         Nologo with this.Nologo, ;
         CmntStyle with this.CommentStyle
         
      * Update the project files
      for each loFile in this.ProjectFiles
         
         if loFile.MainProgram
            
            locate for MainProg
            
         else
            
            append blank
            
         endif
         

         replace ;
            Name with loFile.FileName + ;
               chr(0), ;
            Type with loFile.FileType, ;
            Id with loFile.id, ;
            Exclude with loFile.Exclude, ;
            MainProg with ;
               loFile.MainProgram, ;
            CPID with loFile.codepage, ;
            Comments with ;
               loFile.FileDescription 
         
      endfor
      
      
      use in PJX
      
               
   endproc


   * Build the create the project files AND 
   * verify that files in the project exist.
   procedure CreatePjx

   * Preserve the existing project if 
   * testing. Normally during a build
   * there is only the pjm.
   lcFile = iif( empty( this.BuildName ) and ;
      file( forceext( this.PjmFile, "pjx" ) ) ;
      , "_", "" ) + this.PjmFileName
   
   lcPjx = forceext( lcFile, "pjx" )
   lcPjt = forceext( lcFile, "pjt" )
   
   this.PjxFile = addbs( this.PjmPath ) + lcPjx

   set default to ( this.PjmPath )

   erase ( lcPjx )
   erase ( lcPjt )

   lcFiles = ""
   lcMain = ""

   for each loFile in this.ProjectFiles

      lcFileName = loFile.FileName

      if file( lcFileName )

         if loFile.MainProgram
            
            lcMain = lcFileName
            
         else
            
            * The docs say you can specify 
            * more than one file in the 
            * build project files clause. 
            * In practice only the first file 
            * is added.
            * lcFiles =lcFiles+" "+lcFileName
            
         endif
         

      else

         \Project file << lcFileName >> 
         \\was not found.

      endif
      

   endfor
   
   
   lcFiles = lcMain && + ", " + lcFiles

   * Create the project file and add the file(s)
   build project ( lcPjx ) from &lcFiles


   * Initialize the project
   procedure init
   this.ProjectFiles[1] = null
   set textmerge to
   endproc

   
   * Clean up
   procedure destroy
   set default to ( this.CurrentDefault )
   set textmerge to
   set textmerge off
   endproc


   * Hydrate the object from the PJM
   procedure ParsePjm

   if empty( this.pjmfile ) or ;
      not file( this.pjmfile )
      return
   endif

   lcPjM = filetostr( this.pjmfile )

   if empty( lcPjM )
      return
   endif

   =alines( laLines, lcPjM, 1 )

   * First section of file
   lcState = "[Properties]"

   for each lcLine in laLines

      * Section markers
      if inlist( lcLine, ;
         "[OLEServers]", ;
         "[OLEServersEnd]", ;
         "[ProjectFiles]", ;
         "[EOF]" )
         
         lcState = lcLine
         loop
         
      endif


      do case
      case lcState = "[Properties]"

         lcPropertyName = alltrim( ;
            getwordnum( lcLine, 1, "=" ) )
         lcProperty = "this." + lcPropertyName
         lcValue = getwordnum( lcLine, 2, "=")

         if not empty( lcValue )
            
            do case
            case inlist( lcPropertyName, ;
               [SaveCode], ;
               [Debug], ;
               [Encrypt], ;
               [NoLogo], ;
               [AutoIncrement] )
               
               store evaluate(lcValue) ;
                  to &lcProperty
               
            case inlist( lcPropertyName, ;
               [CommentStyle], ;
               [Major], ;
               [Minor], ;
               [Revision] )
               
               store val(lcValue) ;
                  to &lcProperty
            otherwise
               store lcValue to &lcProperty
            endcase
            
            
         endif
         

      case lcState = "[OLEServers]"
         * TODO: Add support for COM Servers
         loop
      case lcState = "[OLEServersEnd]"
         * TODO: Add support for COM Servers
         loop
      case lcState = "[ProjectFiles]"
         this.AddFile( lcLine )
      case lcState = "[EOF]"
         loop
      endcase


   endfor
   

   endproc


   * Add a file to the project object
   procedure AddFile( lcLine )

   loFile = createobject( "ProjectFile" )
   loFile.ParseFile( lcLine )

   if not isnull( this.ProjectFiles[ alen( ; 
      this.ProjectFiles ) ] )
      dimension this.ProjectFiles[ alen( ;
         this.ProjectFiles ) + 1 ]
   endif

   this.ProjectFiles[ ;
      alen( this.ProjectFiles ) ] = loFile

   endproc


enddefine



define class ProjectFile as session
   id = ""
   FileType = ""
   FileName = ""
   Exclude = .f.
   MainProgram = .f.
   codepage = 1252
   User1 = ""
   User2 = ""
   FileDescription = ""



   procedure init

   endproc

   procedure destroy

   endproc

   procedure ParseFile( pcLine )

   this.id = val( getwordnum( pcLine, 1, "," ) )
   this.FileType = getwordnum( pcLine, 2, "," )
   this.FileName = getwordnum( pcLine, 3, "," )
   this.Exclude = eval( getwordnum( pcLine, 4, "," ) )
   this.MainProgram = eval( getwordnum( pcLine, 5, "," ) )
   this.codepage = val( getwordnum( pcLine, 6, "," ) )
   this.User1 = getwordnum( pcLine, 7, "," )
   this.User2 = getwordnum( pcLine, 8, "," )
   this.FileDescription = getwordnum( pcLine, 9, "," )

   endproc

   
   procedure Error
   lparameters nError, cMethod, nLine
   
   \
   \Error Building Project 
   \Error: << nError >>
   \Line: << nLine >>
   \Method: << cMethod >>

   endproc

   
   procedure BuildProject
   lparameters cBuildName, cProjectName

   cEXE = forceext( cProjectName, "exe" )
   cPJX = forceext( cProjectName, "pjx" )

   build exe ( cEXE ) from ( cPJX )   


enddefine