jQuery ate my values

by Mikael Henriksson 22. July 2010 20:33
I wanted to clear some textboxes when it gets focus so that the user does not have to clear the textbox before they enter whatever text. I came up with the following pretty quickly.
$('input[type=text]').focus(function () {    
    $(this).val('');
});

This worked like a charm!

I clicked a textbox and it got cleared, perfect right? Wrong! The problem here is that no matter how the textbox gets focus the box will be cleared. While tabbing through the form I cleared it completely and had to reload the entire page to get the values back. I thought of implementing some sort of ctrl+z to undo changes but realized I am better of not going down that road.

If it isn't broken…

UPDATE (2010-07-26): As suggested in the comments it’s actually a great idea to store the value locally and if the textbox is empty when the user leaves it we just add the original value back. This can be done like follows:

$(document).ready(function() {
        $('input[type=text]').bind('focusin', function(e) {
            tbData = $(this).val();
            $(this).val('')   
        });
        $('input[type=text]').bind('focusout', function(e) {
            if ($(this).val() == '') {
                $(this).val(tbData);
            }
        });
});

 

I’d like to shorten this a little but can’t get it working as expected. I’d probably want to get rid of the global variable for instance. Expect another update sometime soon.

Tags:

jQuery

Resharper NOT broken, MSpec R# runner IS

by Mikael Henriksson 15. July 2010 21:23

I have no idea what is going on over at jetbrains but youtrack is down and resharper is broken. I got the latest version of Resharper 5.1.1727.12 and I can’t use it. The renaming functionality is broken. I keep pressing F2 and nothing happens. Feels like going back to the stoneage. Obviously it’s one of the features I use the most and without the coding experience is terrible.

EDIT: I just discovered that intellisense also is broken both on my work VS2008 and at home VS2010.

Obviously they have to create a version 5.2 pretty rapidly but I can’t report the bug because youtrack is down! Sad smile

On a side-note I am happy with the latest version of windows live!

Thank you Hadi HaririI would probably be looking for a long time if you had not been there! I posted this to the mspec guys instead. http://github.com/machine/machine.specifications/issues/issue/18

Tags:

Using Powershell to perform ftp uploads

by Mikael Henriksson 11. July 2010 23:58

This is not so much for informational purposes as it is for safe keeping

. 'x:\path-to\ftp-tools.ps1'
$server = 'ftp.domain.com'
$dir = 'C:\Users\Public\Pictures\Sample Pictures'
$pass = ConvertTo-SecureString "******" -AsPlainText -Force
$cred = new-object `
 	-typename System.Management.Automation.PSCredential -argumentlist "*******", $pass

upload-directory `
 -server $server `
 -dir $dir `
 -overwrite $true `
 -cred $cred
function upload-directory {
  param([string] $server = $( Throw "You must specify an FTP server to logon to."),
	 [string] $dir = $( Throw "You must specify a local directory to upload (ie, C:\Testing\FTPTest\)"),
	 [switch] $overwrite = $false,
	 [System.Management.Automation.PSCredential] $cred = $( Throw "You must provide credentials with which to logon to the FTP server.") ) 
        
  $files = (get-childitem $dir -r)

  foreach ($file in $files) {
    $remfilename = $file.FullName.Replace($dir, "")
    $remfilename = $remfilename.Replace("\", "/")
    if ($file.Attributes.ToString().IndexOf("Directory") -ge 0) {
  	  try
  	  {
      	send-ftp -server $server -cred $cred
      }
      catch {} #if the directory already exists, ignore the error
    }
    else {
      send-ftp `
	  	-Server $server `
		-LocalFile $file.FullName `
		-Path "/account/zoolutions.se/Test" `
		-RemoteFile $remfilename `
		-Credentials $cred `
		-Overwrite $true
    }
  }
}
function send-ftp {

	param([string] $server = $( Throw "You must specify an FTP server to logon to."),
		[string] $dir = $( Throw "You must specify a local directory to upload (ie, C:\Testing\FTPTest\)"),
		[switch] $overwrite = $(Throw "Must select wheter to overwrite existing files"),
		[System.Management.Automation.PSCredential] $credentials = $(Get-Credential),
		[Parameter(ValueFromPipeline=$true)] $LocalFile = $( Throw "You must specify an FTP server to logon to."),  
		[string] $path = $( Throw "You must specify an FTP server to logon to."),  
		[string] $remoteFile = $(Split-Path $LocalFile -Leaf), 
		[int] $parentProgressId = -1,
		[string] $progressActivity = "Uploading $LocalFile" )
	  
	## Assert the existence of the file in question
	if( -not (Test-Path $LocalFile) ) {
		Throw "File '$LocalFile' does not exist!"
	}

	## Create the server string (and make sure it uses forward slashes and ftp://)
	$upload = "ftp://" + $Server + ( Join-Path (Join-Path "//" $Path) $RemoteFile ) -replace "\\", "/"
	#$Upload = $upload
	$total = (gci $LocalFile).Length

	Write-Debug $upload
	## Create FTP request
	$request = [Net.FtpWebRequest]::Create($upload)

	## NOTE: we do not create a folder here...
	# [System.Net.WebRequestMethods+Ftp]::MakeDirectory
	$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
	$request.Credentials = $Credentials.GetNetworkCredential()
	$request.UsePassive = $true
	$request.UseBinary = $true
	$request.KeepAlive = $false

	try {
		## Load the file
		$read = [IO.File]::OpenRead( (Convert-Path $LocalFile) )
		$write = $request.GetRequestStream();
		
		$buffer = new-object byte[] 20KB
		$offset = 0
		$progress = 0

		do {
		$offset = $read.Read($buffer, 0, $buffer.Length)
		$progress += $offset
		$write.Write($buffer, 0, $offset);
		Write-Progress $ProgressActivity "Uploading" -Percent ([int]($progress/$total * 100)) -Parent $ParentProgressId
		} while($offset -gt 0)

	} finally {
		Write-Debug "Closing Handles"
		$read.Close()
		$write.Close()
	}
}

Tags:

PowerShell

All my current Fluent NHibernate Conventions

by Mikael Henriksson 5. July 2010 23:17

I have been working on these for a while now. It all started  when I had to adapt to DBA style naming conventions. I wanted to automate everything because 16 tables with almost the same number of columns becomes a lot of text to write . Yeah I have to override a column name every now and then but all in all it has served me pretty well. Later I added some conventions for invert many-to-many and some other conventions as well.

I’ll just add them here so let the games begin:

public class TableNameConvention : IClassConvention, IClassConventionAcceptance
{
	public void Apply(IClassInstance instance)
	{
		instance.Table("`" + Inflector.Underscore(instance.EntityType.Name) + "´");
	}

	public void Accept(IAcceptanceCriteria<IClassInspector> criteria)
	{
		criteria.Expect(x => x.TableName, Is.Not.Set);
	}
}

I recently posted the Inflector class and this and much more is what I have been using it for. The method “Inflector.Underscore” makes SomeEntity into some_entity. It should be easy enough to understand :) Oh and the reason for the acceptance criteria is for those few times I got complaints about the table names and needed to set it in the mapping. It sets the table name if it has not been set from the mapping.

public class ManyToManyTableName : ManyToManyTableNameConvention
{
	protected override string GetBiDirectionalTableName(IManyToManyCollectionInspector collection,
	                                                    IManyToManyCollectionInspector otherSide)
	{
		return Inflector.Underscore(collection.EntityType.Name + "_" + otherSide.EntityType.Name);
	}

	protected override string GetUniDirectionalTableName(IManyToManyCollectionInspector collection)
	{
		return Inflector.Underscore(collection.EntityType.Name + "_" + collection.ChildType.Name);
	}
}

This one was a bit difficult for me to get right, had to do a few trials before I got it correct.

public class EnumConvention : IPropertyConvention, IPropertyConventionAcceptance
{
	public void Apply(IPropertyInstance instance)
	{
		instance.CustomType(instance.Property.PropertyType);
	}

	public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
	{
		criteria.Expect(x => x.Property.PropertyType.IsEnum);
	}
}

My trusty old EnumConvention, it has made developing against a RDBMS a lot nicer. Enums are a powerful addition to the model.

public class IdColumnConvention : IIdConvention
{
	public void Apply(IIdentityInstance instance)
	{
		instance.Column(Inflector.Underscore(instance.EntityType.Name) + "_id");
		instance.GeneratedBy.Identity();
	}
}

The IdColumnConvention is the only one that changes between projects and it’s the generated by that is subject to change. You might want to throw in a IIdConventionAcceptance if you have more than one type of Id column.

public class PropertyConvention : IPropertyConvention
{
	public void Apply(IPropertyInstance instance)
	{
		instance.Column(Inflector.Underscore(instance.Property.Name));
	}
}

Nothing too exciting here either, just the naming of the columns.

public class CustomForeignKeyConvention : ForeignKeyConvention
{
	protected override string GetKeyName(Member property, Type type)
	{
		if (property == null)
			return Inflector.Underscore(type.Name) + "_id"; 

		return Inflector.Underscore(property.DeclaringType.Name) + "_id";
	}
}

This one on the other hand is a different story. Here is where conventions become convenient (and difficult). It’s really nice to be able to set the foreign key but figuring out the correct variable to put in took some time. The last return is for  many-to-one and the top one is for all the others.

public class HasManyConvention : IHasManyConvention
{
	public void Apply(IOneToManyCollectionInstance instance)
	{
		instance.Cascade.All();
		instance.Inverse();
		instance.Key.Column(Inflector.Underscore(instance.EntityType.Name + "_id"));
		instance.Key.ForeignKey(string.Format("fk_{0}_{1}",
		                                      Inflector.Underscore(instance.EntityType.Name).ToLower(),
		                                      Inflector.Underscore(instance.ChildType.Name).ToLower()));
	}
}

I set cascade and inverse here as well as the names of the foreign key and the column name. I probably don’t need the column name though. If you want to customize the inverse and cascading you would be better of moving that over to a second convention so you can pass in a convention acceptance criteria to decide whether to set it or not.

public class ReferenceConvention : IReferenceConvention
{
	public void Apply(IManyToOneInstance instance)
	{
		instance.Cascade.None();

		instance.Column(Inflector.Underscore(instance.Property.PropertyType.Name) + "_id");

		instance.ForeignKey(
			string.Format("fk_{0}_{1}",
				Inflector.Underscore(instance.Property.PropertyType.Name).ToLower(),
				Inflector.Underscore(instance.EntityType.Name).ToLower()));
	}
}

The last one for today is pretty similar to the HasMany convention. When working with ASP.NET MVC it’s really important to set the cascading to none in the following scenario:

Let’s say you have and order that holds products. You retrieve the order from the database. Then you add a product to the order and want to use the cascading inverse functionality in the Order mapping. However if your product holds a reference to the Order in your View (you use your data model in your presentation layer) you could end up with the following message “a different object with the same identifier value was already associated with the session”. What this means is that you have cascading on the wrong end. Cascading from the wrong side of things is as bad as leaving your aggregate members as publicly accessible as the aggregate root.

Tags: ,

Fluent NHibernate | NHibernate

Inflector – renaming utilities

by Mikael Henriksson 3. July 2010 11:04

One of my favorite utilities when working with 1. C#, 2. ORM, 3. DBA has been and will always be Inflector.  It takes any string input and changes ist’s form. For instance it can make your "some_table" in the database become "SomeTable" in C# by calling the right method. This is not my work, I take no credit for it. It originally belongs to some Andrew Peters guy. He seems to have vanished from the surface. It can still be found in a various places but this is where I store “my” stuff so:)

	public static class Inflector
	{
		#region Default Rules

		static Inflector() {
			AddPlural("$", "s");
			AddPlural("s$", "s");
			AddPlural("(ax|test)is$", "$1es");
			AddPlural("(octop|vir)us$", "$1i");
			AddPlural("(alias|status)$", "$1es");
			AddPlural("(bu)s$", "$1ses");
			AddPlural("(buffal|tomat)o$", "$1oes");
			AddPlural("([ti])um$", "$1a");
			AddPlural("sis$", "ses");
			AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves");
			AddPlural("(hive)$", "$1s");
			AddPlural("([^aeiouy]|qu)y$", "$1ies");
			AddPlural("(x|ch|ss|sh)$", "$1es");
			AddPlural("(matr|vert|ind)ix|ex$", "$1ices");
			AddPlural("([m|l])ouse$", "$1ice");
			AddPlural("^(ox)$", "$1en");
			AddPlural("(quiz)$", "$1zes");

			AddSingular("s$", "");
			AddSingular("(n)ews$", "$1ews");
			AddSingular("([ti])a$", "$1um");
			AddSingular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
			AddSingular("(^analy)ses$", "$1sis");
			AddSingular("([^f])ves$", "$1fe");
			AddSingular("(hive)s$", "$1");
			AddSingular("(tive)s$", "$1");
			AddSingular("([lr])ves$", "$1f");
			AddSingular("([^aeiouy]|qu)ies$", "$1y");
			AddSingular("(s)eries$", "$1eries");
			AddSingular("(m)ovies$", "$1ovie");
			AddSingular("(x|ch|ss|sh)es$", "$1");
			AddSingular("([m|l])ice$", "$1ouse");
			AddSingular("(bus)es$", "$1");
			AddSingular("(o)es$", "$1");
			AddSingular("(shoe)s$", "$1");
			AddSingular("(cris|ax|test)es$", "$1is");
			AddSingular("(octop|vir)i$", "$1us");
			AddSingular("(alias|status)es$", "$1");
			AddSingular("^(ox)en", "$1");
			AddSingular("(vert|ind)ices$", "$1ex");
			AddSingular("(matr)ices$", "$1ix");
			AddSingular("(quiz)zes$", "$1");

			AddIrregular("person", "people");
			AddIrregular("man", "men");
			AddIrregular("child", "children");
			AddIrregular("sex", "sexes");
			AddIrregular("move", "moves");

			AddUncountable("equipment");
			AddUncountable("information");
			AddUncountable("rice");
			AddUncountable("money");
			AddUncountable("species");
			AddUncountable("series");
			AddUncountable("fish");
			AddUncountable("sheep");
		}

		#endregion

		private static readonly List<Rule> _plurals = new List<Rule>();
		private static readonly List<Rule> _singulars = new List<Rule>();
		private static readonly List<string> _uncountables = new List<string>();

		internal static void AddIrregular(string singular, string plural) {
			AddPlural("(" + singular[0] + ")" + singular.Substring(1) + "$", "$1" + plural.Substring(1));
			AddSingular("(" + plural[0] + ")" + plural.Substring(1) + "$", "$1" + singular.Substring(1));
		}

		internal static void AddUncountable(string word) {
			_uncountables.Add(word.ToLower());
		}

		internal static void AddPlural(string rule, string replacement) {
			_plurals.Add(new Rule(rule, replacement));
		}

		internal static void AddSingular(string rule, string replacement) {
			_singulars.Add(new Rule(rule, replacement));
		}

		public static string Pluralize(string word) {
			return ApplyRules(_plurals, word);
		}

		public static string Singularize(string word) {
			return ApplyRules(_singulars, word);
		}

		private static string ApplyRules(List<Rule> rules, string word) {
			string result = word;

			if (!_uncountables.Contains(word.ToLower())) {
				for (int i = rules.Count - 1; i >= 0; i--) {
					if ((result = rules[i].Apply(word)) != null) {
						break;
					}
				}
			}

			return result;
		}

		public static string Titleize(string word) {
			return Regex.Replace(Humanize(Underscore(word)), @"\b([a-z])",
			                     match => match.Captures[0].Value.ToUpper());
		}

		public static string Humanize(string lowercaseAndUnderscoredWord) {
			return Capitalize(Regex.Replace(lowercaseAndUnderscoredWord, @"_", " "));
		}

		public static string Pascalize(string lowercaseAndUnderscoredWord) {
			return Regex.Replace(lowercaseAndUnderscoredWord, "(?:^|_)(.)",
			                     match => match.Groups[1].Value.ToUpper());
		}

		public static string Camelize(string lowercaseAndUnderscoredWord) {
			return Uncapitalize(Pascalize(lowercaseAndUnderscoredWord));
		}

		public static string Underscore(string pascalCasedWord) {
			return Regex.Replace(
				Regex.Replace(
					Regex.Replace(pascalCasedWord, @"([A-Z]+)([A-Z][a-z])", "$1_$2"), @"([a-z\d])([A-Z])",
					"$1_$2"), @"[-\s]", "_").ToLower();
		}

		public static string Capitalize(string word) {
			return word.Substring(0, 1).ToUpper() + word.Substring(1).ToLower();
		}

		public static string Uncapitalize(string word) {
			return word.Substring(0, 1).ToLower() + word.Substring(1);
		}

		public static string Ordinalize(string number) {
			int n = int.Parse(number);
			int nMod100 = n%100;

			if (nMod100 >= 11 && nMod100 <= 13) {
				return number + "th";
			}

			switch (n%10) {
				case 1:
					return number + "st";
				case 2:
					return number + "nd";
				case 3:
					return number + "rd";
				default:
					return number + "th";
			}
		}

		public static string Dasherize(string underscoredWord) {
			return underscoredWord.Replace('_', '-');
		}

		#region Nested type: Rule

		private class Rule
		{
			private readonly Regex _regex;
			private readonly string _replacement;

			public Rule(string pattern, string replacement) {
				_regex = new Regex(pattern, RegexOptions.IgnoreCase);
				_replacement = replacement;
			}

			public string Apply(string word) {
				if (!_regex.IsMatch(word)) {
					return null;
				}

				return _regex.Replace(word, _replacement);
			}
		}

		#endregion
	}

Tags:

C# | Tools

How to make TeamCity stop checking for changes and start building!

by Mikael Henriksson 23. June 2010 21:52

I had a delicate issue on the new server. Builds would work for any given period of time and then I got a message about java jada jada jada time out. As it turns out there is a bug with the jgit library that TeamCity uses and unfortunately Jetbrains does not have rights or something to modify this by themselves since it’s a third party library. The strange thing is that it was never a problem on my machine but on the server it turned out to cause problems.

A quick search actually brought me straight to the solution. The information is contained within a YouTRACK issue http://youtrack.jetbrains.net/issue/TW-9574 but I’ll summarize it here in brevity.

  1. Inside the .BuildServer/config directory create a new file called internal.properties
  2. Copy the contents from my internal.properties at the bottom
  3. Download jetbrains.git.zip
  4. Stop TeamCity
  5. Delete the .BuildServer\plugins\jetbrains.git folder
  6. Delete the .BuildServer\plugins\jetbrains.git.zip file
  7. Copy the new jetbrains.git.zip to .BuildServer\plugins\
  8. Start TeamCity

 

teamcity.git.fetch.separate.process=true
teamcity.git.fetch.timeout=0
teamcity.git.clone.timeout=0

 

That’s it, TeamCity should start checking for changes again. If not I recommend contacting Jetbrains about it.

-Have fun

Tags:

Team City

How to move TeamCity to another computer

by Mikael Henriksson 23. June 2010 20:12

Last post I said I would come back with information about how to move TeamCity from one computer to another. This part of TeamCity I really like. Let’s establish some ground rules first. For this to work at all both computers/servers need to run the same version of TeamCity. Second if you like me had everything running fine locally backed by a SQL Server the server also needs this for things to move smoothly.

So that’s the gibberish now let’s move on to the solution.

  1. Take a full backup of TeamCity on the original machine
  2. Move the backup zip file to the TeamCity copy
  3. From the .BuildServer/Config (aka <TeamCity Data Directory>) grab the database.properties file and copy this file to where ever you want on the new machine and modify it if necessary to point to the new database. Don’t forget the problems with the port and the instance name
  4. Fire up a remote session to the new machine and start a command prompt
  5. Stop the TeamCity services
  6. Run the maintainDB command at the bottom of this post
  7. Copy the database.properties file to the .BuildServer/config dir
  8. Start TeamCity services. It should now fire up with all the build configs etc from the old machine.

cd "C:\Program Files (x86)\TeamCity\bin\"

maintainDB.cmd restore -F "C:\TeamCity_Backup_20100619_150814.zip" -T "C:\database.properties"

-F – specifies the path to the backup archive
-T – specifies specifies the path to the database.properties.

The only thing left now is to run all the builds and watch the lights go green and smile :)

Tags:

Team City

Installing TeamCity with MS SQL 2008

by Mikael Henriksson 19. June 2010 16:42

The first time I installed TeamCity it took me about a day to get everything in place (excluding setting up the build configurations). Second time I installed TeamCity on a real server it took me about 30 minutes including setting up all the build configurations. I didn’t really do any second time, I transferred the database.properties file and a backup to the server.

There are a few tricks to doing this right and I’ll walk you through them but first let’s establish a few things. The .BuildServer directory is where the magic happens, the TeamCity directory is where the web server and the BuildAgent installers and plugins are located. A BuildAgent can be installed on any remote machine, just make sure you have enough licenses to connect with the BuildServer.

.BuildServer directory is by default "C:\Users\UserName\.BuildServer" and will from now on be referred to as just .BuildServer

  1. Install TeamCity (no brainer)
  2. Install configure a database you want to use. The user you are using for teamcity must have dbo access to the database.
  3. Download the database driver from http://sourceforge.net/projects/jtds/files/
  4. Unzip "jtds-1.2.5-dist.zi"
  5. Copy "jtds-1.2.5.jar" to ".BuildServer\lib\jdbc\jtds-1.2.5.jar"
  6. Navigate to ".BuildServer\config" and create a new file called database.properties

Here is where I got lost for a while. The port for SQL Server needs to be specified and this is not done by default. Further it was really hard to find how to specify the instance name. This is how it should look when it’s correct (using an instance):

connectionUrl=jdbc:jtds:sqlserver://COMPUTERNAME:1433/DATABASENAME/instance=INSTANCENAME;
connectionProperties.user=teamcity
connectionProperties.password=teamcity

maxConnections=50
testOnBorrow=true

This led me up to the next problem saying java is not recognized function or program. After searching the internet for about an hour I gathered that I needed to add environment variables for java so I added JRE_HOME and JAVA_EXE but this didn’t do (pardon the language). There is a quick fix though. Inside "C:\TeamCity\bin" there is a file called "findJava.bat" inside here I just modified the second last statements. This is what my findJava looks like:

:: ---------------------------------------------------------------------
:: Searches for Java executable
:: ---------------------------------------------------------------------

SET JAVA_EXE=%JRE_HOME%\bin\java.exe
IF EXIST "%JAVA_EXE%" goto java_found

SET JAVA_EXE=%JAVA_HOME%\jre\bin\java.exe
IF EXIST "%JAVA_EXE%" goto java_found

SET JAVA_EXE=%JAVA_HOME%\bin\java.exe
IF EXIST "%JAVA_EXE%" goto java_found

SET JAVA_EXE=%TEAMCITY_HOME_DIR%\jre\bin\java.exe
IF EXIST "%JAVA_EXE%" goto java_found

:: ---------------------------------------------------------------------
:: No JAVA_HOME or JRE_HOME set and no bundled JRE found, trying to run java from the PATH
:: ---------------------------------------------------------------------
SET JAVA_EXE="C:\TeamCity\jre\bin\java.exe"

:java_found

Now your TeamCity build server should start working if you have configured your SQL Server instance correctly. I was missing adding to the instance that it should be listening to TCP Port 1433.

sql_server_config

And of course your windows firewall might need opening ports if the two are not located on the same machine.

Next I’ll tell you how to move this configuration to another machine should you ever have the need.

Tags: ,

Team City | Windows Server

Conquering NServiceBus part 6 – Upgrading StructureMap to 2.6.x

by Mikael Henriksson 11. June 2010 00:31

I mentioned somewhere that I had to change the StructureMap ObjectBuilder a tiny bit to be able to upgrade and make use of the new API goodness that Jeremy and Joshua had created.

It’s all really simple, I just lifted out the whole project “x:\NServiceBus\src\impl\ObjectBuilder\ObjectBuilder.StructureMap” and made that project a part of my own solution file. I probably don’t have to do that but I did not want to risk accidently overwriting or deleting my changes.

There are only two steps necessary:

  1. Upgrade StructureMap.dll to the newest version (currently 2.6.1)
  2. Inside StructureMapObjectBuilder.cs replace:

configuredInstance = x.ForRequestedType(component)
	.CacheBy(scope)
	.TheDefaultIsConcreteType(component);

with the following

configuredInstance = x.For(component)
		.LifecycleIs(scope)
		.Use(component);

It’s not even  necessary to do this change, a simple replace of the StructureMap.dll in the external-bin folder of the NServiceBus source code should do the trick. It’s been ages ago since I submitted the patch for this but it did not make the 2.0. Starting from the trunk onwards the upgrade should not be necessary.

Now it’s bed time since hours ago :)

Tags:

NServiceBus

Moving from NUnit tests to MSpec

by Mikael Henriksson 5. June 2010 23:26

Today I did something brain numbing moving away from NUnit to Machine Specifications. The reason being, it’s a lot nicer from a business perspective. In this case the business is open source and free of charge but it makes more sense. The way mspec forces me to think about the test context and the reason I am at all writing the tests is very useful in a lot of scenarios. For me I am testing various parts of my PBN serializers/readers/writers and everything is context based so MSpec fits like a glove.

This is how the tests looked originally:

namespace Zoolutions.PbnSerialization.Tests
{
	[TestFixture]
	public class Verify_that_PbnReader_
	{
		private PbnReaderImpl _reader;

		[Test]
		public void adds_a_new_board_when_the_Event_line_is_read()
		{
			const string newBoardLine = @"[Event ""VBK 080114""]";
			_reader = new PbnReaderImpl();
			_reader.ReadLine(newBoardLine);
			_reader.Tournament.Boards.Satisfy(x => x.Count == 1);
		}

		[Test]
		public void can_read_board_info_Annotator()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Annotator ""Växjö BK""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Annotator")
				.Value.Satisfy(x => x == "Växjö BK");
		}

		[Test]
		public void can_read_board_info_Application()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Application ""Ruter - Read more at www.brenning.se""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Application")
				.Value.Satisfy(x => x == "Ruter - Read more at www.brenning.se");
		}

		[Test]
		public void can_read_board_info_BoardNr()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Board ""1""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Board")
				.Value.Satisfy(x => x == "1");
		}

		[Test]
		public void can_read_board_info_Competition()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Competition ""Pairs""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Competition")
				.Value.Satisfy(x => x == "Pairs");
		}

		[Test]
		public void can_read_board_info_Contract()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Contract """"]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Contract")
				.Value.Satisfy(x => x == string.Empty);
		}

		[Test]
		public void can_read_board_info_Date()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Date ""2008.01.14""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Date")
				.Value.Satisfy(x => x == "2008.01.14");
		}

		[Test]
		public void can_read_board_info_Deal()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Deal ""N:T2.QT53.KJ32.AK9 Q53.974.A94.J843 AK9876.AK.76.Q62 J4.J862.QT85.T75""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Deal")
				.Value.Satisfy(x => x == "N:T2.QT53.KJ32.AK9 Q53.974.A94.J843 AK9876.AK.76.Q62 J4.J862.QT85.T75");
		}

		[Test]
		public void can_read_board_info_Dealer()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Dealer ""N""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Dealer")
				.Value.Satisfy(x => x == "N");
		}

		[Test]
		public void can_read_board_info_Declarer()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Declarer """"]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Declarer")
				.Value.Satisfy(x => x == string.Empty);
		}

		[Test]
		public void can_read_board_info_EventDate()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[EventDate ""2008.01.14""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "EventDate")
				.Value.Satisfy(x => x == "2008.01.14");
		}

		[Test]
		public void can_read_board_info_North()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[North """"]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "North")
				.Value.Satisfy(x => x == string.Empty);
		}

		[Test]
		public void can_read_board_info_Result()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Result """"]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Result")
				.Value.Satisfy(x => x == string.Empty);
		}

		[Test]
		public void can_read_board_info_Scoring()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Scoring ""MatchPoints;MP1""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Scoring")
				.Value.Satisfy(x => x == "MatchPoints;MP1");
		}

		[Test]
		public void can_read_board_info_Site()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Site ""Växjö BK, Växjö, Sydöstra, Sverige""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Site")
				.Value.Satisfy(x => x == "Växjö BK, Växjö, Sydöstra, Sverige");
		}

		[Test]
		public void can_read_board_info_South()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[South """"]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "South")
				.Value.Satisfy(x => x == string.Empty);
		}

		[Test]
		public void can_read_board_info_Vulnerable()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[Vulnerable ""None""]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "Vulnerable")
				.Value.Satisfy(x => x == "None");
		}

		[Test]
		public void can_read_board_info_West()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			const string line = @"[West """"]";
			_reader.ReadLine(line);
			_reader.Tournament.Boards.First()
				.BoardProperties.Single(x => x.Name == "West")
				.Value.Satisfy(x => x == string.Empty);
		}

		[Test]
		public void can_read_score_meta()
		{
			adds_a_new_board_when_the_Event_line_is_read();
			_reader.ScoreIsTotal = false;
			const string line =
				@"[ScoreTable ""Table\2R;Round\2R;PairId_NS\2R;PairId_EW\2R;Contract\4L;Declarer\1R;Result\2R;Lead\3L;Score_NS\5R;Score_EW\5R;MP_NS\4R;MP_EW\4R;Percentage_NS\3R;Percentage_EW\3R""]";
			_reader.ReadLine(line);
		}

		[Test]
		public void can_read_score_z()
		{
			can_read_score_meta();
			var line = @" 2 13  9 15 3N   N 12 C3  ""490""     - 15.0  1.0  94   6";
			_reader.ReadLine(line);
			foreach (var pbnBoard in _reader.Tournament.Boards)
			{
				foreach (var pbnScore in pbnBoard.Scores)
				{
					pbnScore.RowProperties.Satisfy(prop =>
					                               prop.Single(x => x.Name == "Table").Value == "2" &&
					                               prop.Single(x => x.Name == "Round").Value == "13" &&
					                               prop.Single(x => x.Name == "PairId_NS").Value == "9" &&
					                               prop.Single(x => x.Name == "PairId_EW").Value == "15" &&
					                               prop.Single(x => x.Name == "Contract").Value == "3N" &&
					                               prop.Single(x => x.Name == "Declarer").Value == "N" &&
					                               prop.Single(x => x.Name == "Result").Value == "12" &&
					                               prop.Single(x => x.Name == "Lead").Value == "C3" &&
					                               prop.Single(x => x.Name == "Score_NS").Value == @"490" &&
					                               prop.Single(x => x.Name == "Score_EW").Value == "-" &&
					                               prop.Single(x => x.Name == "MP_NS").Value == "15.0" &&
					                               prop.Single(x => x.Name == "MP_EW").Value == "1.0" &&
					                               prop.Single(x => x.Name == "Percentage_NS").Value == "94" &&
					                               prop.Single(x => x.Name == "Percentage_EW").Value == "6");
				}
			}
		}

		[Test]
		public void can_read_total_score_meta()
		{
			_reader = new PbnReaderImpl();
			const string totalScoreMeta =
				@"[TotalScoreTable ""Rank\2R;RankTie\2R;PairId\2R;Table\2R;Direction\5R;TotalScoreMP\5R;TotalPercentage\5R;Names\40L;NrBoards\2R;Club\25L;MemberID1\7R;MemberID2\7R;Sex1\3R;Sex2\3R""]";
			_reader.ReadLine(totalScoreMeta);
			var props = _reader.Tournament.TotalScore.MetaProperties;
			props.Satisfy(x => x.Count == 14);
		}

		[Test]
		public void can_read_total_score_meta_with_sections()
		{
			_reader = new PbnReaderImpl();
			const string totalScoreMeta =
				@"[TotalScoreTable ""Rank\2R;RankTie\2R;PairId\2R;Section\3R;Table\1R;Direction\5R;TotalScoreMP\5R;TotalPercentage\5R;Names\42L;NrBoards\2R;Club\30L;MemberID1\7R;MemberID2\7R;Sex1\3R;Sex2\3R""]";
			_reader.ReadLine(totalScoreMeta);
			var props = _reader.Tournament.TotalScore.MetaProperties;
			props.Satisfy(x => x.Count == 15);
		}

		[Test]
		public void can_read_total_score_z()
		{
			can_read_total_score_meta();
			const string line = @" 1  - 17  4 ""N-S"" 255.0 61.30 ""Ulla Axelsson - Tord Ericsson""          26 ""Växjö BK""                  ""901""   ""915"" ""W"" ""M""";
			_reader.ReadLine(line);
			var props = _reader.Tournament.TotalScore.Scores;
			props.ForEach(score => score.RowProperties.Satisfy(prop => prop.Count == 14));
		}

		[Test]
		public void can_read_tournament_meta()
		{
			_reader = new PbnReaderImpl();
			const string metaLine = @"% <META  name=Generator                  content=""Ruter"">";
			_reader.ReadLine(metaLine);
			Assert.That(_reader.Tournament.MetaProperties.Count.Equals(1));
			var prop = _reader.Tournament.MetaProperties.Single();

			prop.Satisfy(x =>
			             x.Name == "Generator" &&
			             x.Value == "Ruter");
		}
	}

After a lot of playing around with MSpec I ended up with the following which allows my test subjects to grow more independently of each other.

[Subject("PbnReaderImpl")]
public class read_score_table : PbnReaderContext
{
	Because of = () =>
	{
		PbnReader.ReadLine(EventLine);
		PbnReader.ScoreIsTotal = true;
		PbnReader.ReadLine(ScoreTableLine);
	};

	It should_not_be_score_is_total = () =>
	{
		PbnReader.ScoreIsTotal.ShouldBeFalse();
		PbnReader.Tournament.Boards.Last().MetaProperties.Count.ShouldEqual(14);
	};
}

[Subject("PbnReaderImpl")]
public class read_total_score_table_with_sections : PbnReaderContext
{
	Because of = () =>
	{
		PbnReader.ReadLine(EventLine);
		PbnReader.ScoreIsTotal = true;
		PbnReader.ReadLine(TotalScoreMetaWithSectionLine);
		PbnReader.ReadLine(TotalScoreLine);
	};

	It should_be_score_is_total = () =>
	{
		PbnReader.ScoreIsTotal.ShouldBeTrue();
	};

	It should_have_15_meta_properties = () =>
	{
		ISet<MetaProperty> props = PbnReader.Tournament.TotalScore.MetaProperties;
		props.Count.ShouldEqual(15);
	};
}

[Subject("PbnReaderImpl")]
public class read_total_score_table : PbnReaderContext
{
	Because of = () =>
	{
		PbnReader.ReadLine(EventLine);
		PbnReader.ScoreIsTotal = false;
		PbnReader.ReadLine(TotalScoreMetaWithSectionLine);
	};

	It should_be_score_is_total = () =>
	{
		PbnReader.ScoreIsTotal.ShouldBeTrue();
	};

	It should_have_14_meta_properties = () =>
	{
		ISet<MetaProperty> props = PbnReader.Tournament.TotalScore.MetaProperties;
		props.Count.ShouldEqual(15);
	};

	It each_board_should_have_14meta_properties_with_values = () =>
	{
		ISet<PbnScore> props = PbnReader.Tournament.TotalScore.Scores;
		props.ForEach(score => score.RowProperties.Count.ShouldEqual(14));
	};
}


[Subject("PbnReaderImpl")]
public class read_score_table : PbnReaderContext
{
	Because of = () =>
	{
		PbnReader.ReadLine(EventLine);
		PbnReader.ScoreIsTotal = true;
		PbnReader.ReadLine(ScoreTableLine);
	};

	It should_not_be_score_is_total = () =>
	{
		PbnReader.ScoreIsTotal.ShouldBeFalse();
		PbnReader.Tournament.Boards.Last().MetaProperties.Count.ShouldEqual(14);
	};
}

[Subject("PbnReaderImpl")]
public class read_total_score_table_with_sections : PbnReaderContext
{
	Because of = () =>
	{
		PbnReader.ReadLine(EventLine);
		PbnReader.ScoreIsTotal = true;
		PbnReader.ReadLine(TotalScoreMetaWithSectionLine);
		PbnReader.ReadLine(TotalScoreLine);
	};

	It should_be_score_is_total = () =>
	{
		PbnReader.ScoreIsTotal.ShouldBeTrue();
	};

	It should_have_15_meta_properties = () =>
	{
		ISet<MetaProperty> props = PbnReader.Tournament.TotalScore.MetaProperties;
		props.Count.ShouldEqual(15);
	};
}

[Subject("PbnReaderImpl")]
public class read_total_score_table : PbnReaderContext
{
	Because of = () =>
	{
		PbnReader.ReadLine(EventLine);
		PbnReader.ScoreIsTotal = false;
		PbnReader.ReadLine(TotalScoreMetaWithSectionLine);
	};

	It should_be_score_is_total = () =>
	{
		PbnReader.ScoreIsTotal.ShouldBeTrue();
	};

	It should_have_14_meta_properties = () =>
	{
		ISet<MetaProperty> props = PbnReader.Tournament.TotalScore.MetaProperties;
		props.Count.ShouldEqual(15);
	};

	It each_board_should_have_14meta_properties_with_values = () =>
	{
		ISet<PbnScore> props = PbnReader.Tournament.TotalScore.Scores;
		props.ForEach(score => score.RowProperties.Count.ShouldEqual(14));
	};
}


[Subject("PbnReaderImpl")]
public class read_meta_lines : PbnReaderContext
{
	Because of = () =>
	{
		PbnReader.ReadLine(MetaLine);
		PbnReader.ReadLine(EventLine);
		PbnReader.ReadLine(AnnotatorLine);
		PbnReader.ReadLine(ApplicationLine);
		PbnReader.ReadLine(BoardLine);
		PbnReader.ReadLine(CompetitionLine);
		PbnReader.ReadLine(ContractLine);
		PbnReader.ReadLine(DateLine);
		PbnReader.ReadLine(DealLine);
		PbnReader.ReadLine(DealerLine);
		PbnReader.ReadLine(DeclarerLine);
		PbnReader.ReadLine(EventDateLine);
		PbnReader.ReadLine(NorthLline);
		PbnReader.ReadLine(ResultLine);
		PbnReader.ReadLine(ScoringLine);
		PbnReader.ReadLine(SiteLine);
		PbnReader.ReadLine(VulnerableLine);
		PbnReader.ReadLine(SouthLine);
		PbnReader.ReadLine(WestLine);
		PbnReader.ReadLine(EastLine);
		
	};

	It should_observation = () =>
	{
		PbnReader.Tournament.MetaProperties.Count.ShouldEqual(1);
		MetaProperty prop = PbnReader.Tournament.MetaProperties.Single();

		prop.Name.ShouldEqual("Generator");
		prop.Value.ShouldEqual("Ruter");
	};

	It should_add_a_new_board_if_event_line_is_Read = () =>
		{
			PbnReader.Tournament.Boards.Count().ShouldBeGreaterThan(0);
		};

	It should_read_annotator = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Annotator")
			.Value.ShouldEqual("Växjö BK");
	};

	It should_read_application_property = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Application")
			.Value.ShouldEqual("Ruter - Read more at www.brenning.se");
	};

	It should_read_board_info_date = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Date")
			.Value.ShouldEqual("2008.01.14");
	};

	It should_read_board_number = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Board")
			.Value.ShouldEqual("1");
	};

	It should_read_competition = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Competition")
			.Value.ShouldEqual("Pairs");
	};

	It should_read_contract = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Contract")
			.Value.ShouldEqual(string.Empty);
	};

	It should_read_deal = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Deal")
			.Value.ShouldEqual("N:T2.QT53.KJ32.AK9 Q53.974.A94.J843 AK9876.AK.76.Q62 J4.J862.QT85.T75");
	};

	It should_read_dealer = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Dealer")
			.Value.ShouldEqual("N");
	};

	It should_read_declarer = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Declarer")
			.Value.ShouldEqual(string.Empty);
	};

	It should_read_east = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "East")
			.Value.ShouldEqual(string.Empty);
	};

	It should_read_event_date = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "EventDate")
			.Value.ShouldEqual("2008.01.14");
	};

	It should_read_north = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "North")
			.Value.ShouldEqual(string.Empty);
	};

	It should_read_result = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Result")
			.Value.ShouldEqual(string.Empty);
	};

	It should_read_scoring = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Scoring")
			.Value.ShouldEqual("MatchPoints;MP1");
	};

	It should_read_site = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Site")
			.Value.ShouldEqual("Växjö BK, Växjö, Sydöstra, Sverige");
	};

	It should_read_south = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "South")
			.Value.ShouldEqual(string.Empty);
	};

	It should_read_vulnerable = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "Vulnerable")
			.Value.ShouldEqual("None");
	};

	It should_read_west = () =>
	{
		PbnReader.Tournament.Boards.First()
			.BoardProperties.Single(x => x.Name == "West")
			.Value.ShouldEqual(string.Empty);
	};
}

I think the later looks much nicer than the previous and most important i outputs much nicer to TeamCity. Any “foo” can read what the MSpec runner outputs to both consoles, resharper and TeamCity.

Tags: , ,

C# | Testing | MSpec