Build and Deployment Practices
Goals
- Manage concurrent development efforts of the team effectively.
- Manage product quality by rigorous unit and functional testing.
- Maintain the ability to deploy any version of the application and related database schema and base-line data at any time to any environment.
- Provide the ability to address defects identified in production while continuing to add new functionality slated for future releases.
- Be able to respond to defects in User Acceptance Testing (UAT) or production without injecting new functionality into the deployed version of the application.
- Keep the main code line (development branch) synchronized with any defect fixes made in a release branch.
- Provide the ability to report on the status and progress of all development and deployment operations.
Principles
- Source code is the source of truth (i.e. Golden).
- Maintain fidelity between the source code and the delivered product.
- Remove the chance for error everywhere in the process by using automation and implementing process controls where possible.
- Make the management and tracking process simple and easy for developers (low friction).
- Audit (log) all build and deployment activities.
Versioning
The continuous integration process increments the build label each time it builds the solution. We use the build version number to track the build, build metrics, integration test results, and deployments.
The version number uniquely identifies each build. When we have a build with new stories ready for testing, we select the build in TFS, set the build quality to “Ready for Initial Tests”, and the functional tester selects the build, creates the deployment, and starts functional testing the build. When the functional tester is finished testing the new stories and regression testing the application, the build quality is marked “Ready for Deployment” and can be deployed to production.
To facilitate identification and tracking of the application versions the following number schema is used:
{Major}.{Minor}.{Build}.{Revision}
e.g.: 1.0.123.0
e.g.: 1.1.343.0
e.g.: 1.1.343.1
The major version number designates the current release branch we are working on. The major version number is incremented each time we have a new release to production. The major version is tied to the release number we establish for a group of features within TFS.
The minor version number indicates the sub-release for the designated branch. We increment the minor version number when deploying production fixes made to a code branch that is already in production.
The build number indicates the specific build we are working with. We increment the build number each time we build the product. With continuous integration, we may build the product several times a day. Each build attempt will have a new build number associated with it.
The revision number indicates a ‘patch’ release. We don’t expect to need patch releases and will not use the revision number at this time.
Branching Strategy
We use a Branch for Release strategy where we create a new branch each time we release a version of the application to production. The release branch gives us the ability to fix production issues and re-release without adding additional functionality from the next sprint into the build.
Branching Model

Note: we don’t need to actually create the physical branch for the release when we release the application. We can create the branch from the label if we need to make changes and re-deploy the release branch during the next sprint. This allows us to only branch the source tree when absolutely needed.
Continuous Integration and Version Build Use Cases
1. Continuous Integration (CI) Build
The continuous integration build retrieves the latest version of the source code from the mainline (development) branch in source code, updates the version number in the assemblies, executes the full set of database scripts against the development (integration) database, compiles the source code, performs the static code analysis, executes the unit tests, and code coverage analysis. If all these steps execute without error, we consider the build successful and the build reports indicate success and all build metrics.
The continuous integration build ensures the solution compiles and unit tests execute properly. We want to ensure that all the code changes made by the developers on the team integrate properly. If changes break the build, we want to know as quickly as possible so the developer that checked in the breaking change can resolve the issue immediately. Since we won’t be deploying the CI build for testing, we won’t label the version in the source repository and we won’t update the version number in the App.Config file.
2. Test Version (New Version) Build
When we are ready to test a new version, we initiate the build of a new version of the application and prepare the application for deployment to the test environment by creating a ClickOnce deployment package. To do this we increment the version number, apply a label with that version number to the main (development) branch in the source repository, retrieve the source files, set the value of the version number in the App.config file, update the value of the database connection string in the App.config file, execute all database package scripts against the Test database, update the assembly version number for all assemblies, compile, analyze, and unit test all assemblies. With the assemblies created and tested, we then build the ClickOnce deployment package, modify the link on the ClickOnce deployment HTML page, and copy the deployment files to the deployment location (currently on the TFS01 server).
If the build succeeds, we have a new version of the application ready to be installed and tested. The tester can install the new version of the application on a Virtual Machine for testing, or directly on their workstation.
3. Production Version Build
Once a version of the application has been through a full suite of functional and user acceptance tests and is ready to be deployed to production, we build the specific version of the application and create a ClickOnce package for deployment to production.
To create the versioned build, the build master selects the labeled version to create, and initiates the build process. The build process retrieves the files tagged with the specified version number from the source repository, sets the version number and the connection string information in the App.config file, updates the assembly version number information, compiles the code, performs static analysis, executes the unit tests and code coverage analysis, creates the ClickOnce deployment packages, updates the ClickOnce HTML page link information, and copies the deployment pack to the deployment location.

Using Team Build to manage the build process
Requirements of TFS
TFS’s Team Build requires the following:
· A unique name (number) for each build.
We must:
1.) Store the current increment number in a common location on the build or drop server.
2.) Increment the build number when the build kicks off.
3.) Label Source control with the build number generated.
4.) Override the Target in TFS – “BuildNumberOverrideTarget”.
5.) Remember every build number must be unique. (Build name must be unique.)
a. We may need to use something like “{BuildDefinitionName}_1.0.0.0″ to define a build that we do for CI and a build we do for Testing, Or Production.
Note: Custom Build Number Generator – uses file stored in build drop section, and DLL Placed in the Build Directory in Source Control – and referenced in the MS Build file on the server.
We Override the <Target Name=”AfterGet” /> target to get the assembly info files and update the version number of each assembly to match the build version number.
<ItemGroup>
<AssemblyInfoFiles Include="$(SolutionRoot)\**\assemblyinfo.cs" />
</ItemGroup>
<Target Name="AfterGet">
<!-- Update all the assembly info files with generated version info -->
<Message Text="Modifying AssemblyInfo files under
"$(SolutionRoot)"." />
<Attrib Files="@(AssemblyInfoFiles)" Normal="true" />
<FileUpdate Files="@(AssemblyInfoFiles)"
Regex="AssemblyVersion\(".*"\)\]"
ReplacementText="AssemblyVersion(
"$(VersionMajor).$(VersionMinor).
$(VersionService).$(VersionBuild)
")]" />
<FileUpdate Files="@(AssemblyInfoFiles)"
Regex="AssemblyFileVersion\(".*"\)\]"
ReplacementText="AssemblyFileVersion(
"$(VersionMajor).$(VersionMinor).
$(VersionService).$(VersionBuild)
")]" />
<Message Text="AssemblyInfo files updated to version
"
$(VersionMajor).$(VersionMinor).
$(VersionService).$(VersionBuild)
"" />
</Target>
Custom Build Tasks
To control the build number versioning, we created a custom MSBuild Task: “IncrementBuildNumber” to retrieve the last build number from the build number file, increment the number and update the build number file with the newly created version number.
We include our custom task in the build type, by adding the following tag to the TFSBuild.proj file:
<UsingTask TaskName=”IncrementBuildNumber”
AssemblyFile=”$(TeamBuildRefPath)\CustomBuildTasks.dll”/>
To manage the increment the build number during the build process, we call the IncrementBuildNumber task in the “BuildNumberOverrideTarget” in the TFSBuild.proj file as shown below:
<PropertyGroup>
<VersionBuild>9999</VersionBuild>
</PropertyGroup>
<Target Name=”BuildNumberOverrideTarget”>
<!– Create a custom build number, matching the assembly version –>
<Message Text=”Loading last build number from file
"$(DropLocation)\buildnumber.dat"” />
<IncrementBuildNumber NumberFile=”$(DropLocation)\buildnumber.dat”>
<Output TaskParameter=”NextNumber” PropertyName=”VersionBuild” />
</IncrementBuildNumber>
<PropertyGroup>
<BuildNumber>$(BuildDefinitionName)_$(VersionBuild)</BuildNumber>
</PropertyGroup>
<Message Text=”Build number set to "$(BuildNumber)"” />
</Target>
Running all Unit Tests
Initially, TFS assumes that you want to use a .vsmdi – metadata – file to maintain a list of all unit tests to run in the build. This requires the developers to manage the updates to this file each time the add a unit test. When we create an integration build, we want to run all the unit tests in the solution, and we don’t want to have to rely on the developers updating the unit test metadata file each time they check in a new unit test. So, we use the Test Container feature of TFS 2008 that allows us specify a pattern for test assemblies. When the pattern specified, we tell the TFS build process to execute all tests within the solution, not just those specified in the unit test metadata. To use the test container, add the following to the TFSBuild.proj build definition file after the TestArguments section:
<TestContainer Include=”$(OutDir)\%2a%2a\%2a.Test.dll” />
Executing Code Coverage Analysis in the Build
To run code coverage analysis in Team system, a test run configuration file is required. The test run configuration file maintains a list of assemblies to instrument for code coverage analysis. Developers maintain the test run configuration file as needed when they add or remove assemblies from the solution.
To include Code Coverage Analysis in the build, we specify the location of the test run configuration file using the <RunConfigFile> tag in the TFSBuild.proj file. Note, this tag must be specified within the <PropertyGroup> tag.
<PropertyGroup>
…
<RunConfigFile>$(SolutionRoot)\Main\
BuildIntegrationTests.testrunconfig</RunConfigFile>
…
</PropertyGroup>
ClickOnce Deployment
We use ClickOnce deployment to manage the distribution of new versions of the application in all environments (Dev Test, Functional Test, and Production). Our build and deployment process creates a new ClickOnce deployment packages with each build. Each environment has a unique drop location and install web page, allowing our testers to install a test version of the application and production users to install a production version of the application.
The application deployments may differ both in version number as well as the database connection used. (Test uses the test database connection, and production uses the production database.)
To create the ClickOnce deployment packages, the build process must:
· Set the application and deployment properties to match the version and environment specified by the build type information.
· Create an application manifest file using the mage.exe utility.
The application manifest describes the application, including: the assemblies, the dependencies, the files that make up the application, the required permissions, and the location where updates are available.
· Create a deployment manifest file that describes how we deploy the application, again using the mage.exe utility.
The deployment manifest includes the location of the application manifest, the version of the application that clients should be running, and the update location where the application checks for updated versions – specified in the publish properties.
· Copy the deployment files and application files to the deployment location, i.e. the web server running on the TFS01 server. The deployment manifest specifies the deployment location.
