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

blog comments powered by Disqus

About the author

Life architect specialized in programming

Month List

Widget Twitter not found.

Root element is missing.X