 Tuesday, February 06, 2007
Just a quick post, for part two let's look at grouping our results for display purposes... first off, I grab only the episodes which we think are "valid" (in this case ones which are assigned a series, episode and part number)...
EpisodeParser episodeParser = new EpisodeParser();
YouTubeSearcher searcher = new YouTubeSearcher(DeveloperId);
// get a list of all the parts we are interested in
IEnumerable<EpisodePart> parts = (from result in searcher.QueryByTags("QI", "Quite", "Interesting")
select episodeParser.Parse(result))
.Where(p => p.SeriesNumber > 0 && p.EpisodeNumber > 0 && p.PartNumber > 0)
.Distinct();
Obviously what I've done here is probably rather poor style, surely the where clause could have been inside the select statement??... but I'm just trying to illustrate how you can mix the two notations... next we're going to group the results up using some nested "group by" and object projections... this is disgustingly easy, what more can you say about it.
// group those parts
var allSeries = from part in parts
group part by part.SeriesNumber into series
orderby series.Key
select new { SeriesNumber = series.Key,
Episodes = from episodePart in series
group episodePart by episodePart.EpisodeNumber into episodes
orderby episodes.Key
select new {EpisodeNumber = episodes.Key,
Parts = from chunk in episodes
orderby chunk.PartNumber
select chunk
}
};
Great, I think that saved us a lot of time, compared to doing it ourselves in C# 2.0... let's now generate a little page to view the results - for now I'm just writing code in NUnit fixtures - so we'll use Console.WriteLine... though in a website I would probably just use some quite similar brail template code.
foreach (var series in allSeries)
{
Console.WriteLine("<h3>Series {0}</h3>\r\n", series.SeriesNumber);
foreach (var episode in series.Episodes)
{
Console.WriteLine("<h4>Episode {0}</h4>\r\n", episode.EpisodeNumber);
foreach (var author in episode.Parts.GroupBy(p => p.Result.Author))
{
Console.WriteLine("<div>Author: <strong>{0}</strong>\r\n", author.Key);
foreach(var part in author)
{
YouTubeResult result = part.Result;
Console.WriteLine("<a href=\"{0}\">part {1} <img src=\"{2}\" /></a>", result.Url, part.PartNumber, result.ThumbUrl);
}
}
}
}
I've also dumped the output from this to an html page, if you'd like to have a look at it. Next time I might have a look at storing the results of the youtube query with base4 and writing some code for a little daemon which can identify newly posted episode parts on a periodic basis... which should all lead towards implementing the RSS feed capability I discussed in part one. But for now I'm off to enjoy the sun!
 Friday, February 02, 2007
Well, the recent musings of Alex James have caught my interest around LINQ (part one and two) - and so I thought I would start having a play with LINQ and using it to query some well known web sites... and decided to go with dragging some info out of youtube... Basically, I like QI (also known as Quite Interesting - british comedy show) - and there's a lot of episodes on youtube, querying on the tags "QI", "Quite" & "Interesting" returns about 1000 results... almost every result represents a part of an episode... and most people posting are kind enough to include something in the title or description of the video which lets you know which episode and series it belongs to... However it's going to take some effort to actually start watching at series 1, episode 1 and work your way through the episodes in the right order by endlessly browsing youtube's search results... unless we start "value adding" - creating an "episodic" view over youtube's data. At this point you could put your magic hat on and wish for features from you're youtube "episodes" site: - Perhaps an RSS feed notifying me of new episodes as they're listed on youtube daily.
- A nice way to see a series, it's episodes, and each part that I need to play.
- Maybe some aggregation of episode summaries from another web site for each episode (in this case I'm thinking maybe TVRage.Com...)
So you can quickly see that we can start making something virtually out of nothing, like MacGyver... but much lamer and without the hair. First things first, I started having a look at YouTube's API - you can search by tags and get plenty of info back from their REST web service, but you can only get 20 results per page, 20 results isn't much cop so I built a simple class which gives access to the whole set of search results as an IEnumerable<YouTubeResult > - here's the code for that:
public class YouTubeSearcher
{
private const string TagQuery = "http://www.youtube.com/api2_rest?method=youtube.videos.list_by_tag&dev_id={0}&tag={1}&page={2}";
private string _developerId;
public YouTubeSearcher(string developerId)
{
if (string.IsNullOrEmpty(developerId)) throw new ArgumentNullException("developerId");
_developerId = developerId;
}
public IEnumerable<YouTubeResult> QueryByTags(params string[] tags)
{
if ((tags == null) || (tags.Length <= 0)) throw new ArgumentNullException("tags", "tags must contain one or more tags");
for (int page=1; true; page++)
{
List<YouTubeResult> results = QueryByTagAndPage(JoinTags(tags), page);
if (results.Count <= 0) break;
foreach (YouTubeResult result in results) yield return result;
}
}
private string JoinTags(string[] tags)
{
if (tags.Length == 1) return tags[0];
StringBuilder builder = new StringBuilder(tags[0]);
for (int i=1; i<tags.Length; i++) builder.AppendFormat(",{0}", tags[i]);
return builder.ToString();
}
private List<YouTubeResult> QueryByTagAndPage(string tag, int page)
{
Console.WriteLine("Querying by tag: {0}, page: {1}", tag, page);
Stopwatch watch = Stopwatch.StartNew();
try
{
List<YouTubeResult> results = new List<YouTubeResult>();
string uri = string.Format(TagQuery, _developerId, tag, page);
XPathDocument xpd = new XPathDocument(uri);
XPathNavigator xpn = xpd.CreateNavigator();
XPathNodeIterator xniError = xpn.Select(@"/ut_response");
xniError.MoveNext();
if (xniError.Current.GetAttribute("status", String.Empty) == "fail")
{
string expression = "/ut_response/error/description";
string errorText = xpn.SelectSingleNode(expression).InnerXml;
throw new YouTubeException("Error occured while querying youtube: {0}", errorText);
}
try
{
XPathNodeIterator xni =
xpn.Select(@"/ut_response/video_list/video");
while (xni.MoveNext())
{
XPathNavigator navigator = xni.Current;
string title = navigator.SelectSingleNode("title").InnerXml;
string url = navigator.SelectSingleNode("url").InnerXml;
string thumbUrl = navigator.SelectSingleNode("thumbnail_url").InnerXml;
string id = navigator.SelectSingleNode("id").InnerXml;
string description = navigator.SelectSingleNode("description").InnerXml;
int lengthInSeconds = int.Parse(navigator.SelectSingleNode("length_seconds").InnerXml);
string author = navigator.SelectSingleNode("author").InnerXml;
results.Add(new YouTubeResult(id, url, title, thumbUrl, lengthInSeconds, description, author));
}
}
catch (XPathException xpe)
{
throw new YouTubeException("Xpath exception occured: {0}", xpe.Message);
}
return results;
}
finally
{
Console.WriteLine("Query complete in {0}ms", watch.ElapsedMilliseconds);
}
}
}
Following on from that we need to parse each search result and attempt to pull out it's episode information: - Series Number
- Episode Number
- Part Number
At this point we might also make the assumption that parts should be grouped by the user who posted them - in case the same episode has been posted twice by two users (quite likely, people are silly). Parsing part information could be done using successive LINQ queries, but It's actually not that pleasant considering we're generally interogating only two text fields - the title for the clip, and it's description - horses for courses - so instead I built a quick 'n dirty " EpisodeParser" class... here's the code for that:
public class EpisodeParser
{
private List<AbstractContributor> _contributors = new List<AbstractContributor>();
public EpisodeParser()
: this(
new SeriesContributor(),
new EpisodeContributor(),
new PartContributor(),
new XFormatContributor(),
new PilotContributor(),
new PartOfPartsContributor(),
new WordNumberPartsContributor())
{
}
public EpisodeParser(params AbstractContributor[] contributors)
{
if (contributors != null) _contributors.AddRange(contributors);
}
public EpisodePart Parse(YouTubeResult result)
{
EpisodePart ep = new EpisodePart(result);
foreach (AbstractContributor contributor in _contributors)
{
contributor.Contribute(ep);
}
return ep;
/*
* QI Series 4 EpisodePart 12 (part 3)
* s2e10 part 1/4
* Qi Series 1 Ep 5 Part 1/3
* QI 2x01
* Take Out 1
* QI Pilot EpisodePart part 6
* S2E09
*/
}
private class SeriesContributor : AbstractContributor
{
public override void Contribute(EpisodePart episodePart)
{
int? seriesNumber = ParseNameNumber(episodePart, "series", "s");
AssignSeriesNumber(episodePart, seriesNumber);
}
}
private class EpisodeContributor : AbstractContributor
{
public override void Contribute(EpisodePart episodePart)
{
int? episodeNumber = ParseNameNumber(episodePart, "episode", "ep", "e");
AssignEpisodeNumber(episodePart, episodeNumber);
}
}
private class PartContributor : AbstractContributor
{
public override void Contribute(EpisodePart episodePart)
{
int? partNumber = ParseNameNumber(episodePart, "part", "p");
AssignPartNumber(episodePart, partNumber);
}
}
private class PilotContributor : AbstractContributor
{
public override void Contribute(EpisodePart episodePart)
{
if (episodePart.Result.Title.ToUpper().Contains("PILOT"))
{
AssignSeriesNumber(episodePart, 1);
AssignEpisodeNumber(episodePart, 1);
}
}
}
private class XFormatContributor : AbstractContributor
{
public override void Contribute(EpisodePart episodePart)
{
int seriesNumber = 0;
int episodeNumber = 0;
if (SplitNumber(episodePart, ref seriesNumber, ref episodeNumber, 'X'))
{
AssignSeriesNumber(episodePart, seriesNumber);
AssignEpisodeNumber(episodePart, episodeNumber);
}
}
}
private class PartOfPartsContributor : AbstractContributor
{
public override void Contribute(EpisodePart episodePart)
{
int partOf = 0;
int parts = 0;
if (SplitNumber(episodePart, ref partOf, ref parts, '/'))
{
AssignPartNumber(episodePart, partOf);
}
}
}
private class WordNumberPartsContributor : AbstractContributor
{
private static readonly Dictionary<string, int> _phrases;
static WordNumberPartsContributor()
{
_phrases = new Dictionary<string, int>();
_phrases.Add("part one", 1);
_phrases.Add("part two", 1);
_phrases.Add("part three", 1);
_phrases.Add("part four", 1);
_phrases.Add("part five", 1);
_phrases.Add("part six", 1);
_phrases.Add("part seven", 1);
_phrases.Add("part eight", 1);
_phrases.Add("part nine", 1);
_phrases.Add("part ten", 1);
}
public override void Contribute(EpisodePart episodePart)
{
AssignPartNumber(episodePart, FindPhrase(episodePart, _phrases));
}
}
}
At this point we have the building blocks for starting to write some LINQ queries... here's my first test - running a basic "select all"...
YouTubeSearcher searcher = new YouTubeSearcher(DeveloperId);
IEnumerable<YouTubeResult> results = from result
in searcher.QueryByTags("QI", "Quite", "Interesting")
select result;
Console.WriteLine("total results: {0}", results.Count());
Trying a more explicit style of query, and parsing episodes:
EpisodeParser episodeParser = new EpisodeParser();
YouTubeSearcher searcher = new YouTubeSearcher(DeveloperId);
IEnumerable<EpisodePart> parts = searcher.QueryByTags("QI", "Quite", "Interesting")
.Select(result => episodeParser.Parse(result))
.Where(part => part.SeriesNumber == 1 && part.PartNumber == 1)
.OrderBy(part => part.EpisodeNumber);
foreach (EpisodePart part in parts.Distinct())
{
Console.WriteLine(part);
}
Which will let us know which episodes in series 1 exist... Bit of a rush, but next time I'll start digging in a little deeper... At this point though it's worth noting that we have some stuff for free because of IEnumerable<T >... - Search results are being processed as their yielded, if we're just looking for the first matching item for a query we can stop without having to request additional result pages on a match is made.
- Same goes for episodes, we only parse them as they are required - no unnecessary overhead.
So far nothing has required LINQ, but I think we'll start to see it being a great time saver come the next couple of parts... compared to writing the code ourselves. We shall see!
 Thursday, February 01, 2007
BackgroundAs a bit of background, for the last couple of months I've been doing some work for a personal client aside from the work for Seismic Technologies which is on the back burner till we pick up some more investment interest (I'm still the lead dev though) - the project is an add-in for an existing product (COM interop) which be must be deeply-integrated, as well as being capable of being used in stand alone mode... It's a very advantageous project considering the time frame, but that's part of the fun :) Once the clients moved forward on some marketing I'll post a little more about some of the challenges I've faced along the way. At any rate - the project's stalled briefly while the clients doing a little business analysis to get the underlying methodology sorted - so they've asked me to switch across to building the license generation / customer portal / license purchasing module for their preexisting CMS system ( CMS made simple - PHP) ... where are the ruby or Monorail CMS's to wean my clients onto? PHP... ack... So I haven't used PHP in anger for years and years, but the one advantage of dynamic languages is you can generally hit the ground running a lot quicker then their statically compiled competitors... maybe PHP even more so because it's focused on web development. So far the two things that have bugged/puzzled me are: - Classes don't call their base classes default constructor implicity - you have to do that yourself. This isn't all bad, at least you can control when the default constructor is called.
- Methods are instance, static and pseudo-instance all in one...
I think the second one bugs me more because you end up with 2+ potential code paths that should be accounted for in testing, if your exposing an "api" for consumption - or more importantly you should throw an exception for the usages you don't wish to allow (I'm probably missing the "quick and dirty" point here of course ;o) - it's hard to fight years of instance methods != static methods... Maybe I'm just old fashioned and there's nothing wrong with this, I should have a flick through Programming Language Pragmatics again, there must be some other dynamic languages with similar behavior? At any rate, the example:
class A { function foo() { if (isset($this)) { echo '$this is defined ('; echo get_class($this); echo ")\n"; } else { echo "\$this is not defined.\n"; } } }
class B { function bar() { A::foo(); } }
$a = new A(); $a->foo(); A::foo(); $b = new B(); $b->bar(); B::bar();
And the output of that little example is shown below, notice how A:foo() knew it was being called from class B... I wonder what phalanger is doing under the hood to achieve the same thing in the CLR...
$this is defined (a) $this is not defined. $this is defined (b) $this is not defined.
 Saturday, January 13, 2007
Well I've been really slack of late re: the blog and the coding community in general - but at any rate, what brought me back into focus was actually getting email regarding Splicer - yes, there are people using it... who knew?
At any rate, I also noticed that reading back through other peoples blogs - it appears I was actually tagged by Alex James - now I had a personal blog for a while before a technical one, and it's that kind of bollix that drove me away from it as a past time :) but then I'm just a sour sod, soooo.... I've decided to respond, if for no other reason but to confirm that I do in fact read Alex James blog - but I wont bother inflicting the pain on anyone else (plus most of the NZ bloggers have already been swatted a few weeks ago)
Without further delay, 5 things you probably don't know about me:
1) I got engaged last year on the rocks at pink beach, omaha.
2) The first programming language I learned was basic for the vic20, followed shortly by gwbasic. And then (turbo) C++ when I was 11 or 12 (I forget exactly).
3) I bought my first car at the end of last year, a BMW 318ti.
4) I grew up on a farm and was home schooled for a couple of years, got Dux at the dubious Rodney college in Wellsford, and then did some tertiary studies (ie. made new friends) at Unitec.
5) My cat is named shodan, and my previous cat was run over and consequently body-snatched by gypsies or possibly itallians (according to the neighbours at the time...)
Wasn't that fun, tune in next time when I actually post something of value :)
 Sunday, December 10, 2006
Appologies for the extended blog down-time... moved house last weekend (and did a few major changes to my network, including a entirely new domain etc.) and only just got a chance to set up an ubuntu/apache/mod_proxy box for forwarding requests onto the server where my blog lives.
Drop me a comment if anything seems a little wonky (ie. missing images etc.) as I did the config in a hurry :)
 Thursday, November 30, 2006
The evil designer attribute ;o)
I'm working on a project at the moment where the client want's an "Add-in" for an existing piece of software... it's a COM interop project, .Net 2.0 (and yes, it uses Castle, IoC sits in nicely with the IServiceProvider :) and it hosts a custom designer, and the add-ins must exist in their own directory, outside of the host applications base directory.
At any rate, it's been working just fine so far, but today I was trying to introduce some custom designers... and they weren't being constructed... which highlighted some odd behaviour in the design time support - I have 6 or so assemblies, most are support assemblies, some containing controls and their associated designers, something like this:
public class MyButtonDesigner : ControlDesigner { ..}
[Designer(typeof(MyButtonDesigner))] public class MyCustomButton : ButtonBase { .. }
And then there is the core assembly which hosts the design surface etc. and contains a number of COM-visible classes - one of these class in the core assembly is constructed via Com interop by it's Name, and everything starts it's life from there in their Add-in, much like a Main class in a WinForms project.
So the core assembly references the support assemblies, and has no problem creating controls at runtime within these assemblies...
But then...
However, the design time support fails to construct or make use of the custom designers... and doesn't blow up, it just fails silently - personally I hate this kind of behaviour... I like things to fail fast, in your face :)
It appears the custom designer is never constructed because the design time infrastructure fails to locate the assembly it's held within, even though my code has just created an instance of a control from the same said assembly not an instant before... arghh!
To my mind this is madness... I'm not passing a string reference to the type, and the assembly is already loaded, it should be as simple as using the Type in the attribute to create an instance of the designer - looking at the internal implementation of this attribute, I can see where it all goes terribly wrong:
public DesignerAttribute(Type designerType) { this.designerTypeName = designerType.AssemblyQualifiedName; this.designerBaseTypeName = typeof(IDesigner).FullName; }
It's still a puzzle as to why this isn't being resolved... the assembly is already loaded against the current AppDomain - I guess I could mess with the fusion logger to figure it out, there must be some subtle different - but I didn't have the time to waste, so I just cheated, implementing an AppDomain.AssemblyResolve event handler to take care of returning the assembly that's already loaded when matching against a set of pre-loaded assemblies.
I think the silent failure is what annoys me the most, this is something which could be easily introduced and might not be imediately detected when performing a minor update... I'm still not sure how I could write a test to detect it...
Speaking of custom designers... I'm unimpressed by how most custom designers are marked internal... especially for classes that are designed to be subclassed... why the hell is the ButtonBaseDesigner marked internal?
 Sunday, November 26, 2006
Vista
Well I've been playing with a couple of things this weekend... first off is Vista... it's been on my laptop for a while now, but I haven't really been using it because It's a bit of a drag getting my development environment set up... but I've been working on that tonight, and have my current project being built successfully... so that's cool... the only real problem is my laptop is no longer a laptop since I've installed Vista...
The problem is power... where as I could squeeze upwards of 2 to 2.5 hours from the battery under xp, in power saving mode with Vista I get an hour if I'm lucky... it says exactly 1 hour of available capacity after boot, every "build all" in Visual Studio reduces that number by about 5 minutes... this is not particularly useful, heading to a cafe to code for an hour or two is looking problematic - At first I thought that perhaps the minimum speed step value wasn't being utilized, but according to the resource monitor it's at "37%" of the maximum frequency, which is ~800mhz, which seems right... So I have no idea why my battery life is so bad.
Second to this, max performance is only workable for about two hours before the machine will freeze - too much heat - not a new problem, certainly this was an issue under xp sp2 or win2k3 - Incidentally it's an HP NW8240 which scores 4 on the "Vista" performance scale (Pentium M 2.13ghz, 2gig ram, ATI FireGL V5000) and the cooling solution is really not adequate to deal with the heat generated from the processor and GPU at max performance, but it only cropped up while gaming in the past with XP SP2... now this happens while working with Visual Studio 2005 for a couple of hours... I suspect it's probably because I'm using Aero, the laptops definitely hotter then it used to be, I'll switch off Aero tomorrow and see how I fare, problem goes away if I switch to balanced performance mode of course, but without max performance Resharper 2.02 is an utter dog.
Other problem I have is getting bluetooth to work, which is a bit of a pain when using my phone as I've lost the mini-SD adaptor so I can't slot it into my monitors card reader... more a minor niggle though, I can live without it for now.
Supreme Commander
The other thing I've been playing with this weekend was the Supreme Commander beta, spiritual successor to Total Annihilation... TA was the defacto RTS we played while I was at uni, and it also served as the standard by which we measured any future RTS's (and generally found them wanting, especially when it came to an interface for selecting, scheduling and command units)...
SupCom is very faithful to the concepts and feel of TA, and it runs just fine on Vista, but I did end up lowering all the settings, and still suffered from overheating and crashes after half an hour of play on my laptop... *sigh* I need to experiment a little further to see how the dual screen support works, but so far the game looks great, and it provides some wonderful strategic views which make it easier to control masses of units (And I do mean masses, hopefully the multi-threading support is good, I could definitely see this game benefiting from dual-core, or maybe quad-core once an implementation is released which isn't quite so gimped)... looking forward to getting my hands on the finished product when it's released next year.
 Tuesday, November 14, 2006
More mocking...
The title for this entry should probably be "more IRepository<T> mocking" - as that's what I'm mocking out... but at any rate, let's get down and dirty... In this entry I will cover mocking out a "complex" method, incidentally I think the size of this test suggests that our test isn't fine grained enough (and that individual features within the data loader should be exposed so we can test them better) - but that's my problem, not yours ;o)
Background
So, as some background, this is a test for a class called "DataLoader" - a most unimaginative name for a class that loads data... it's used for loading an XML file which has a bunch of info for different entities... if your recall the last entry, I was talking about simplistic music information, well this class can be used to load musical data into the store, including something not mentioned so far, which is the "test suite" - the test suite defines tests which can be run against the music in the store, the details aren't really up for discussion, but it's for executing customer acceptance tests, not unit tests.
The test below checks one of the primary paths, where by no pre-existing data is available, so it must all be created by the data loader first, before creating the customer acceptance test, which references the track, release etc. data (the definition for customer acceptance test suites are also stored in the store, as are the results from run the customer acceptance tests).
Diving In...
So here is the test for loading a "test suite" for a single track, the song "Dirty Harry" by the "Gorillaz", we are testing the behavior of loading the file when the track, release and artist do not already exist in the store - most of this is basic RhinoMock's usage (though I'm no expert, I'm probably misusing some of the features...).
[Test]
public void LoadDataFreshForSingleTrack()
{
/**** RECORD ****/
Environment environment = new Environment();
SourceDevice sourceDevice = new SourceDevice();
RecordingDevice recordingDevice = new RecordingDevice();
Track track = new Track();
ObjectPath artistPath = (Artist.Fields.Name == "Gorillaz");
Expect.Call(_artistRepository.FindOne(artistPath)).Constraints(Base4Query.PathEqual(artistPath)).Return(null);
_artistRepository.Save(null);
LastCall.Constraints(Property.Value("Name", "Gorillaz"));
ObjectPath releasePath = (Release.Fields.Name == "Demon Days");
Expect.Call(_releaseRepository.FindOne(releasePath)).Constraints(Base4Query.PathEqual(releasePath)).Return(null);
_releaseRepository.Save(null);
LastCall.Constraints(Property.Value("Name", "Demon Days") &&
Property.ValueConstraint("Artist", Property.Value("Name", "Gorillaz")));
_trackRepository.Save(null);
LastCall.Constraints(Property.Value("Name", "Dirty Harry") &&
Property.ValueConstraint("Release", Property.Value("Name", "Demon Days")));
ObjectPath encodingPath = (TrackContentEncoding.Fields.Name == "MP3");
Expect.Call(_encodingRepository.FindOne(encodingPath)).Constraints(Base4Query.PathEqual(encodingPath)).
Return(null);
_trackContentRepository.Save(null);
LastCall.Constraints(Property.ValueConstraint("Track", Property.Value("Name", "Dirty Harry")));
ObjectPath trackReferencePath = (Track.Fields.Name == "Dirty Harry" &&
Track.Fields.Release.Name == "Demon Days" &&
Track.Fields.Release.Artist.Name == "Gorillaz");
Expect.Call(_trackRepository.FindOne(trackReferencePath)).Constraints(
Base4Query.PathEqual(trackReferencePath)).Return(track);
Expect.Call(_trackRepository.FindOne(trackReferencePath)).Constraints(
Base4Query.PathEqual(trackReferencePath)).Return(track);
ObjectPath environmentPath = (Environment.Fields.Name == "Indoors");
Expect.Call(_environmentRepository.FindOne(environmentPath)).Constraints(
Base4Query.PathEqual(environmentPath)).Return(environment);
ObjectPath sourceDevicePath = (SourceDevice.Fields.Name == "Loopback");
Expect.Call(_sourceDeviceRepository.FindOne(sourceDevicePath)).Constraints(
Base4Query.PathEqual(sourceDevicePath)).Return(sourceDevice);
ObjectPath recordingDevicePath = (SourceDevice.Fields.Name == "Loopback");
Expect.Call(_recordingDeviceRepository.FindOne(recordingDevicePath)).Constraints(
Base4Query.PathEqual(recordingDevicePath)).Return(recordingDevice);
_testSuiteRepository.Save(null);
LastCall.Constraints(Property.Value("Name", "One track, one loopback sample") &&
Property.Value("Description",
"Testing with 30 second snippets pulled from the source files with audacity (Loopback)") &&
Property.ValueConstraint("Tracks",
ItemListOf<Track>.NumberOfItems(1) && ItemListOf<Track>.IndexedItemConstraint(0, Is.Same(track))));
/***** REPLAY *****/
_mockRepository.ReplayAll();
DataLoader loader = CreateLoader();
loader.LoadData(OneTrackFile, ContentPath);
_mockRepository.VerifyAll();
}
First you'll probably see that the callback and anonymous delegates from the last post are absent, they've been replaced with constraints... which has made the syntax a lot more concise - so far the rhino mocks support is pretty basic, we have two static classes being used for creating the constraints:
- Base4Query - for constraints on ObjectQuery and ObjectPath arguments/properties.
- ItemListOf<T> - for constraints on IItemList<T> arguments/properties.
The constraints method in RhinoMocks expects the same number of arguments as the associated method call on the mock object, constraints work on the arguments value, however you can apply constraints to the value of single property of an argument, and those constraints can be logically AND or OR'd together - for instance here:
_testSuiteRepository.Save( null);
LastCall.Constraints(Property.Value("Name", "One track, one loopback sample") &&
Property.Value("Description",
"Testing with 30 second snippets pulled from the source files with audacity (Loopback)") &&
Property.ValueConstraint("Tracks",
ItemListOf<Track>.NumberOfItems(1) && ItemListOf<Track>.IndexedItemConstraint(0, Is.Same(track))));
We are applying these constraints:
- The first argument should:
- Have a property called "Name", and it's value should be "One track, one loopback sample".
- And have a property called "Description", and it's value should be "Testing...." etc.
- And have a property called "Tracks", which is of type IItemList<Track> which:
- Contains a total of 1 items
- And the item at index 0 should:
- Be the same as the instance returned from an earlier save call.
Notice that we have to call the method we want to apply constraints to first, so that we can address it with LastCall - a necessary evil when the method has a return type of void... otherwise you can use the more pleasant "Expect.Call(...)" convention.
Also, I'm declaring instances of the expected Base4 object paths, and then applying them in the constraints... I could in-line the object paths, but I then need to disambiguate the overloaded call to the FindOne method, so we have my preference:
ObjectPath trackReferencePath = (Track.Fields.Name == "Dirty Harry" &&
Track.Fields.Release.Name == "Demon Days" &&
Track.Fields.Release.Artist.Name == "Gorillaz");
Expect.Call(_trackRepository.FindOne(trackReferencePath)).Constraints(
Base4Query.PathEqual(trackReferencePath)).Return(track);
or this:
Expect.Call(_trackRepository.FindOne(( ObjectPath) null)).Constraints(
Base4Query.PathEqual(Track.Fields.Name == "Dirty Harry" &&
Track.Fields.Release.Name == "Demon Days" &&
Track.Fields.Release.Artist.Name == "Gorillaz")).Return(track);
The second involves less code, but I think it's easy to get confused between the compile time query language's logical operators and those of the RhinoMock's constraints... each have merits of course.
Conclusion...
I'm still working through some of the finer points of the implementation (and so won't publish any code just yet...) but this should give you some ideas about how you can test base4 interactions in your application with RhinoMocks, instead of hitting the base4 server and testing the results... and I think it's a lot more intuitive when attempting to do TDD with base4...
I would also like to make a special mention that this code won't actually work with the latest published release of Base4... there is an issue preventing the addition of items to an IItemList<T> in some situations when you have not set a valid default context - there is a fix (Which works well) in the Base4 trunk, and I assume Alex James will release this at some point... and thanks for resolving that issue so quickly Alex :) much appreciated.
 Saturday, November 11, 2006
Domain Driven Design Afterthoughts...
I just finished reading Jimmy Nilsson’s Applying Domain-Driven Design and Patterns [ADDDP] book... I actually read it cover to cover, something I’ve found difficult to compel myself to do with some of the other books that have been lying around my desk for a wee while now (such as Petzold’s “Applications = Code + Markup” – a great book to assault someone with in a dark alley)...
First off, I think this book is a pretty good, it didn’t cover a lot of new ground for me, but It’s very down to earth, which I liked, and it’s encouraged me to have a read of Evan’s and Fowler’s more definitive works on the subject too... I also liked the fact that this book does attempt to tie the whole story together, from rough sketch through to identifying the domains language, using TDD to build up your domain model and even some of the gritty integration work, including evaluating OR/M features, using NHibernate as an example, and even looks into inversion of control contains (spring sadly, it would’ve been great to have seen Castle get a mention) and finally AOP. On the down side – I think the book could’ve tackled the application of rules to your domain model a little better (I wanted to see more code) and depending on your TDD knowledge, you might find that a couple of the chapters don’t really do much for you as there’s little focus on the model so much as the key concepts of red, green, refactor...
At any rate, one thing I did keep rolling around in the back of my mind is just how Base4.Net fits into the “domain model” picture ... it’s difficult to nail down, there are plenty of mechanisms for implementing most of what you need to create a domain model, for instance:
- Inheritance, though it’s support for discriminators in user types isn’t quite up to scratch – it only works via “ItemBase” at the moment – though I think Alex James mentioned that this would be implemented at some point... and though I haven’t tried, you could roll your
|