Dec 15 2009

Integrate NUnit tests and results into CruiseControl.NET

Category: Continuous Integrationryancmartin1976 @ 21:04

First Strategy:

Second Strategy:

Integrating useful Unit Tests into CruiseControl.NET in ASP.NET Web Application

Unit tests can be a waste of time, especially if your writing them for every method looking for every possible way it could break. I found a very useful way to use unit tests in a project.

The entire project is data driven. All of the content lives in a database, even the page title, keywords and description.

This leaves a lot of room for possible failure points and unacceptable mistakes. Every page should have content on it, which goes without saying, but sometimes we forget to add the Meta data for pages thinking we will get back to it later and just forget. This is not a good scenario when the site depends heavily on SEO.

I have 4 projects:

  1. Web (UI)
  2. Core (Data Access)
  3. WebTests (UI Tests)
  4. CoreTests (Data Access Tests)

We are going to focus on the Web project and WebTests project.

Web project: Example web page that grabs content and Meta data from the database

About.aspx.cs

public partial class About : System.Web.UI.Page, IAbout
{
    private AboutPresenter _presenter;
    protected override void OnInit(EventArgs e)
    {
        _presenter = new AboutPresenter();
        _presenter.Init(this);
        _presenter.PerformLoad(MethodType.Page.Company_About.GetStringValue(),
                               null,
                               MethodType.Section.Main.GetStringValue());
    }
 
    public void LoadHtml(PageFT pageFt, PageContentFT pageContentFt)
    {
        if (pageFt == null)
        {
            Log.Error(this, "There is not any page details for this page.");
        }
        else if (pageFt.IsArchived)
        {
            Log.Error(this, "This page is archived and should not be allowed to be to viewed by users.");
        }
        else
        {
            BuildHeader(pageFt.Title, pageFt.Keywords, pageFt.Description);
            if (pageContentFt == null)
                Log.Error(this, "There is not any content for this page for the " + pageFt.Language + " language");
            else
                BuildPageContent(pageContentFt.PageContent);
        }
    }
 
    private void BuildHeader(string title, string keywords, string description)
    {
        litTitle.Text = "<title>" + title + " </title>";
        litKeywords.Text = "<meta name=\"keywords\" content=\"" + keywords + "\">";
        litDescription.Text = "<meta name=\"description\" content=\"" + description + "\">";
    }
 
    private void BuildPageContent(string pageContent)
    {
        litPageContent.Text = pageContent;
    }
}

AboutPresenter.cs

public class AboutPresenter
{
    private IAbout _view;
    private IUserSession _userSession;
    private IPageService _pageService;
    private IPageContentService _pageContentService;
 
    public void Init(IAbout view)
    {
        _view = view;
        _userSession = ObjectFactory.GetInstance<IUserSession>();
        _pageService = ObjectFactory.GetInstance<IPageService>();
        _pageContentService = ObjectFactory.GetInstance<IPageContentService>();
    }
 
    public void PerformLoad(string page, string lang, string section)
    {
        lang = string.IsNullOrEmpty(lang) ? _userSession.Culture : lang;
 
        if (string.IsNullOrEmpty(lang))
        {
            Log.Error(this, "Users browser culture is not getting passed in by method parameter or session variable.");
            lang = MethodType.Language.English.GetStringValue();
        }
 
        if (string.IsNullOrEmpty(page))
        {
            Log.Error(this, "Page name was not passed into the PerformLoad method which is needed.");
            return;
        }
 
        if (string.IsNullOrEmpty(section))
        {
            Log.Error(this, "Section name was not passed into the PerformLoad method which is needed.");
            return;
        }
 
        PageFT pageFt = _pageService.GetPageInfoByName(page, lang);
        PageContentFT pageContentFt = _pageContentService.GetPageContentBySection(page, lang, section);
        _view.LoadHtml(pageFt, pageContentFt);
    }
}

WebTests project: this unit test checks for a specific pages content and Meta data in all possible translated languages

AboutPresenterTester.cs

[TestFixture]
public class Company_About_Tester
{
    MockAboutPageView pageView = new MockAboutPageView();
    MockAboutPageTitleView pageTitleView = new MockAboutPageTitleView();
    MockAboutPageKeywordsView pageKeywordsView = new MockAboutPageKeywordsView();
 
    MockAboutPageContentView pageContentView = new MockAboutPageContentView();
    MockAboutPageContentHtmlView pageContentHtmlView = new MockAboutPageContentHtmlView();
 
    private MockRepository _mocks;
    private AboutPresenter _presenter;
 
    private string pageName;
    private string languageEnUs;
    private string languageDeDe;
    private string sectionMain;
 
    [SetUp]
    public void SetUp()
    {
        TestUtil.SetUpHttpContext();
        _mocks = new MockRepository();
        _presenter = _mocks.Stub<AboutPresenter>();
 
        pageName = MethodType.Page.Company_About.GetStringValue();
        languageEnUs = MethodType.Language.English.GetStringValue();
        languageDeDe = MethodType.Language.German.GetStringValue();
        sectionMain = MethodType.Section.Main.GetStringValue();
    }
 
    [Test]
    public void Company_About_Page_Record_Exists_In_Database_English()
    {
        _presenter.Init(pageView);
        _presenter.PerformLoad(pageName, languageEnUs, sectionMain);
    }
 
    [Test]
    public void Company_About_Page_Title_Exists_In_Database_English()
    {
        _presenter.Init(pageTitleView);
        _presenter.PerformLoad(pageName, languageEnUs, sectionMain);
    }
 
    [Test]
    public void Company_About_Page_Keywords_Exists_In_Database_English()
    {
        _presenter.Init(pageKeywordsView);
        _presenter.PerformLoad(pageName, languageEnUs, sectionMain);
    }
 
    [Test]
    public void Company_About_PageContent_Record_Exists_In_Database_English()
    {
        _presenter.Init(pageContentView);
        _presenter.PerformLoad(pageName, languageEnUs, sectionMain);
    }
 
    [Test]
    public void Company_About_PageContent_Html_Exists_In_Database_English()
    {
        _presenter.Init(pageContentHtmlView);
        _presenter.PerformLoad(pageName, languageEnUs, sectionMain);
    }
 
    [Test]
    public void Company_About_PageDetails_German_NotNull()
    {
        _presenter.Init(pageView);
        _presenter.PerformLoad(pageName, languageDeDe, sectionMain);
    }
 
    [Test]
    public void Company_About_PageDetails_German_TitleNotEmpty()
    {
        _presenter.Init(pageTitleView);
        _presenter.PerformLoad(pageName, languageDeDe, sectionMain);
    }
 
    [Test]
    public void Company_About_PageDetails_German_KeywordsNotEmpty()
    {
        _presenter.Init(pageKeywordsView);
        _presenter.PerformLoad(pageName, languageDeDe, sectionMain);
    }
 
    [Test]
    public void Company_About_PageContent_German_NotNull()
    {
        _presenter.Init(pageContentView);
        _presenter.PerformLoad(pageName, languageDeDe, sectionMain);
    }
 
    [Test]
    public void Company_About_PageContent_German_HtmlNotEmpty()
    {
        _presenter.Init(pageContentHtmlView);
        _presenter.PerformLoad(pageName, languageDeDe, sectionMain);
    }
}
 
public class MockAboutPageView : IAbout
{
    public void LoadHtml(PageFT pageFt, PageContentFT pageContentFt)
    {
        Assert.IsNotNull(pageFt);
    }
}
 
public class MockAboutPageTitleView : IAbout
{
    public void LoadHtml(PageFT pageFt, PageContentFT pageContentFt)
    {
        Assert.IsNotNull(pageFt.Title);
        Assert.IsNotEmpty(pageFt.Title);
    }
}
 
public class MockAboutPageKeywordsView : IAbout
{
    public void LoadHtml(PageFT pageFt, PageContentFT pageContentFt)
    {
        Assert.IsNotNull(pageFt.Keywords);
        Assert.IsNotEmpty(pageFt.Keywords);
    }
}
 
public class MockAboutPageContentView : IAbout
{
    public void LoadHtml(PageFT pageFt, PageContentFT pageContentFt)
    {
        Assert.IsNotNull(pageContentFt);
    }
}
 
public class MockAboutPageContentHtmlView : IAbout
{
    public void LoadHtml(PageFT pageFt, PageContentFT pageContentFt)
    {
        Assert.IsNotNull(pageContentFt.PageContent);
        Assert.IsNotEmpty(pageContentFt.PageContent);
    }
}

Now we have to setup the configuration file for CruiseControl.NET and add a task in the NAnt build script.

CruiseControl.NET ccnet.config

<project name="Test">
  <labeller type="defaultlabeller" />
  <webURL>http://localhost/ccnet/ViewFarmReport.aspx</webURL>
 
  <sourcecontrol type="filtered">
    <sourceControlProvider type="svn" autoGetSource="true">
      <executable>C:\Program Files\VisualSVN Server\bin\svn.exe</executable>
      <trunkUrl>http://domain.com/svn/ProjectName/trunk</trunkUrl>
      <workingDirectory>D:\Projects\compile\ProjectName\trunk</workingDirectory>
      <tagOnSuccess>false</tagOnSuccess>
      <tagBaseUrl>http://domain.com/svn/ProjectName/tags</tagBaseUrl>
      <username>rmartin</username>
      <password>*******</password>
    </sourceControlProvider>
    <inclusionFilters>
      <pathFilter>
        <pattern>**/*.*</pattern>
      </pathFilter>
    </inclusionFilters>
  </sourcecontrol>
 
  <tasks>
    <msbuild>
      <executable>C:\WINDOWS\Microsoft.NET\Framework\v3.5\MSBuild.exe</executable>
      <workingDirectory>D:\Projects\compile\ProjectName\trunk\Source</workingDirectory>
      <projectFile>ProjectNameWeb.sln</projectFile >
      <buildArgs>
        /noconlog
        /noconsolelogger /v:quiet
        /p:Configuration=Debug
        /p:ReferencePath="D:\Projects\compile\ProjectName\trunk\Binaries\NUnit"
      </buildArgs>
      <targets>ReBuild</targets >
      <timeout>600</timeout >
      <logger>c:\Program Files\CruiseControl.NET\server\Rodemeyer.MsBuildToCCNet.dll</logger>
    </msbuild>
 
    <nunit>
      <path>D:\Projects\compile\ProjectName\trunk\Binaries\NUnit\nunit-console.exe</path>
      <assemblies>
        <assembly>D:\Projects\compile\ProjectName\trunk\Source\ProjectNameWebTests\bin\Debug\ProjectName.ProjectNameWebTests.dll</assembly>
      </assemblies>
    </nunit>
  </tasks>
 
  <publishers>
    <merge>
      <files>
        <file>C:\Program Files\CruiseControl.NET\server\Test\Artifacts\results.xml</file>
      </files>
    </merge>
    <xmllogger />
    <statistics />
    <email
      from="ProjectNameBuild@domain.com"
      mailhost="mail.domain.com" includeDetails="TRUE">
      <users>
        <user name="Ryan Martin" group="buildmaster" address="rmartin@domain.com"/>
      </users>
      <groups>
        <group name="buildmaster" notification="always"/>
      </groups>
    </email>
  </publishers>
</project>

NAnt build script

<project name="ProjectNameWeb" default="build" xmlns="http://nant.sf.net/release/0.85/nant.xsd">
  <!-- Properties -->
  <property name="devserver.website.dir" value="D:\Projects\www\ProjectName"/>
  <property name="trunk.dir" value="..\" />
  <property name="source.dir" value="${trunk.dir}\Source" />
  <property name="msbuild.exe" value="C:\WINDOWS\Microsoft.NET\Framework\v3.5\msbuild.exe" />
  <property name="solution.sln" value="${source.dir}\ProjectNameWeb.sln" />
 
  <!-- Called Externally -->
  <target name="build" depends="compile" />
  <target name="test" depends="compile, test.project.ProjectNameWeb" />
 
  <!-- Coding Tasks -->
  <target name="move.assemblies.for.tests" depends="compile">
    <echo message="Moving contents of ${ProjectNameWebTests.bin.dir} to ${assemblies.output.dir}" />
 
    <copy todir="${assemblies.output.dir}" flatten="true">
      <fileset basedir="${ProjectNameWebTests.bin.dir}">
        <include name="*.dll" />
      </fileset>
    </copy>
  </target>
 
  <target name="test.project.ProjectNameWeb" depends="move.assemblies.for.tests">
    <echo message="Starting to run tests for ProjectNameWeb" />
 
    <nunit2 haltonfailure="false" failonerror="false" verbose="true">
      <formatter type="Xml" extension=".xml" outputdir="${nunit.output.dir}" usefile="true" />
      <test assemblyname="${assemblies.output.dir}\ProjectNameWebTests.dll" />
    </nunit2>
  </target>
</project>

Run the task through CruiseControl.NET Web dashboard and viewed the results.

After click on the FORCE button for Test click on the most recent build number

And view the results by clicking the Nunit Details link on the left hand panel

What we make out of this test is the About.aspx page has content for the body, title and keywords in English but that content has either not been translated into German yet or it should be and now we know there is a problem that needs to be addressed

Tags: , , , ,