Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Continuous Integration & .NET: Part I


August, 2004: Continuous Integration & .NET: Part I

Weaving together an open-source solution

Thomas is a manager with Deloitte. He is currently working on the firm's Commonwealth of Pennsylvania account and is located in Harrisburg, PA. Thomas can be reached at thbeckdeloitte.com.


When questioned by a colleague as to what open-source tools and methodologies I thought would most likely make the transition to .NET, my response was immediate—Continuous Integration tools. Continuous Integration is a term that describes an automated process that lets teams build and test their software many times a day. Continuous Integration, and Java-based tools such as Ant and JUnit that support it, have been a hot topic in the Java community for several years and are the subject of a number of popular books. In this first installment of a two-part article, I examine the building blocks of an open-source Continuous Integration solution.

Several basic tools used to support Continuous Integration were ported to the .NET environment during the 1.0 release of the Framework. Other tools, such as CruiseControl and Clover, have been ported more recently. Furthermore, certain other tools such as NDoc are indigenous to .NET and only conceptually related to their Java counterparts (Java Doc, in this case). For the most part, articles written to date about these tools have addressed only a subset of the tools (usually NAnt and NUnit), thus failing to weave together a holistic Continuous Integration solution. Moreover, the unit-testing examples are function based and fail to address the database-driven reality of today's enterprise applications.

In this two-part article, I introduce a complete Continuous Integration solution that encompasses automated builds, data-driven unit testing, documentation, version control, and code coverage. All of these tasks are performed automatically every time the project source code is modified. The results of the build (including the complete build history), the application's user interface, the MSDN-style documentation, .NET Framework Design Guidelines conformance report, and the code coverage results can then be made available from a central project build site. Figure 1 is a conceptual architecture of this solution. Additionally, I introduce the components necessary to address the various tasks integral to Continuous Integration, adding new tasks to the NAnt build file to accommodate the tools as they are introduced. All of these tools, with the exception of Clover (an exclusive preview version of Clover.NET is available electronically; see "Resource Center," page 5) and FxCop (which is covered under a Microsoft EULA), are covered by one of the common open-source licenses. Although the solution presented here was originally built on a Windows 2000 platform (including IIS), it can be easily modified to suit the needs of your application. These changes could accommodate a fully open-source .NET environment (Mono and Apache mod_mono, for instance) or align more closely with a pure Microsoft environment (Visual Source Safe, SQL Server, and the like).

NAnt: Building a Simple Executable

NAnt (http://nant.sourceforge.net/), the .NET port of Ant supported by the Jakarta project, is an XML-based build tool that is meant to replace the combination of the Make build program and other scripts cobbled together by projects to build their applications in the past.

Before installing NAnt, you need to have already installed IIS or the Microsoft Personal Web Server and the .NET Framework classes. If you haven't done so, it's important to install the Framework classes after the web server. It is also worth noting that Microsoft introduced MSBuild, its own .NET build tool, at the 2003 Professional Developer's Conference. Expect to see this tool integrated into the upcoming Visual Studio "Whidbey" release as an alternative to NAnt.

Once you have downloaded NAnt, extract the files into the /Program Files/NAnt/ folder and change the Windows PATH definition to include the \NAnt\bin directory. To verify that NAnt is installed correctly, type nant at the DOS command line. You should get a build error. That's okay because you haven't defined a build file yet. NAnt requires that the build file is either present in the directory you are in when you invoke the tool or that you specify the build file that you want to use with the -build file: option.

The first NAnt build addresses the most basic of all programming scenarios—building a single binary executable that implements the standard "Hello World" functionality. Both the build file and C# and VB.NET source code for this example are available electronically. Listing One, the first NAnt build file, outlines the basic structure of this file and illustrates the critical components of an NAnt build file:

  • Project. The root element of the build file. Projects can contain a number of properties and a number of tasks.
  • Property. A property represents a name/value pair that can be used in the tasks.
  • Target. Targets represent a particular component of the build process (cleanup, build, execute). Dependencies may be established between the various targets in a project. Each target consists of one or more tasks.
  • Tasks. Tasks are discrete pieces of work that are performed within a target. This build file includes the mkdir and csc tasks (see http://nant.sourceforge.net/help/tasks/index.html for a complete list of tasks for NAnt).

To build the executable, go to the directory where your build file is located and type nant at the command line. If there is more than one build file in your directory, make sure to use the -build file: option. If NAnt reports difficulty finding your compiler, make sure that the directory where the .NET compiler resides is reflected in your PATH. Moreover, depending on the version of the .NET Framework you're running, check the nant.exe.config file that can be found in the NAnt \bin directory. In the <frameworks> node of the file, you can set the default version of the framework that you want to use.

The result of the NAnt build is an executable "Hello World" program that can run from the command line. Of course, it would have been easier to compile this program from the command line. NAnt's true strength doesn't begin to show until the builds get complex and time consuming and various external components (unit-testing, documentation) need to be integrated into the build process.

NAnt Part II: Building a Component that Accesses Data

The move from building an executable to building a component is, from an NAnt standpoint, a relatively simple transition. There are, however, other significant implications of moving to a DLL-based application. Foremost amongst these implications is the increase in the number of software objects in your application and the consequential increase in the complexity of building, testing, and documenting your software. This is where NAnt really shines.

Here, I build and deploy a business object that exposes three core business methods. These three methods were selected because they illustrate the different aspects of unit-testing XML datasets, .NET-based collections, and traditional integer return type functions:

  • public dataset GetAuthors(). A method used to get a dataset containing the names of all of the authors in the authors table.
  • public StringCollection GetTitlesForAuthors(string authorID). A method that returns a string collection containing all of the titles associated with a particular author ID.
  • public int TotalSalesForAuthors(string authorID). A method used to calculate the total book sales associated with the author identified by a particular ID.

The example component included with this article uses MySQL (http://www.mysql.com/) as a database leveraging the Open Source ByteFX ADO.NET database driver (http://sourceforge.net/projects/mysqlnet/). The ByteFX driver need only be extracted to a program file directory; NAnt makes sure that the DLL is available when compiling your component.

The data access examples use a database entitled "pubs," which contains a subset of the tables available in its namesake database available in SQL Server. SQL DDL statements for the setup of pubs are included as part of the source code if you are using a database other than MySQL or SQL Server. Since all data access is done via ADO.NET, these programs can be easily modified to communicate with your database of choice as long as you are using an ADO.NET-compatible driver for database connectivity.

The build file for the component build is not much more complex than our previous build file; see Listing Two. There are several noteworthy changes included in this build file. First, the target statement has been modified to create a "library" and the inclusion of the import statements corresponding to the imports in our program. Second, references, which refer to metadata in the specified file assemblies, have been included for each of the DLLs needed for the build. Finally, a new target, deploy, has been added. It copies the .dll and the .aspx files to the appropriate location on the web server. This target is dependent on the build target and that target is now specified as the default target for the project.

When you've run the build program, you will have created the appropriate library in your build folder and your web server's \bin folder. You can run the SQL DDL files to set up your database (as needed) and check out the web site you've built. This is the first step in your comprehensive build solution. The next step will be to integrate unit testing into our automated build process.

NUnit: Automating Your Unit Testing

NUnit (http://www.nunit.org/) is a tool for automating .NET unit testing, regardless of the .NET language you choose to program in. The NUnit tool includes a standalone GUI for managing and running unit tests as well as a command-line interface. In addition, plug-ins have been written for several popular .NET IDEs such as Visual Studio.NET and the open source #Develop. NUnit can also be invoked and automated using a special NAnt task. It is the last method that I explore here. Installing NUnit is as simple as double clicking on the Windows MSI installer file.

Once you have NUnit installed, you can jump into unit testing the business object that you previously built. Although I concentrate on testing the business logic in this article, a fairly capable sibling application named "NUnitAsp" is available for testing ASP.NET server-side logic.

In Listing Three, the database target provides for the setup of the initial database objects and test data that are required for all of the tests to be run. In this case, I make a direct call to the mySQL program with the execute (-e) option to execute the SQL scripts. Calls to analogous utilities for your database of choice could be substituted here, as necessary. Note that the special &quot values in the execute call are XML standard delimited quotations. Listing Four represents the test target and covers the setup and invocation of the NUnit tests. First, the nunit.framework.dll is copied to the build directory. Then the unit-testing class BusObjTstCS is compiled. Finally, the NUnit tests in this dll are invoked using the NAnt <nunit2> task. The special XML formatter element causes the results of the unit test to be written out to an XML file. This file is later used by our continuous integration engine, CruiseControl.NET, to interpret and display our testing results.

At the heart of the Continuous Integration solution is automated unit testing, supported by NUnit. NUnit tests can be written in any of the .NET languages. The NUnit test fixture marks the class that contains the test methods. Each of these tests can test for a number of conditions including true/false, null/not-null, and equal/not-equal. Table 1 outlines NUnit-testing fundamentals.

The test fixture for this article contains a number of tests to exercise the business logic in our component:

  • public void init(). Technically, this is a setup fixture and not a test. For our purposes, this class is used for business object instantiation and database setup.
  • public void GetAllAuthors(). One of the most important test fixtures in our example. This fixture displays how to unit test database results; specifically a DataSet in this example. The test uses the getXml() and readXml() methods of the DataSet class to compare the XML rendering of the dataset returned by our GetAuthors() method to a pregenerated XML test file on the file system.
  • public void GetMultipleTitles(). This fixture, along with the similarly inclined GetSingleTitle() and GetNoTitle(), test that the correct title(s) and correct number of titles are returned by the GetTitlesForAuthors() method.
  • public void GetTotalSalesMultipleBooks(). This fixture, along with GetTotalSalesSingleBook() and GetTotalSalesNoBook(), test that the TotalSalesForAuthor() method returns the correct sales total for a given author.

The output of the test is written to the command line during the build and is also written out in an XML format. A failed result causes the entire build to fail (the haltonerror and failonerror attributes of the nunit2 task can be used to control this behavior) and outputs both an expected result and the actual result to the command line. Once you've achieved a clean build and are confident the tests are working, you can begin documenting the system.

NDoc: Professional Documentation Simplified

Microsoft established solid groundwork for the generation of professional quality documentation with the release of the .NET Framework. The C# compiler uses reflection to extract specially tagged comments from the assemblies' metadata and create XML documentation files. The open-source NDoc tool (http://ndoc.sourceforge.net/) then uses these XML files to create MSDN-style HTML help files. Currently, this functionality is only available for C# programs although all indications are that VB.NET will be capable of generating these XML comment files as well with the upcoming Visual Studio Whidbey release. In the meantime, a special tool (VB.DOC) is available to harvest VB.NET code for comments and to create the XML comment files. Both NDoc and VB.DOC can be integrated into the build process via NAnt tasks.

You will also need Microsoft's HTML Help Workshop (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/htmlhelp/ html/hwMicrosoftHTMLHelpDownloads.asp), which lets you compile your HTML help files into a standard CHM Windows help file format. If you use VB.NET as your .NET language or you would like to test out the VB.NET examples from this article, you will also need VB.DOC and the VB.DOC NAnt task (http://vb-doc.sourceforge.net/). In addition, to install the VB.Doc NAnt task, you have to copy the VB.Doc DLL's from the VB.Doc /bin folder to the NAnt /bin folder.

To have your documentation automatically generated for the code, you need to change your build file and add the appropriate documentation comments to your source code. I concentrate on the former activity here. Combined with the source code for this article, several of the references at the end of this article will give you a good idea of how to document your classes. Due to the lack of native XML documentation of file-generation capabilities in VB.NET, there are distinct differences in the build file based upon what particular .NET dialect you choose. The VB.NET build file includes the addition of a new document target (Listing Five), which includes calls to both the vbdoc and the ndoc tasks for generating documentation. The C# build file includes a similar document target, minus the call to vbdoc. Instead, the C# build file has an additional line to accommodate a compiler argument in the csc task informing it to generate the XML document:

<arg value="/doc:${build.dir}\${basename}.xml"/>

The source files (available electronically) contain comments to support basic documentation features such as method descriptions, inputs/outputs, and usage examples. In addition to basic MSDN-style documentation, NDoc also supports the ability to link back to the documentation for .NET Framework classes (provided that the classes are installed on the same machine as the NDoc documentation) and to use an <include> tag to link in external XML documentation sources. The latter feature is especially useful on larger projects to avoid the commingling of source code and documentation and the associated contention issues for access to combined source code and documentation files.

With the tools outlined in this installment, you will be able to put together a process to automate the building, testing, and documentation of .NET applications. In the next installment of this article, I integrate these tools under the umbrella of a CVS-based continuous integration process and introduce the concept of code-coverage testing.

DDJ



Listing One

<?xml version="1.0"?>
  <project name="Hello World" default="build" basedir=".">
     <property name="basename" value="HelloWorld"/>
     <property name="build.dir" value="cs_build"/>
     <target name="build">
      <mkdir dir="${build.dir}"/>
      <csc target="exe" output="${build.dir}\${basename}.exe">
        <sources>
          <includes name="src\HelloWorld.cs"/>
        </sources>
      </csc>
     </target>
  </project>
Back to article


Listing Two
<?xml version="1.0"?>
  <project name="Business Object" default="deploy" basedir=".">
     <property name="basename" value="BusObjCS"/>
     <property name="build.dir" value="cs_build"/>
     <target name="build">
      <mkdir dir="${build.dir}"/>
      <copy file="c:\Program Files\ByteFX\ByteFX .Data Provider 
            0.75\ByteFX.Data.dll" tofile="${build.dir}\ByteFX.Data.dll" />
      <csc target="library" output="${build.dir}\${basename}.dll"
            imports="System,System.Data,System.Data.SQLClient,
                     System.Collections.Specialized,System.XML">
        <sources>
          <includes name="src\BusObjCS.cs"/>
        </sources>
      <references>
                <includes asis="true" name="System.dll"/>
        <includes asis="true" name="System.Data.dll"/>
        <includes asis="true" name="System.XML.dll"/>
          <includes name="${build.dir}\ByteFX.Data.dll"/>
        </references>
      </csc>
     </target>
     <target name="deploy" depends="build">
      <copy file="${build.dir}\BusObjCS.dll" 
          tofile="c:\Inetpub\test\bin\BusObjCS.dll" />
          <copy todir="c:\Inetpub\test">
          <fileset basedir="aspx">
          <includes name="*.aspx" />
          </fileset>
          </copy>
     </target>
  </project>
Back to article


Listing Three
<target name="database">
     <exec program="${sql.program}" commandline="${sql.db} -u root 
       "source ${sql.drop_file}""/>
    <exec program="${sql.program}" commandline="${sql.db} -u root 
      "source ${sql.load_file}""/>
</target>
Back to article


Listing Four
<target name="test" depends = "build, database">
    <copy file="${nunit.dll}" tofile="${build.dir}\nunit.framework.dll"/>
     <csc target="library" output="${build.dir}\${testname}.dll"
            imports="System,System.Data,System.XML,BusObjVB,nunit.framework">
         <sources>
           <includes name="src\BusObjTstCS.cs"/>
         </sources>
         <references>
             <includes asis="true" name="System.dll"/>
             <includes asis="true" name="System.Data.dll"/>
             <includes asis="true" name="System.XML.dll"/> 
             <includes name="${build.dir}\BusObjCS.dll"/>
             <includes name="${build.dir}\nunit.framework.dll"/>
         </references>
      </csc>
       <nunit2>
           <formatter type="Plain" />
           <formatter type="Xml" usefile="true" outputdir="${build.dir}" 
                extension=".xml" />
            <test assemblyname="${build.dir}\${testname}.dll"/>
        </nunit2>
     </target>
Back to article


Listing Five
<target name="document" depends="test">
    <mkdir dir="${help.dir}"/>
     <vbdoc assembly="${build.dir}\${basename}.dll" destination="
           ${build.dir}\${basename}.xml" encoding="ansi" prefix="'''" >
         <sourcefiles>
             <includes name="${src.dir}\${basename}.vb" />
         </sourcefiles>
     </vbdoc>
     <ndoc>
        <assemblies basedir="${build.dir}"> 
            <includes name="${basename}.dll"/>
        </assemblies> 
        <summaries basedir="${build.dir}">
            <includes name="${basename}_doc.xml"/>
         </summaries>       
         <documenters>
         <documenter name="MSDN">
          <property name="OutputDirectory" value="${help.dir}"/>
          <property name="RootPageContainsNamespace" value="false"/>
           <property name="SortTOCByNamespace" value="true"/>
          </documenter>
        </documenters> 
     </ndoc>
   </target> 
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.