Using RemObjects Train for Automated Testing and Building

Train
I was recently tasked with coming up with an automated build process for one of my projects. Given the available free and commercial tools on the market I decided to give RemObjects Train a try. Train is an Open Source project from RemObjects Software (one of their many Open Source offerings). Benefits of Train include:

  • It is Open Source
  • It has a simple, extensive, extensible API
  • Scripts are friendly to version control (based on ECMAScript rather than, say, XML)
  • The tool is simple, light-weight
  • It’s free – as in beer

My requirements for the build process were limited (one of the reasons I wanted a free, simple tool rather than a capable commercial tool such as FinalBuilder or Automated Build Studio):

  1. Support for running and failing based on unit tests (using MSTest)
  2. Support for building a release configuration of several .NET assemblies
  3. Support for packaging assemblies using Inno Setup
  4. Configurable version number used for assemblies and installer

Train ended up being a very elegant and useable solution. The scripts are easy to create and maintain – I used Sublime Text 2 with its JavaScript syntax highlighting.

In the end I created two scripts: RunTests.train and BuildRelease.train. By naming my scripts with a .train extension I was able to associate them with Train.exe and can run my unit tests and build releases with a simple double-click.

My RunTests script looks like this:

//rebuild unit tests
msbuild.rebuild("../ExchangePurge.UnitTests/ExchangePurge.UnitTests.csproj", { configuration: "Release" });
msbuild.rebuild("../ExchangeSync.UnitTests/ExchangeSync.UnitTests.csproj", { configuration: "Release" });

//function for running MSTest unit tests, detecting failures
function runTest(testAssemblyPath) {
	var mstestPath = "C:/Program Files/Microsoft Visual Studio 11.0/Common7/IDE/MSTest.exe";
	shell.exec(mstestPath, "/testcontainer:" + testAssemblyPath);
}

//run unit tests
runTest("../ExchangePurge.UnitTests/bin/Release/ExchangePurge.UnitTests.dll");
runTest("../ExchangeSync.UnitTests/bin/Release/ExchangeSync.UnitTests.dll");

The script is pretty straight-forward. It builds release versions of my unit test assemblies using the msbuild API provided by Train (API documentation can be found here). It then defines a simple function – runTest – for running unit tests with MSTest.exe using the shell API. Finally, the function is called for each unit test assembly.

My BuildRelease script looks like this:

//run unit tests
run("RunTests.train");

//get version info from VersionInfo.ini
var versionInfoIni = ini.fromFile("VersionInfo.ini");
var appVersion = versionInfoIni.getValue("VersionInfo", "AppVersion", "1.0");

//update the assembly versions for each .NET project using the above version info
msbuild.updateAssemblyVersion("../ExchangePurge/Properties/AssemblyInfo.cs", appVersion);
msbuild.updateAssemblyVersion("../ExchangeSync/Properties/AssemblyInfo.cs", appVersion);
msbuild.updateAssemblyVersion("../ExchangeSyncAdmin/Properties/AssemblyInfo.cs", appVersion);

//rebuild each .NET project
msbuild.rebuild("../ExchangePurge/ExchangePurge.csproj", { configuration: "Release" });
msbuild.rebuild("../ExchangeSync/ExchangeSync.csproj", { configuration: "Release" });
msbuild.rebuild("../ExchangeSyncAdmin/ExchangeSyncAdmin.csproj", { configuration: "Release" });

//export environment variable for InnoSetup to use for app version
export("ES_AppVersion", appVersion);

//build an InnoSetup installer
inno.build("Installer.iss", { });

//copy setup.exe to folder for this version
folder.create("Output/" + appVersion);
file.copy("Output/setup.exe", "Output/" + appVersion);

This script is a bit more complex but still pretty easy to understand. It starts by using the Train API to run the RunTests script. Because of the nature of MSTest.exe and Train.exe, if the RunTests.train script fails then the BuildRelease.train script will be aborted.

The script then uses Train’s ini API to read a configurable version from VersionInfo.ini. Next, the msbuild API is used to both update the assembly version information and build release versions of each assembly.

Next, I used the Train API to export an environment variable – ES_AppVersion – with the version information read earlier from the INI file. My Inno Setup script uses ISPP, which is installed by default if you download an Inno Setup QuickStart Pack. At the top of my Installer.iss Inno Setup script I used the following code (thanks to Carlo from RemObjects):

#define MyAppVersion GetEnv("ES_AppVersion")
#if MyAppVersion == ""
#define MyAppVersion "1.0"
#endif

And then further down use the MyAppVersion ISPP macro:

[Setup]
AppVersion={#MyAppVersion}

This bit of ISPP macro code will make use of the ES_AppVersion environment variable set by the BuildRelease.train script to define the installer’s version.

Finally, the last lines of the BuildRelease.train script use the folder and file Train API’s to copy the created installer to a folder named after the version read from the INI file.

So far I’m very happy with Train as a simple, capable tool for automated testing and building. If you’d like to read more about Train you can refer to Marc Hoffman’s post on the project as well as the official documentation.

2 thoughts on “Using RemObjects Train for Automated Testing and Building

  1. Jeroen Vandezande

    Hi,

    I also use Train for creating my builds.
    A word of warning: when you use the MSBuild.updateAssemblyVersion function you need to make sure that the original file does not contains unicode characters, because the file is stored as ascii. I fixed this in Train and issued a pull request for that, but the current build of train does not includes that.
    I found out after a long search on why that my Copyright signs kept disappearing :)

    concerning InnoSetup: I have the setup version the same as the exe version so I do this:

    #include “scriptsproducts.iss”
    #include “scriptsproductswinversion.iss”
    #include “scriptsproductsfileversion.iss”
    #include “scriptsproductsmsi31.iss”
    #include “scriptsproductsie6.iss”
    #include “scriptsproductsdotnetfx35sp1or40.iss”
    #define MyAppVer GetFileVersion(“.CompiledFilesWorklistBuilder.exe”)

    AppVersion={#MyAppVer}

    OutputBaseFilename=ThunderboltEIAGUISetup_{#MyAppVer}

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>