This can be a *very* handy configuration to use for several reasons:
1) By creating an instance of a service for every development team you can effectively administer their builds without impacting builds for other teams. (You may even achieve the holy grail and get them to look after it themselves but don't hold your breath!)
2) If you want to test new configurations or new builds you can do so totally independently of your 'production' builds.
So, you wanna do it - how is it done?
It's actually pretty simple.
__--==**!!Assuming you already have the Cruise Control 1.0 RC2 package installed and all you current builds are living in the 'server' directory.!!**==--__
1) You don't need to close down your current Cruise Control Service instance - so you can set all this up and then automagically make it all appear and bask in the marvelment of your peers.
2) Decide how you want to group your builds - I usually just create a service instance for a team and have all their builds run under that instance unless they have a *really* good reason why a build should have its own process (Release builds may be a candidate here).
3) Create a copy of the 'server' directory in your Cruise installation folder. I usually follow a naming convention 'server_teamName' so all the server build directories appear together in explorer (I am weird like that!).
4) Edit the ccservice.exe.config file. Change the service.name and channel elements as follows: (I usually increment the port by 1 from 21235 for every build process).
<
add key="service.name" value="Cruise Control .Net Server teamName"/>
<
system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="21238" />
</channels>
</application>
</system.runtime.remoting>
OK - you're done with that file.
5) Edit the dashboard.config in 'webdashboard' so you show up on the dash by adding a server key as follows:
<
remoteServices>
<servers>
<!-- Update this list to include all the servers you want to connect to. NB - each server name must be unique -->
<server name="local" url="tcp://localhost:21234/CruiseManager.rem" />
<server name="local_teamName" url="tcp://localhost:21238/CruiseManager.rem" />
</servers>
</remoteServices>
OK - so that's the dash sorted.
6) Install the service instance using a Visual Studio .Net Command Prompt:
installutil /ServiceName="use the name you used in the ccservice.exe.config" ccservice.exe
You should see the instance appear in the Services snap-in.
7) You now configure builds in this build directory the same as you would for the default 'server' directory.
8) If you want to see the new builds in CCTray - remember to add your server again to the server list using the port you specified in the .exe.config and dashboard.config and you're all set!
We all know how easy it is to add configuration settings to our web.config and app.config files but writing code which accesses these values and is easily maintainable is not always so immediately obvious.
My preferred method dealing with appSettings is to create a Configuration class in any assembly which needs to access the .config file.
The configuration class implements a bunch of static getter properties which return the value of the required key from the appSettings section of the .config file.
To make things easier I also tend to code the keyname to the namespace.classname.propertyname that I require.
e.g. Configuration class in MyAssembly - note the 'welcomeMessageKey' format
using
System.Configuration;
namespace
MyAssembly
{
public class Configuration
{
private static string welcomeMessageKey = "MyAssembly.Configuration.WelcomeMessage";
public static string WelcomeMessage
{
get
{
return ConfigurationSettings.AppSettings[welcomeMessageKey];
}
}
}
I can now add a key to my .config file:
<
add key="MyAssembly.Configuration.WelcomeMessage" value="Hello World!" />
There are several advantages to this approach:
1) I can immediately tell just by the keyname exactly which configuration class in which assembly references this key and the property that exposes it.
2) Because all my production code accesses the appSettings via the Configuration class I only have a single place to update if I ever change the keyname.
3) You can also access the settings using the Configuration classes within your unit tests pulling values from a supplied .config for testing purposes.
I have often encountered clients struggling with how to write maintainable unit tests which for one reason or another require large amounts of sample data.
e.g. a manager class which parses in a custom xml configuration file.
It may be desirable to be able to pass in variations of the configuration file to test how the manager class handles it - in particular, if a value is missing etc.
We need a bunch of configuration files which incorporate the conditions for which we are testing. We can create them manually within the solution and deploy them with our unit tests using a post-build step to copy the files to the right place but this does not make for easy maintenance.
My preferred method is to create as many files as I need (each file displaying a particular characteristic I want to test) within the test assembly project and set the files' Compile property to 'EmbeddedResource'. This instructs the compiler to compile the file as an embedded resource directly into the output assembly which can then be retrieved by the unit tests at runtime - voila! No dodgy filepaths or missing files etc - everything you need is in the test assembly.
Sure - it takes a little longer to compile (fractions of seconds - I can live with that) and the output test assembly is bigger (but we'd need the files anyway, right?) but I happily trade this for the simplicity it offers in ensuring you have the files you need to test and no troublesome filesystem housekeeping to worry about :0)
With a little effort it's easy to refactor classes which rely on parsing in a text file to pass off the reading in bit to a separate class (which you can mock) to return the string (which you get from your embedded resource files).
Here's a simple implementation of an EmbeddedResource String helper class (including a unit test of course! so you can see how it all hangs together ;0))
1) Create a blank solution, call it whatever you want.
2) Add a c# class library to the solution and call it Utilities.
3) Add a c# class library to the solution and call it Utilities.Test.
4) Go to the project properties and change the Default Namespace to Utilities (to match the production assembly project default namespace).
5) Add a class to the production project called ResourceStringHelper and add the following code:
using
System;
using
System.IO;
using
System.Reflection;
namespace
Utilities
public
class ResourceStringHelper
{
public static string GetResourceString(string ManifestResourceName)
{
Assembly assembly = Assembly.GetCallingAssembly();
return GetResourceString(assembly, ManifestResourceName);
}
public static string GetResourceString(Assembly ResourceAssembly, string ManifestResourceName)
{
string embeddedString = string.Empty;
try
{
Stream stream = ResourceAssembly.GetManifestResourceStream(ManifestResourceName);
StreamReader streamReader =
new StreamReader(stream);
embeddedString = streamReader.ReadToEnd();
return embeddedString;
}
catch (Exception exception)
{
throw new Exception
("Error retrieving resource: '" + ManifestResourceName + "' from '" + ResourceAssembly.GetName().Name.ToString() + "'", exception);
}
}
}
6) Add a folder called 'Inline' to the test assembly and add a text file called resource.txt to the folder
7) Edit the file to contain: test string
8) Close and save the file.
9) Change the txt file's Compile property to 'EmbeddedResource' - **HIGHLY IMPORTANT**
10) Add a test class to the test assembly called ResourceStringHelperTest and add the following code:
using
System.Reflection;
using
NUnit.Framework;
namespace
Utilities
{
[TestFixture]
public class ResourceStringHelperTest
{
private string expected;
private string manifestResourceNameValid;
private string manifestResourceNameInvalid;
[SetUp]
public void SetUp()
{
this.expected = "test string";
this.manifestResourceNameValid = "Utilities.Inline.resource.txt";
this.manifestResourceNameInvalid = "invalid.resource.name";
}
[TearDown]
public void TearDown()
{
}
[Test]
public void GetResourceStringTest_ManifestResourceName()
{
string actual = ResourceStringHelper.GetResourceString(this.manifestResourceNameValid);
Assert.AreEqual(
this.expected, actual);
}
[Test]
[ExpectedException(
typeof(Exception),"Error retrieving resource: 'invalid.resource.name' from 'Rbsfm.Common.Utilities.Test'")]
public void GetResourceStringTest_ManifestResourceName_Exception()
{
ResourceStringHelper.GetResourceString(
this.manifestResourceNameInvalid);
}
[Test]
public void GetResourceStringTest_AssemblyManifestResourceName()
{
Assembly assembly = Assembly.GetAssembly(
this.GetType());
string actual = ResourceStringHelper.GetResourceString(assembly, this.manifestResourceNameValid);
Assert.AreEqual(
this.expected, actual);
}
[Test]
[ExpectedException(
typeof(Exception),"Error retrieving resource: 'invalid.resource.name' from 'Rbsfm.Common.Utilities.Test'")]
public void GetResourceStringTest_AssemblyManifestResourceName_Exception()
{
Assembly assembly = Assembly.GetAssembly(
this.GetType());
ResourceStringHelper.GetResourceString(assembly,
this.manifestResourceNameInvalid);
}
}
}
11) This should all compile OK and should run just fine in NUnit (this was compiled against v2.2.0).
You'll notice I am testing that an exception of type Exception is raised - you can easily change this to test for a custom exception type (EmbeddedResourceException?) just make sure to create the custom exception and throw it when you catch the Exception exception type....
ManifestResourceName is the main element of this which causes people the most pain but once you get it in your head it's a cinch!
The resource name is the default namespace of the assembly (Utilities) in our example to which you concatenate the folder path and filename using dot syntax.
e.g. \NamespaceA\NamespaceB\NamespaceC\resourceXml.xml in a project with the default namespace Example would be:....
Example.NamespaceA.NamespaceB.NamespaceC.resourceXml.xml - Easy!
99/100 if the test fails either the resource name is wrong or the compile property is not set to EmbeddedResource.
Enjoy - I hope this is of some help - I know it's saved me hassle a-plenty in the past!
John Robbins has posted a useful article with a couple of gems over at MSDN.
In particular, he has a code snippet to show how you can harness your tests to run under MS Unit Test or NUnit Test frameworks.
Check it out here.
As I reported in my previous post TypeMock has problems when dynamically mocking interfaces with rectangular array return types e.g. string[,].
Unfortunately, it seems the issue lies with the Reflection.Emit code and they are still working on finding a solution but they have provided a temporary workaround.
You can find the entire TypeMock support thread here.