Tuesday, October 20, 2009

Running MSpec with TFS Team Build

Recently, I’ve become a big fan the .net specification framework Machine.Specifications, or MSpec.  It has helped us define our requirements and communicate in a common language with our Business Analysts.  Rob Conery had an awesome introduction to using MSpec. I’m going to piggy-back on his example here.

We use Microsoft Team Foundation Server for our continuous integration (as well as work item tracking…more on that later), and I wanted to be able to provide the MSpec report to our developers and BAs automatically for each build.  Here is what I came up with.

Versions :

  • Microsoft Team Foundation Server 2008 SP1
  • Machine.Specifications 0.2.0.0

This assumes that you already have a Specs project set up.  You will also need Machine.Specifications.ConsoleRunner.exe accessible on the Build Server.  You could either install the ConsoleRunner in a well known folder on the (each) build server, or you could include it right in source control.  I chose to include it in source control.

  • In my Specs project, I created a Dependencies folder and dropped in…
    • CommandLine.dll
    • Machine.Specifications.ConsoleRunner.exe
    • Machine.Specifications.dll
    • Machine.Specifications.NUnit.dll
    • Machine.Specifications.Reporting.dll
  • From the Specs project, I added references to…
    • Machine.Specifications.dll
    • Machine.Specifications.NUnit.dll. 
  • For each of the dependencies above, I made sure to set the “Build Action” to “Content” and the the “Copy to Output Directory” to “Copy Always”.  Again, you could just include these files in a folder such as “C:\Program Files\MSpec” and call it there. 

So, my solution ended up looking like this.

MSpecSolution

  • In Team Explorer, I had previously created a continuous integration build, named “CI Build”.

MSpecCIBuild

  • I set the local folder for the workspace to a known location, at “F:\MSpecTestSource”.

MSpecWorkspace

  • Next, I needed to edit my Team Build project file.  This is located in the TeamBuildTypes folder in the Team Explorer Source Control Window.  You have to Check Out this file, edit it, and Check it back In.

MSpecTeamBuildTypes

MSpecCheckOutBuild

  • Finally, in the TFSBuild.proj file, I added a target to run the MSpec Console Runner.  By default the TFSBuild.proj file ends with

<ItemGroup>
<!--  ADDITIONAL REFERENCE PATH
The list of additional reference paths to use while resolving references. For example:

<AdditionalReferencePath Include="C:\MyFolder\" />
<AdditionalReferencePath Include="C:\MyFolder2\" />
-->
</ItemGroup>
</Project>


  • Copy the following code after the </ItemGroup> and before the </Project> tags


<Target Name="AfterDropBuild">
<BuildStep TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="Mspec Report"
Message="Running MSpec">
</BuildStep>

<PropertyGroup>

<!--The Path to the console runnner-->
<MSpecConsoleRunner>&quot;$(BuildDirectory)\Binaries\Release\Dependencies\Machine.Specifications.ConsoleRunner.exe&quot;</MSpecConsoleRunner>

<!--The Path to the Spec file-->
<MSpecSpecClass>&quot;$(BuildDirectory)\Binaries\Release\MSpecTest.Specs.dll&quot;</MSpecSpecClass>

<!--Html or Xml?-->
<MSpecReportFormat>--html</MSpecReportFormat>

<!--The drop location for the report-->
<MSpecReportPath>&quot;$(DropLocation)\$(BuildNumber)\MSpecTestReport.html&quot;</MSpecReportPath>

<MSpecCommandLine>$(MSpecConsoleRunner) $(MSpecSpecClass) $(MSpecReportFormat) $(MSpecReportPath)</MSpecCommandLine>
</PropertyGroup>

<Exec Command="$(MSpecCommandLine)" ContinueOnError="true" />
</Target>


The variable names (<MSpecConsoleRunner />, <MSpecSpecClass />) are insignificant.  You will need to change the variables to point to your paths.  I created some MSBuild debug statements that you can copy and paste below the </BuildStep> and above the <PropertyGroup> above to help determine what paths Team Build is using.  You can view these messages in the BuildLog for each build.



<!--<Message Text="DEBUGMESSAGE:BinariesRoot = $(BinariesRoot)" />
<Message Text="DEBUGMESSAGE:BinariesSubdirectory = $(BinariesSubdirectory)" />
<Message Text="DEBUGMESSAGE:BuildAgentName = $(BuildAgentName)" />
<Message Text="DEBUGMESSAGE:BuildAgentUri = $(BuildAgentUri)" />
<Message Text="DEBUGMESSAGE:BuildBreak = $(BuildBreak)" />
<Message Text="DEBUGMESSAGE:BuildDefinition = $(BuildDefinition)" />
<Message Text="DEBUGMESSAGE:BuildDefinitionId = $(BuildDefinitionId)" />
<Message Text="DEBUGMESSAGE:BuildDefinitionName = $(BuildDefinitionName)" />
<Message Text="DEBUGMESSAGE:BuildDefinitionUri = $(BuildDefinitionUri)" />
<Message Text="DEBUGMESSAGE:BuildDirectory = $(BuildDirectory)" />
<Message Text="DEBUGMESSAGE:BuildNumber = $(BuildNumber)" />
<Message Text="DEBUGMESSAGE:BuildProjectFolderPath = $(BuildProjectFolderPath)" />
<Message Text="DEBUGMESSAGE:BuildUri = $(BuildUri)" />
<Message Text="DEBUGMESSAGE:ConfigurationFolderUri = $(ConfigurationFolderUri)" />
<Message Text="DEBUGMESSAGE:DropLocation = $(DropLocation)" />
<Message Text="DEBUGMESSAGE:IsDesktopBuild = $(IsDesktopBuild)" />
<Message Text="DEBUGMESSAGE:LastBuildNumber = $(LastBuildNumber)" />
<Message Text="DEBUGMESSAGE:LastGoodBuildLabel = $(LastGoodBuildLabel)" />
<Message Text="DEBUGMESSAGE:LastGoodBuildNumber = $(LastGoodBuildNumber)" />
<Message Text="DEBUGMESSAGE:MachineName = $(MachineName)" />
<Message Text="DEBUGMESSAGE:MaxProcesses = $(MaxProcesses)" />
<Message Text="DEBUGMESSAGE:Port = $(Port)" />
<Message Text="DEBUGMESSAGE:NoCICheckinComment = $(NoCICheckinComment)" />
<Message Text="DEBUGMESSAGE:Reason = $(Reason)" />
<Message Text="DEBUGMESSAGE:RequestedBy = $(RequestedBy)" />
<Message Text="DEBUGMESSAGE:RequestedFor = $(RequestedFor)" />
<Message Text="DEBUGMESSAGE:SolutionRoot = $(SolutionRoot)" />
<Message Text="DEBUGMESSAGE:SourceGetVersion = $(SourceGetVersion)" />
<Message Text="DEBUGMESSAGE:SourcesSubdirectory = $(SourcesSubdirectory)" />
<Message Text="DEBUGMESSAGE:StartTime = $(StartTime)" />
<Message Text="DEBUGMESSAGE:TeamBuildConstants = $(TeamBuildConstants)" />
<Message Text="DEBUGMESSAGE:TeamBuildOutDir = $(TeamBuildOutDir)" />
<Message Text="DEBUGMESSAGE:TeamBuildRefPath = $(TeamBuildRefPath)" />
<Message Text="DEBUGMESSAGE:TeamBuildVersion = $(TeamBuildVersion)" />
<Message Text="DEBUGMESSAGE:TeamFoundationServerUrl = $(TeamFoundationServerUrl)" />
<Message Text="DEBUGMESSAGE:TeamProject = $(TeamProject)" />
<Message Text="DEBUGMESSAGE:TestResultsRoot = $(TestResultsRoot)" />
<Message Text="DEBUGMESSAGE:WorkspaceName = $(WorkspaceName)" />-->

There should now be a Report.html in the drop location. To view this, right click on the build in the Build Explorer, and choose "Open Drop Folder"

Friday, October 9, 2009

Getting SyntaxHighlighter working with Blogger

Yesterday I set up my first blog in order to post some documentation.  As a developer, the first thing that I wanted was to post some code with nice formatting.  The best tool for the job is SyntaxHighlighter.  I saw a lot of different posts on how to get this tool working with Blogger.com, but I couldn’t seem to get a lot of them working.  I tried different versions of the .js files hosted on Google Code and elsewhere, but no luck.  So, here’s what I did that finally worked.

  1. Get a Blogger.com account.  I’ll wait for you.
  2. Sign in to your Blogger account.
  3. View your blog
  4. In the upper right hand corner there is a menu.  Click “Customize”
    Customize
  5. You should see four tabs.  Click “Layout”
    Layout
  6. On the “Layout” tab, click “Edit HTML”
  7. Scroll to the bottom of the html in the textbox, and find the tag “<!-- end outer-wrapper –>”
    OuterWrapper
  8. Paste the following code below the “<!—end outer-wrapper –>” comment, and above the “</body>” tag
  9. <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shCore.js' type='text/javascript'/> 
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushBash.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCpp.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCSharp.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushCss.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushDelphi.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushDiff.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushGroovy.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushJava.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushJScript.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushPhp.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushPlain.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushPython.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushRuby.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushPerl.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushScala.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushSql.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushVb.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushXml.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushAS3.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushJavaFX.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/2.0.320/scripts/shBrushPowerShell.js' type='text/javascript'/>

    <script language='javascript'>
    SyntaxHighlighter.config.bloggerMode = true;
    SyntaxHighlighter.config.stripBrs = true;

    SyntaxHighlighter.all();
    dp.SyntaxHighlighter.HighlightAll('code');
    </script>


  10. I’ve tried Google code and other sites, but after reading the “Hosting” section of the SyntaxHighlighter Wiki, I saw that the wiki site was hosting a version of it’s own.  That was what finally made it work.



Hopefully this points someone in the right direction.

Thursday, October 8, 2009

NHibernate sql-query with stored procedures

I’ve spent a lot of time trying to get Nhibernate to work calling my legacy stored procedures, and have been frustrated by the lack of documentation for any of the Nhibernate edge cases.  In my particular instance, I followed what documentation I could, by kept ending up with a “Named query not known error”.  I finally fixed it by looking at the Nhibernate source and back tracking from there. I figured that I’d document what my problems were in case it helps anyone else out there.

My setup…

  • Nhibernate 2.1 GA
  • FluentNhibernate 1.0 RC
  • SharpArchitecture 1.0
  • My.Data.Shared.dll (Entities, including My.Data.Entities.User.cs.  These are POCO objects)
  • My.Data.Nhibernate dll (Repositories and references to NHibernate)

I obviously can’t show my production code, so I will use a bit of a contrived example. (Also, forgive the example naming conventions.  I tried to use a different name for each piece to make it clear what I was referring to.
My stored procedure is defined as…

ALTER PROCEDURE [dbo].[usp_custom_GetUsers_By_City_Age]
@cityname VARCHAR(255),
@ageinyears int
AS

SELECT
user_id,
city_name,
age_in_years
FROM
tblUsers
WHERE
city_name = @cityname
AND age_in_years = @ageinyears



My Entity is defined as…

User



namespace My.Data.Entities
{
public class User
{
public virtual int Id { get; set; }
public virtual string City { get; set; }
public virtual int Age { get; set; }
}
}



Since I am using FluentNhibernate, I did not have any .hbm files in my solution to begin with.  In the project that was referencing Nhibernate (My.Data.Nhibernate.dll), I added a Queries.hbm.xml file to the root of the project.  Note: This must end in “.hbm.xml”. In the properties window, I made sure to set the “Build Action” to “Embedded Resource” and “Copy To Output Directory” to “Do Not Copy”.  Note : I originally tried setting this to “Conent” and “Copy Always”.  I could not get this to work successfully. 




Properties


Then, where I configured my Nhibernate session, I had to add a line to load all of the hbm files from the assembly.

var config = NHibernateSession.Init(sessionStorage,
new string[] { mappingPath },
new NHPersistance().Generate(),
configPath);

config.AddAssembly("My.Data.NHibernate");



The configuration of the hbm.xml turns out to be the most important part.  Here is mine for this sample…

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="My.Data.Nhibernate"
namespace="My.Data.Entities">

<sql-query name="GetUsersByCityAndAge" callable="true">
<query-param name="City" type="string" />
<query-param name="Age" type="int" />

<return class="User">
<return-property column="user_id" name="Id" />
<return-property column="city_name" name="City" />
<return-property column="age_in_years" name="Age" />
</return>

exec usp_custom_GetUsers_By_City_Age
@cityname = :City,
@ageinyears = :Age
</sql-query>
</hibernate-mapping>



There is a lot going on in here.  The thing that turned out to be the biggest issue causing my “Named Query Not Known” problem was that I was missing the “assembly” attribute and the “namespace” attribute on the “<hibernate-mapping>” node. 



  • The assembly attribute should match the assembly which contains the embedded resource of your hbm.xml file.


  • The namespace attribute should match the namespace of your returned entity.


  • The “<sql-query name=” attribute should be a unique name for your query.  This does not need to match anything else.  You will use this name when calling the stored procedure from code


  • Add a “<query param” for each parameter to the database.  The name of the parameter can be anything.  You’ll use the name in the exec below


  • The “<return class=” attribute should be the name of your entity


  • The “<return-property column=” attribute should match the column that is returned from the stored procedure call


  • The “<return-property name=” attribute should match the name of the property in the Entity


  • When executing the stored procedure, you will call “exec STORED_PROCEDURE_NAME_IN_DATABASE” and pass the parameters.



    • Multiple parameters are separated by columns


    • The parameter name should match the name defined in the stored procedure (i.e. @cityname).


    • The parameter value is the name you defined in the “<query-param name=” attribute





Lastly, we need to call the stored procedure from code. 
My repository is defined as…

namespace My.Data
{
using System.Collections.Generic;
using NHibernate;
using SharpArch.Data.NHibernate;

public partial class UserRepository : NHibernateRepository<My.Data.Entities.User>, IUserRepository
{
public IList<My.Data.Entities.User> GetUsers(string p_city, int p_age)
{
IQuery namedQuery = Session.GetNamedQuery("GetUsersByCityAndAge")
.SetParameter<string>("City", p_city)
.SetParameter<int>("Age", p_age)
;

IList<My.Data.Entities.User> users = namedQuery.List<My.Data.Entities.User>();

return users;
}
}
}