José F. Romaniello

Las aventuras y desventuras de un codificador.

Luego de haber creado el script, como explique anteriormente en la Parte 1 de esta serie, lo que vamos a hacer es añadir nuestro proyecto a un servidor de integración continua. Para este ejemplo voy a utilizar TeamCity, aunque existen otros como CruiseControl, y el propio de TFS, Team Foundation Build.

TeamCity es por el momento mi preferido ya que su instalación es sencilla, su versión Profesional es gratuita y se integra muy bien con varias herramientas diferentes.

La instalación de TeamCity queda fuera del alcance de esta entrada, pero es muy sencilla. La única sugerencia sobre este punto es asignar una cuenta de correo, a la aplicación para que pueda comunicarnos cuando “metemos la pata”.

Crear un proyecto

2010-06-21_1726

Nada complicado, lo único que tenemos que hacer aquí es asignarle un nombre y una descripción a nuestro proyecto.

Configurar la conexión al servidor de control de versiones

2010-06-21_1729

Este paso es muy sencillo, aquí le diremos a TeamCity donde esta alojado el proyecto. Para este caso he seleccionado Subversion como control de versiones. Lo próximo que debo decirle es el url del proyecto, un nombre de usuario y contraseña. Luego hay muchas más opciones para configurar, pero los valores que vienen por defecto suelen ser los correctos.

Al final de esta página hay un botón para probar si los datos de conexión son válidos.

Crear una configuración para la generación

A continuación en la solapa “General” de la administración de nuestro proyecto debemos seleccionar la opción “Create Build Configuration”.

El proceso implica una serie de pasos, de los cuales los más importantes son el 2do y el 3ro:

2010-06-21_1736

En el segundo paso lo único que debemos hacer, es seleccionar el VCS root que creamos anteriormente.

A continuación, en el tercer paso, debemos configurar nuestro “Runner” acorde al script de generación que cree en mi post anterior, tal como sigue:

2010-06-21_1740

Build Runner; selccionamos MsBuild, ya que es la plataforma sobre la cual escribimos el script.

Build file path; nuestro xml de entrada, el nombre que le dimos en el post anterior fue default.build.

Luego lo más importante en esta página es la última configuración, que le dice a TeamCity que tiene que importar reportes de NUnit desde el path “$(OutputPath)\Reports\*.xml”.

Y esto es básicamente todo lo que hay que hacer para tener nuestro proyecto bajo integración continua. Los demás pasos tienen opciones por defecto que para proyectos simples son suficientes, y a medida que vayamos necesitando mas cosas como dependencias entre distintos proyectos dentro de nuestro proceso, podremos ir configurando.

| More

En esta serie de posts voy a explicar como empezar a trabajar con integración continua. Como el título lo dice, la razón por la que una persona o un equipo de desarrollo debería usar integración continua, trasciende el alcance de estos artículos. Deberías usarlo y ya!
El primer post va a estar dedicado a explicar como crear un “build script” para una solución de .Net.

Nota: Quiero agradecerle a Germán Schuager por enseñarme varias de las cosas que voy a explicar.

El script realizará los siguientes 4 pasos:

  1. Actualizar el número de versión en los archivos AssemblyInfo.cs, esto hará que nuestros resultados; (dlls, exes, etc) tengan un número de versión cuyo Build (recordar esquema Major.Minor.Build) coincida con la revisión de subversion.
  2. Compilar la solución, esto incluye todos sus proyectos.
  3. Ejecutar los tests de todos los proyectos de tests.
  4. Y finalmente copiar a una carpeta especifica el resultado de nuestro build.

Para ello utilizaremos la herramienta MsBuild.

Estructura de directorios

A continuación se muestra una estructura de directorios típica que se suele utilizar:

image

 

  1. Es un ejemplo de estructura de directorios que suelo utilizar.
  2. El contenido de la carpeta “Tools”. Se puede ver adentro dos carpetas
    • “msbuildtasks”: En esta carpeta colocaremos los binarios del proyecto MsBuild Community Tools. Este proyecto tiene Tasks adicionales para MsBuild.  Ya que el mismo, no conoce como interactuar con subversion, nunit u otras herramientas out-of-the-box. Un ejemplo de los archivos que van en este directorio se puede ver en ( 3 )
    • “nunit”: En esta carpeta colocaremos los binarios de nunit. Un ejemplo de los archivos que van en este directorio se puede ver en ( 4 ).

Common.targets

Al igual que todo el contenido de la carpeta “Tools”, se puede copiar prácticamente sin modificación alguna, de proyecto en proyecto. En el archivo common.targets resolveremos algunos asuntos, que ha continuación se pueden ver como comentarios de xml:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">

    <!-- Definicion  de Paths y algunas variables en general. -->
    <PropertyGroup>
        <MSBuildCommunityTasksPath>.</MSBuildCommunityTasksPath>
        <ToolsPath>Tools</ToolsPath>
        <NUnitPath>$(ToolsPath)\nunit</NUnitPath>
        <OutputPath>output</OutputPath>
        <ReportsPath>$(OutputPath)\Reports</ReportsPath>
        <ResultPath>$(OutputPath)\Build</ResultPath>
    </PropertyGroup>
    
    <!-- Importamos MsBuild Community Tasks. -->
    <Import Project="msbuildtasks\MSBuild.Community.Tasks.Targets" />
    
    <PropertyGroup>
        <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
        <FullVersion>$(BUILD_NUMBER)</FullVersion>
    </PropertyGroup>

    <!-- Tarea para actualizar mis AssemblyInfo.cs -->
    <Target Name="UpdateAssemblyInfos">
        <Message Text="Updating version numbers to $(FullVersion)..." />

        <CreateItem Include=".\**\AssemblyInfo.cs">
            <Output TaskParameter="Include" ItemName="AssemblyInfos"/>
        </CreateItem>
        
        <FileUpdate Condition="'$(FullVersion)' != ''"
            Files="@(AssemblyInfos)"
            Regex="\[\s*assembly\s*:\s*AssemblyVersion\s*\(\s*&quot;[\d\.\*]+&quot;\s*\)\s*\]"
            ReplacementText="[assembly: AssemblyVersion(&quot;$(FullVersion)&quot;)]" />
        <FileUpdate Condition="'$(FullVersion)' != ''"
            Files="@(AssemblyInfos)"
            Regex="\[\s*assembly\s*:\s*AssemblyFileVersion\s*\(\s*&quot;[\d\.\*]+&quot;\s*\)\s*\]"
            ReplacementText="[assembly: AssemblyFileVersion(&quot;$(FullVersion)&quot;)]" />
        <FileUpdate Condition="'$(FullVersion)' != ''"
            Files="@(AssemblyInfos)"
            Regex="\[\s*assembly\s*:\s*AssemblyInformationalVersion\s*\(\s*&quot;.*&quot;\s*\)\s*\]"
            ReplacementText="[assembly: AssemblyInformationalVersion(&quot;$(FullVersion)&quot;)]" />
    </Target>
    
    <!-- Tarea para compilar mi solucion en la configuracion seleccionada (debug o release)  -->
    <Target Name="DefaultBuild">
        <Message Text="Building $(SolutionFile)..." />
        <MSBuild Projects="$(SolutionFile)"
           Properties="Configuration=$(Configuration)" 
           ContinueOnError="false"   />
    </Target>
    
    <!-- Tarea para correr todos mis tests en el grupo de items definido para tal caso TestAssemblies -->
    <!-- Esta tarea genera reportes de nunit en format xml. En el directorio $ReportsPath. -->
    <Target Name="RunTests">
        <Message Text="Cleaning test reports folder..." />
        <RemoveDir Directories="$(ReportsPath)" />
        <MakeDir Directories="$(ReportsPath)" />
        <Exec Command="$(NUnitPath)\nunit-console-x86.exe @(TestAssemblies) /xml=$(ReportsPath)\%(TestAssemblies.Filename).xml" 
            IgnoreExitCode="true" />
    </Target>
    
    <!-- Tarea para copiar el resultado de mi proyecto al directorio $ResultPath -->
    <Target Name="CopyBuildResult">
        <Message Text="Cleaning build output folder..." />
        <RemoveDir Directories="$(ResultPath)" />
        <MakeDir Directories="$(ResultPath)" />
        <Message Text="xxx @(BuildResult)" />
        <Copy SourceFiles="@(BuildResult)" DestinationFolder="$(ResultPath)" />
    </Target>
    
</Project>

Default.build

Este archivo sí es propio de cada solución, y un ejemplo para este proyecto sería el siguiente:

<Project DefaultTargets="All" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">

    <!--Importar common.targets-->
    <Import Project="Tools\common.targets" />

    <!--declaro el path relativo al archivo de solución a compilar-->
    <PropertyGroup>
        <SolutionFile>MiProyecto.sln</SolutionFile>
    </PropertyGroup>
    
    <!--declaro los proyectos de tests que se deberían incluir-->
    <ItemGroup>
        <TestAssemblies Include="*Tests\bin\$(Configuration)\*Tests.dll" />
    </ItemGroup>
    
    <!--declaro la ubicación de los proyectos a incluir-->
    <ItemGroup>
        <BuildResult Include="MiProyecto.GUI\bin\$(Configuration)\*.*" />
    </ItemGroup>        
    
    <!--declaro el target All-->    
    <Target Name="All" DependsOnTargets="UpdateAssemblyInfos; DefaultBuild; RunTests; CopyBuildResult">
    </Target>
    
</Project>

$(Configuration) es una variable que dice si estamos en Debug o Release.
La especificación de proyectos de Tests  puede parecer un poco rara; podríamos haberla declarado de la siguiente manera

<ItemGroup>
    <TestAssemblies Include="MiProyecto.Domain.Tests\bin\$(Configuration)\MiProyecto.Domain.Tests.dll" />
    <TestAssemblies Include="MiProyecto.Data.Tests\bin\$(Configuration)\MiProyecto.Data.Tests.dll" />
</ItemGroup>

El problema que yo tuve con esto, era que cada vez que agregaba un proyecto de tests (cosa que suelo hacer a menudo) tenía que acordarme de modificar este script. Lo cual es poco práctico, por lo tanto aprovechando mi convención [Proyecto a Testear].Tests utilizaremos los wildcards (comodines) de MsBuild, de esta forma podremos agregar proyectos de tests a la solución sin necesidad de modiricar este archivo. Una cosa que deberemos tener especial cuidado, es cuando desde un proyecto de tests referenciamos otro proyecto de tests, cosa que en general trato de evitar.

Build.bat

El archivo build.bat lo utilizaremos para correr el script localmente, ya sea para probar el script, hacer deployment, etc. Este archivo tiene lo siguiente:

@echo off
call "%VS90COMNTOOLS%..\..\VC\vcvarsall.bat"
msbuild %~dp0default.build /t:All /nologo

La primer línea registra las variables de MsBuild así como el path donde se encuentra la herramienta, y la segunda ejecuta MsBuild. Si se usa Visual Studio 2008, se coloca %VS90COMNTOOLS%… mientras que si se usa 2010, se coloca %VS100COMNTOOLS%. Esto presupone que la maquina donde ejecutaremos el batch tiene instalado Visual Studio. De más esta decir que este no será el caso de nuestro servidor de integración continua.

Cabe destacar que MsBuild, no solamente viene con Visual Studio, sino que también viene con .Net Framework SDK.

Hay más…

Scripts: Lo que hice aquí con MsBuild, puede hacerse con muuuchas otras herramientas diferentes; entre ellas nant, rake, psake, etc. Cada una de ellas tiene sus ventajas y desventajas. Actualmente elijo MsBuild por que hace exactamente lo que quiero, dispone de muchos ejemplos, es sencillo crear nuestras propias tasks directamente en C#, y la mejor de todas… no hace falta instalar prácticamente nada.

Control de versiones: Si están usando un DCVS como Mercurial o GIT se hace un poco mas complicado el tema de asignar un número de versión a los ensamblados ya que estos sistemas no utilizan números para en sus checkins, pero he visto que hay tareas para tal propósito como por ejemplo MSBuild Mercurial Tasks.

Frameworks de Testing: Todos los frameworks de tests unitarios disponen de una herramienta para correr los tests en modo consola, así que debería ser trivial en caso que usen algo como xUnit o MbUnit. En el caso particular de MsTests es mas sencillo ya que esta soportado nativamente por msbuild.

En el próximo post voy a mostrar como dar de alta esto en nuestro servidor de integración continua.

| More

At uNhAddIns we have something called IEntityValidator and it is implemented for Castle Validations, NHibernate Validator, Validation Application Block and DataAnnotations. You can read Fabio Maulo’s post here.

Another interesting point of the DataAnnotations implementation is that I wrote a cache for the attributes and properties (there is not concept of engine in data annotations). You can see the whole pseudo-engine here.

Usage

Step 1, register your IEntityValidator in your container;

container.Register(Component.For<IEntityValidator>()
    .ImplementedBy<uNhAddIns.DataAnnotations.EntityValidator>()
    .Named("da-entity-validator"));

Step 2, inject your IEntityValidator any where, an example usage is in a base viewmodel (wpf), for implementing IDataErrorInfo

public class ViewModelBase
    : IDataErrorInfo
{
    string IDataErrorInfo.this[string columnName]
    {
        get
        {
            var errors = ServiceLocator.Current
                .GetInstance<IEntityValidator>("da-entity-validator")
                .Validate(this, columnName)
                .Select(iv => iv.Message)
                .ToArray();

            return string.Join(Environment.NewLine, errors);
        }
    }

    string IDataErrorInfo.Error
    {
        get
        {
            var errors = ServiceLocator.Current
                .GetInstance<IEntityValidator>("da-entity-validator")
                .Validate(this)
                .Select(iv => iv.Message)
                .ToArray();

            return string.Join(Environment.NewLine, errors);
        }
    }
}

I have registered the service with a key because, I've two services for IEntityValidator in my application. One for NHV used to validate entities, and another one for Data Annotations used to validate ViewModels.

| More

I will show in this post the why of linqspecs.

As an example; lets imagine the domain of Chinook where you have Artists, Albums and Tracks. I will start with this simple View Model;

public class AlbumBrowserViewModel
{
    public Artist SelectedArtist { get; set; }
    
    public DateTime? ReleasedDateFrom { get; set; }
    public DateTime? ReleasedDateTo { get; set; }

    public void Filter()
    {
        CurrentAlbums = 
    }

    public IEnumerable<Album> CurrentAlbums { get; private set; }
}

Pay attention to the line 10, because now I will explain four different approaches to query the data, abstracting the data access code and the separation of concerns for this case.


Data Access Object pattern (without Expressions)

We are going to start a Data Access Object with the following interface:

public interface IAlbumDao
{
    IEnumerable<Album> GetAlbumByArtist(Artist artist);
    IEnumerable<Album> GetAlbumReleasedBetweenDates(DateTime? fromDate, DateTime? toDate);
    IEnumerable<Album> GetAlbumByArtistReleasedBetweenDates(Artist artist, DateTime? fromDate, DateTime? toDate);
}

So, now we can start writing the first test as follows:

public void can_filter_by_artist()
{
    var dao = new Mock<IAlbumDao>();
    var rollingStones = new Artist();
    var someAlbum = new Album();
    var albumList = new List<Album>{someAlbum};

    dao.Setup(d => d.GetAlbumByArtist(rollingStones))
        .Returns(albumList);

    var browser = new AlbumBrowserViewModel(dao.Object);
    
    browser.SelectedArtist = rollingStones;
    browser.ReleasedDateFrom = null;
    browser.ReleasedDateTo = null;

    browser.Filter();

    browser.CurrentAlbums
        .Should().Have.SameValuesAs(albumList);
}

Injecting the dao in the view model, the implementation, so far is:

public void Filter()
{
    CurrentAlbums = albumDao.GetAlbumByArtist(SelectedArtist);
}
but in the end it will looks like:
public void Filter()
{
    if(SelectedArtist != null 
    && (ReleasedDateFrom == null || ReleasedDateTo == null))
    {
        CurrentAlbums = albumDao.GetAlbumByArtist(SelectedArtist);
        return;
    }
    if(SelectedArtist == null 
    && (ReleasedDateFrom != null || ReleasedDateTo != null))
    {
        CurrentAlbums = albumDao
                .GetAlbumReleasedBetweenDates(ReleasedDateFrom, ReleasedDateTo);
        return;
    }
    if(SelectedArtist != null 
    && (ReleasedDateFrom != null || ReleasedDateTo != null))
    {
        CurrentAlbums = albumDao
                .GetAlbumByArtistReleasedBetweenDates(ReleasedDateFrom, ReleasedDateTo);
        return;
    }
    CurrentAlbums = albumDao.GetAllAlbums(); //What?
}

As you can see our Dao needs three different methods for these queries, although you can call the GetAlbumByArtistReleasedBetweenDates method always (sometimes with null parameters) but still suffer from a severe symptom: this is not flexible. On the other hand it violates the SOLID principles, because often you will need to touch the IDao and Dao. So forget about the Open Closed principle.

Pros:
  • Easy to test.
  • You can use the full feature set of the ORM inside the method.
Cons:
  • Violates OCP.
  • You could end with DAOs with 30 query methods inside.

Data Access Object pattern (with Expressions)

Now, I will talk about this interface:

public interface IDao<T>
{
    IEnumerable<T> Retrieve(Expression<Func<T, bool>> predicate);
}

This Data Access Object is much better than the former, but yet still have an issue. This IDao is hard, and almost impossible to mock. The main problem is that you can’t easily compare expressions. So, in my personal experience i know two ways of mocking it:

  • Compile the expression and assert the return value for some inputs.
  • Use a DaoStub with an in-memory collection inside.

I am not going to talk about the former, because I’ve a post about the subject.

The second is easy to understand, reading this test:

public void can_filter_by_artist()
{
    var rollingStones = new Artist();
    var someGirls = new Album {Artist = rollingStones};
    var daoStub = new DaoStub<Album>()
                    .Insert(someGirls, new Album());
    
    var browser = new AlbumBrowserViewModel(daoStub);
    
    browser.SelectedArtist = rollingStones;
    browser.ReleasedDateFrom = null;
    browser.ReleasedDateTo = null;

    browser.Filter();

    browser.CurrentAlbums
        .Should().Contain(someGirls)
                 .And.Have.Count.EqualTo(1);
}

The DaoStub<T>:
public class DaoStub<T> : IDao<T>
{
    private readonly ICollection<T> repository = new List<T>();

    public IDao<T> Insert(params T[] someGirls)
    {
        someGirls.ToList().ForEach(repository.Add);
        return this;
    }

    public IEnumerable<T> Retrieve(Expression<Func<T, bool>> predicate)
    {
        return repository.Where(predicate.Compile());
    }
}

and the implementation to pass the first test:

public class AlbumBrowserViewModel
{
    private readonly IDao<Album> albumDao;

    public AlbumBrowserViewModel(IDao<Album> albumDao)
    {
        this.albumDao = albumDao;
    }

    public Artist SelectedArtist { get; set; }
    
    public DateTime? ReleasedDateFrom { get; set; }
    public DateTime? ReleasedDateTo { get; set; }

    public void Filter()
    {
        CurrentAlbums = albumDao
            .Retrieve(a => a.Artist == SelectedArtist);
    }

    public IEnumerable<Album> CurrentAlbums { get; private set; }
}

I like this approach, however the two methods has a conceptual issue, you are “testing” a query that will be never executed. The linq provider for objects may work different than the Entity framework linq provider, or the NHibernate provider. So, you need to test the queries against the database provider, and if is possible I will highly recommend to test against the real world database. To summarize

Pros:

  • Very flexible and powerful.
  • Doesn’t violate OCP.

Cons:

  • It is hard to test and mock.
  • If you don’t use carefully you can end with a lot of predicates all over the code.

Query object pattern (or my style of query object pattern)

Forget about the dao, and start thinking in the following interface:

public interface IAlbumBrowseQuery
{
    Artist SelectedArtist { get; set; }
    DateTime? ReleasedDateFrom { get; set; }
    DateTime? ReleasedDateTo { get; set; }

    IEnumerable<Album> Execute();
}

For this case, you will only have to map, the UI fields to the query properties, but believe me there are few cases like this. I have named the query like the use case on purpose, because for this case I don’t want anybody modifying this interface unless is required for the use case.

The test and implementation are very simple, and you can mock this interface as we have mocked the first IDao. So, you can test your ViewModel.

Pros:

  • If you give to the query an specific responsibility, this pattern does not violate the OCP principle.
  • You can use the full featured set of the ORM tool, for instance, you can write your queries even in hql but for this case I will highly recommend you the Criteria API. This is not so important when you are using EF because linq is the only way you can write queries.

Cons:

  • You have to write an interface and the implementation per each query.
  • This pattern is less flexible than using dao with expression, if you only use this pattern you can end with a lot of query objects.

LinqSpecs; my baby project

I will use a DAO interface like this:

public interface IDao<T>
{
    IEnumerable<T> Retrieve(Specification<T> specification);
}

And I’ve two specs like this:

public class ByArtistSpecification : Specification<Album>
{
    public ByArtistSpecification(Artist artist)
    {
        Artist = artist;
    }

    public Artist Artist { get; private set; }

    public override Expression<Func<Album, bool>> IsSatisfiedBy()
    {
        return a => a.Artist == Artist;
    }

    protected override object[] Parameters { get { return new[]{ Artist }; } }
}

You don’t need to mock this class in order to test your ViewModel. Lets add a test for this VM:

public void can_filter_by_artist()
{
    var rollingStones = new Artist();
    var someGirls = new Album ();
    var dao = new Mock<IDao<Album>>();


    dao.Setup(d => d.Retrieve(new ByArtistSpecification(rollingStones)))
        .Returns(new List<Album>{someGirls});

    var browser = new AlbumBrowserViewModel(dao.Object);
    
    browser.SelectedArtist = rollingStones;
    browser.ReleasedDateFrom = null;
    browser.ReleasedDateTo = null;

    browser.Filter();

    browser.CurrentAlbums
        .Should().Contain(someGirls);
}

It doesn't matter that you have two instances of ByArtistSpecification, they are equals if they share the same parameters (last method in the specification.

In fact I can write a setup like this:

dao.Setup(d => d.Retrieve(new ByArtistSpecification(rollingStones) &
                          new ByReleasedDateRangeSpecification(new DateTime(1984, 1, 1), 
                                                               new DateTime(1989, 1, 1))))
    .Returns(new List<Album>{someGirls});

 

Pros:

  • You write tests for what you should test
  • It is very flexible and powerful
  • You write less than with query objects
  • You can operate specifications &, | and ! are supported.
  • You can easily test the specifications targeting against the real provider.
  • Specifications are cheaper, you can even serialize them-

Cons:

  • Only works for linq, so you can access the full feature set of your ORM.
  • You need to write specifications (yet another artifact) :)

It goes without saying that the specification pattern is not a silver bullet, so you should choose the best pattern to meet your needs.

 

Thank you all! and I’m waiting for your comments.

| More

For those who do not follow me on Twitter.

I’ve created a new open source project named “LinqSpecs” in codeplex with the same concept described in this post.

The project is hosted here; http://linqspecs.codeplex.com/.

Have a look and try it!

| More