Archives
-
TDD validation... NetTiers Style
Let’s face it. At the end of the day, even the most purist of us object-oriented bigots know the real truth. Objects need data to work. Yes, behaviour defines an object and all that OOD popycock but I mean, a Customer domain object still needs a name, telephone number, and address in order to be of any value in an Enterprise system right? Sure there are “business rules” we want to write but all the unit tests in the world are not going to get around the fact that objects need data and sometimes that data needs to be saved (or retrieved, or both).
The problem we all face, sooner or later, is how do I get data from some datasource (for the sake of the argument here, let’s say a database) into (and out of) my object? There are really two ways to go about it and for the purpose of this article, let’s avoid the O/R mapping ramble. So that leaves us plucking data from properties exposed in a business object and shuffling it off to parts unknown to be stored somewhere. Service objects and Repositories and Mappers and Translators and all kinds of other things having intimate knowledge of the types and properties of domain objects. On the flipside of this, there’s a pattern called ActiveRecord that follows the principle that an object would communicate with an outside service and publish it’s properties for persistence (or the other way round loading data from a data source). Neither of these situations is really pleasant but it’s a bear we deal with when we have to shut down machines and expect that information to be there tommorow.
The latest version of a set of CodeSmith templates called NetTiers provides us with an interesting way to look at our domain (and getting it to/from a database). CodeSmith is a code generation tool and using the NetTiers templates, it creates a n-tier set of classes (about 90 of them from a simple database) that allow you to do cool stuff like this:
DataRepository.MyObjectProvider.Save(MyObject);
This is all neatly wrapped up in a transaction (if the database supports it and/or you configure it to use transactions) and all dynamically generated based on tables and columns in a database. It’s all very slick and NetTiers can create an entire app (minus the front-end) for you in less than 10 seconds. So what’s this got to do with TDD?
Imagine we’re starting an application and we’re going to be building it using TDD. We have some requirements and we’re ready to start writing tests. Given a requirement that a Customer name can not exceed 50 characters we might write a test like this:
namespace NetTiersTDD.UnitTests
{
[TestFixture]
public class CustomerFixture
{
[Test]
[ExpectedException(typeof(ApplicationException))]
public void CustomerNameShouldNotExceed50Characters()
{
Customer customer = new Customer();
customer.Name = "X".PadLeft(51);
}
}
}
This is fine and a good start. So now we create our business entity to support the test:
namespace NetTiersTDD.Entities
{
public class Customer
{
private string _name;
public string Name
{
set { _name = value; }
}
}
}
Great. Our unit test compiles but out test fails (which is good, remember Red-Green-Refactor). Now let’s make the test pass in our business entity:
namespace NetTiersTDD.Entities
{
public class Customer
{
private string _name;
public string Name
{
set
{
if(value.Length > 50)
throw new ApplicationException();
_name = value;
}
}
}
}
Fantastic. So this can be typical when doing something like these checks. There are other checks you might do and the general idea is around failing fast so you would throw an exception when some validation fails on your business entity (you don’t want to find out your domain object is invalid several layers or operations down the stack).
Fast forward about 3 days and you find yourself slugging through requirements like this (and others) and your classes start buffing up and out. More domain logic, lots of tests, goodness. However, you might notice something happening with some of your tests. Take a look at our Customer test now:
namespace NetTiersTDD.UnitTests
{
[TestFixture]
public class CustomerFixture
{
[Test]
[ExpectedException(typeof(ApplicationException))]
public void CustomerNameShouldNotExceed50Characters()
{
Customer customer = new Customer();
customer.Name = "X".PadLeft(51);
}
[Test]
[ExpectedException(typeof(ApplicationException))]
public void CustomerAddressShouldNotExceed255Characters()
{
Customer customer = new Customer();
customer.Address = "X".PadLeft(256);
}
[Test]
[ExpectedException(typeof(ApplicationException))]
public void CustomerAgeShouldNotExceed65()
{
Customer customer = new Customer();
customer.Age = 66;
}
}
}
(note: I’m using ApplicationException here but you would probably have a specific exception for different validations)
Lots of exceptions being thrown because well, it’s a business rule violation. By design, we don’t want to make our domain object invalid by any means so we throw an exception when setting a property that’s invalid. This will immediately let us know something is wrong and, if we had a UI, we could inform the user of the error (maybe with a Message Box telling him that a field is too long or required or whatever). This is fine and dandy but makes me queasy. I mean, exceptions are expensive. Imagine I had an import routine that brought in crappy (un-validated) data from an Excel spreadsheet and created 1,000 domain objects in a collection. That’s a heck of a lot of exceptions.
An alternative is to post-validate an object. That’s a valid approach. Let whatever information come into the system (from a user or that crappy spreadsheet) and validate the object after the fact. Then I can do something like check an IsValid property (or the return value from a method called Validate()) and act accordingly. I’m still not letting my domain object get out of control because the unit of work hasn’t completed until I validate the object so we’re good to go. This is okay but now we need a Validate method, some way to retrieve the validity of our business object, some way to set the rules for various properties, and other “helper” methods. Some astute readers will jump up and say “I know, let’s decorate properties with attributes and use reflection in a standard method to validate a setter”. Cool and nice out-of-the-box thinking and certainly something you could do (and something that might start to drift into AOP territory but that’s a whole ‘nuther can o’ worms that I won’t get into either).
So other than reflection, I now have to write a bevy of code to handle validation, invalid state, modify changes to properties (or maybe respond to them) and lots of other little niggly bits. If I was being paid by the line then maybe I want to do this. And hey, if I build a framework we can use it everywhere in the organization so it’s an investment. Yes, this is again a valid thing to do but my motto is don’t write something that can be created by a tool for you (and works). Using the validation framework generated from NetTiers, we can get all this (and more) for a pretty insignificant cost. On top of that, we have a DAL written for us that we don’t have to worry about and can use later when we need to persist our business entities (and what application doesn’t do that?).
Again Bil, what the heck does this have to do with TDD?
Like I said, NetTiers (via CodeSmith) will generate billions of lines of code for you with the single click of a button. Buried in that code are some base classes that NetTiers will use with your business entities (gen’d from your tables based on it’s columns and constraints) and buried deeper down than that are some pretty cool utilities and classes that you can use to write less code (but get more accomplished). I’m all for something that works and let’s me write less code to get more done and that’s what I can get from NetTiers.
Let’s go back to our Customer class and try some NetTeirsTDD. First thing is you need NetTiers to generate you it’s artifacts. This involves creating a database and a table. Yeah, I know. TDD. Database. The TDD guys are saying “Oh man, he’s got it all wrong”. Just walk with me on this okay?
Create a new database and add a simple table to it. Here’s the one I used:
The table is called Ernie with ID and Title columns. The name and columns really don’t matter so you can call it Temp, Balloon, Mayonaise, or Wurstie for what it’s worth. Just call it something that won’t be one of your business objects because hey, we’re doing TDD and we don’t create business objects until we need them right? The temp table (and resulting generated code) is so you have a starting point (and a framework to leverage, which is what we’re getting to).
Now run CodeSmith using the NetTiers templates. In the latest version (2.0) of the template, you’ll need to set a few things:
- Point it at your datasource via the ChooseSourceDatabase property
- Set IncludeUnitTest to True (this will create a unit test project)
- Set LaunchVisualStudio to True
- Set GenerateWebLibrary and GenerateWebSite to False
- Set ViewReport to False (or leave it on if you want, your choice)
Once those options are set hit Generate. In about 10 seconds you’ll have about 60 classes, 4 projects, and the basis for your starter solution.
Finally let’s get into some TDD. Go to the opened instance of Visual Studio with the solution that was created for you. The unit test project that you created will already have references to the various other projects (including a raft of unit tests to test persistence of your Ernie class). Let’s go back and start our Customer test again, except we’ll do things a little differently.
First let’s modify the test that we originally wrote. Instead of expecting an exception, we’ll use an IsValid boolean property:
namespace NetTiersTDD.UnitTests
{
[TestFixture]
public class CustomerFixture
{
[Test]
public void CustomerNameShouldNotExceed50Characters()
{
Customer customer = new Customer();
customer.Name = "X".PadLeft(51);
Assert.AreEqual(false, customer.IsValid);
}
}
}
Looks harmless and something we might do. IsValid will return true or false if the customer object is valid (and in this test, we want IsValid to return false because we expect it to be that way after setting the name with too many characters). Now here’s where the magic comes in. Create your Customer class but inherit from a class called EntityBase (a class that’s part of the framework NetTiers created). To get this code setup to compile, EntityBase requires a few abstract methods to be implemented. They don’t have to do anything (for now) so you have a couple of options (like create a StubEntityBase and implement the methods in that). So let’s do that make our Customer class work. Create a class called StubEntityBase with these members implemented like so:
public class StubEntityBase : EntityBase
{
public override void CancelChanges()
{
throw new Exception("The method or operation is not implemented.");
}
public override string TableName
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override int ID
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public override string Title
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public override string EntityTrackingKey
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public override object ParentCollection
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public override string[] TableColumns
{
get { throw new Exception("The method or operation is not implemented."); }
}
}
Now inherit Customer from StubEntityBase instead of EntityBase.
public class Customer : StubEntityBase
{
private string _name;
public string Name
{
set
{
_name = value;
}
get
{
return _name;
}
}
}
Great, run the unit test and it fails. IsValid returns true because we haven’t put any validation checking in our business object yet. Now it’s time to make it pass. Change the Customer class to show this:
public class Customer : StubEntityBase
{
protected override void AddValidationRules()
{
ValidationRules.AddRule(
Validation.CommonRules.StringMaxLength,
new Validation.CommonRules.MaxLengthRuleArgs("Name", 50));
}
private string _name;
public string Name
{
set
{
_name = value;
OnPropertyChanged("Name");
}
get
{
return _name;
}
}
}
We’re doing two things in the business class to make our test pass. First, we’re overriding a method called AddValidationRules. This gets called (lazy loaded) when a validation check is done (for example when you call IsValid). The ValidationRules is a list of rule objects (again, classes in the NetTiers framework) that you can setup. The CommonRules contains things like length checks, required fields, not greater than, etc. You can also get into designing your own rules but that’s for another blog. So we tell it to check the property named “Name” and make sure it doesn’t exceed 50 characters. If it does, it adds a BrokenRule object to another list.
Then we add a call to the setter for name to trigger an event called “OnPropertyChanged” and pass the name of the property we’re setting. This will do a validation check against the property by invoking any rules that match the property name (you can have multiple rules per property).
Finally when the IsValid property is called on the business entity, it checks the ValidationRules list, finds a list of BrokenRules (rules that failed validation when the OnPropertyChanged event was called) and returns true of the count is not 0.
Cool huh? Lots of business validation stuff going on but none of the plumbing we had to write to get it. There are some other neat side effects that you can get from this framework. For example, back in our unit test we can just ask the object what the error(s) are and print them out:
[Test]
public void CustomerNameShouldNotExceed50Characters()
{
Customer customer = new Customer();
customer.Name = "X".PadLeft(51);
if (!customer.IsValid)
Console.WriteLine(customer.Error);
Assert.AreEqual(false, customer.IsValid);
}
This prints out:
------ Test started: Assembly: NetTiersTDD.UnitTests.dll ------
Name can not exceed 50 characters
1 passed, 0 failed, 0 skipped, took 0.56 seconds.
Very useful when writing out the errors to the screen or a log (rather than having to figure out what the error was, then compose a message for it).
Let’s go back to what was originally generated with that first cut off the database. By default, NetTiers generates a report showing you all the craziness it did. This includes a plethora of classes like ListBase, EntityFactory, BrokenRule, ValidationRules, EntityCache, EntityPropertyComparer, and a bunch of other classes. These classes actually have nothing to do with data persistance but form an object framework that we can use (like above) to validate our business objects.
These are all generated by the tool but are used by business entities. For example, ListBase is a generic list (just like List<T>) but supports searching, filtering, sorting, and databinding. Feel free to load up business entities into it and bind it to a GridView for some fun. EntityCache is a class that you can use to add entities to a cache manager. The manager is actually driven by Enterprise Libraries (which the NetTiers code is built on top of) but it lets you add your own business entities to a cache and find them later. This can be handy for in-memory Repositories or just quick and dirty tests where you need to retrieve known objects (but don’t want to create singletons or something). There’s lots of great classes and helpers that are generated for you and all it takes is a single table to get going (which you can delete later and all the code for it will wash away) so explore the business entity project that gets created and check it out (there’s also full documentation on all the classes and methods that you can create using something like Sandcastle).
Finally, at some point you will (or probably) have to save your business entities data in a database. Again, this is where it gets simple (but it’s a bit of a manual process). Let’s say you’ve written up your object with various rules (not just length checking stuff but real business tests) and need to save it. You take a look at what the business object has and decide on a structure. Just a simple structure since all you really need to do is CRUD. Create the table and regen the DAL using CodeSmith and NetTiers (saving your business validations before you do this). Now just drop the business validations in the new genrated object that will derive from MyObjectBase and you’re good to go.
Let me clarify the steps I described above:
- Write your unit tests and create whatever business classes that come out of your tests (adding your business classes to the Entities project NetTiers generated)
- Identify what properties you need to persist from your business class and create a simple table with the same name as the class to hold them
- Rename your business class to a placeholder (like MyBusinessObject.txt)
- Generate the DAL from the database
- The Entities project will contain one business class for each table created. This class is split into two parts. MyBusinessObject and MyBusinessObjectBase. MyBusinessObject.cs is never regenerated so you’re free to modify it with your business logic and validations (plus any properties that are not stored in the db). MyBusinessObjectBase is a generated file that will get recreated each time the code gen runs (and update accordingly if you add/remove columns in the database)
For example I create a Customer class that will hold all my customer info. I decide that I’m going to persist the FirstName, LastName, and Age properties. I create a table named Customer to hold these (putting whatever data length and range validations I want on them). Then I rename Customer.cs to a placeholder like Customer.txt. I regen the DAL using NetTiers. I then take the generated Customer class (that will not get overwritten) and drop in the contents of Customer.txt (the part that does my business rules validations).
You’ll probably do two things with the generated business class. First is to override the AddValidationRules method and add any non-data related rules. Second is to write any business domain rules methods and properties that are not stored in the database (like calculated values). Again this class is not overwritten when you regenerate your code, but the generated code will stay in sync with your database adding new properities and modifying data integrity rules along the way (say if you change the length of a field).
The cool thing is that things like the length validations (on properties that are persisted) will already be done for you based on your settings in the database. Go ahead and check out the MyBusinessObjectBase.generated.cs file and you’ll see it’s already written out the AddValidationRules method based on columns in the database (remember to call the base method if you add your own rules in the business class).
Okay, this approach isn’t perfect. It’s not true TDD as in only writing what’s needed. However what does System.Object give you? A bit fat nothing except a unique GetHashCode method and a couple of other useless things. Why not build objects on top of something that has a little extra for you (without having to write it yourself). You do have to make some tradeoffs and for some, it’s too painful to do this. First, you have to bite the bullet that you have a 1:1 relationship of object to table. That’s just the way the NetTiers templates work. Second, you have to have a table (initially and eventually) to generate the code that will do all the data validation but then most business entities have to wind up stored somehow. This technique just helps it along.
You do also have to build your system on top of Enterprise Libraries and some people are not willing to make that leap just yet. Also, some TDD purists will say that you shouldn’t be dependent on this kind of framework but only write what you need when you need it. I agree but this (IMHO) helps. Look at Rocky Lhotka and his CSLA.NET framework. It’s a highly successful business object framework that you could do TDD from. This isn’t that much different from it (other than the fact you need a database to start from). Maybe I’m wrong and this approach is all wrong, but you need to decide that, not some book or blog entry on the interweb.
Finally though, as I mentioned, after all this is said and done I can have my business objects persist with a few lines of code and leverage a validation framework to boot. To me, that’s worth the cost of a one-to-one relationship of table to object and some trade-offs if it means that I can work faster with my business domain and validate it quickly and easily. YMMV.
NOTE: On reading this over now and thinking about it a little more, you might start with a table called Stub. Add an integer ID field and nothing else to it (CodeSmith and NetTiers need at least one column to create something from). Generate the framework from it and base your business objects off the Stub base class (StubBase) rather than creating your own stub like described above. It’ll work in the interim until you have a table of your own for your business class and in the case where you don’t have a table for a business object, you could use this one. Just a thought anyways.
ADDITIONAL NOTE: Thinking even more about the stub idea, you could probably toss away the Data project and Data.SqlClient project and just be left with the Entities project with a business object framework to leverage, but then that’s really no fun is it?
FINAL NOTE: Okay, took me about 4 hours to put this entry together so hope it tweaks your brain and encourages discussion in the community. Maybe I’m completely off my rocker and just talking out of my butt, or maybe not. Anyways, this is a technique I’m using on a current (Enterprise) project and it’s coming along nicely. A combination of using the Composite Application UI Block, NetTiers, Domain Driven Design, and more Unit Tests than you can shake a stick at and I’m pretty confident the project will be a success in many ways.
-
Star Trek Haiku Spam
I think I've finally caught up on emails, RSS feeds, newsgroups, and work since my departure into the Canadian backwoods (note to self: never go anywhere without a good internet connection again, ever). I found an interesting piece of spam this morning in my inbox:
William Shatner trilogy Captains Glory
sometime next second Vulcans Soul Exiles
Josepha Sherman Susan Shwartz. third
new Pocket Books projects
papers Getting LinksPersonally, I think it's some kind of twisted Star Trek-Gilligans Island Haiku that an out-of-control computer somewhere in Colorado is generating.
-
Managing the information stack
I try to keep Life 1.0 in a somewhat organized chaos. When it comes to my computer, there are a lot of things on it (source code, programs, documents, etc.) and there are a lot of things it keeps eating up from the internet (feeds, favorites, newsgroups, emails). That’s a lot of stuff to manage. So when it comes to trying to manage it all, it becomes a bit of a bear. I’m a strong fan of David Allen’s Gettings Things Done (GTD) approach and try to use it with work and hobbies and such (BTW, check out Hanselminutes from a couple of days ago for a lot of great Outlook add-ins around this). This helps organize the mess, but then there’s the problem of disparate types of data and what tools to use to track, read, and store them.
Take for example the big three. RSS feeds, Email, and Newsgroups (I won’t lump in bookmarked favorites as they aren’t as dynamic). As I’ve committed to Office 2007 now, Outlook is my email tool and it does a pretty good job (other than being a resource pig). The links from Scott’s show help on organizing things so hopefully SpeedFiler or something will help with this. Also the flag/task feature you get OOTB is a great way to follow-up on things. Any email that comes in becomes one of 4 things:
- Something I should respond to immediately (a simple reply) so I do (then delete it)
- Something I should follow-up later so I flag it for today, tommorow, next week, etc. This creates a task that’s link to the email. When the email is marked as complete (or deleted), so is the task.
- Something I should delete so I do
- Something I should file for future reference, so I do
The filing is what I might make use of SpeedFiler for, as I do have a lot of different PST files (one for projects, one for software products, one for online things like passwords/sites/domain registrations) and a lot of folders in each one (one folder for each project on the go, one folder for each piece of software organized under the company name). It all works pretty well and since about June I’ve kept my inbox down to a dozen or so messages, all marked for follow-up (as opposed to the 800 items that would just sit there until I slugged through them and moved them somewhere).
That takes care of emails but what about RSS feeds? Outlook 2007 is pure and simple crap when it comes to these (at least in beta 2). They don’t update correctly, don’t always display correctly, and seem to chew up even more resources than email alone does. That sent me back to RSS Bandit. I really enjoyed that tool but it suffers from a few problems. First is focus. When I’m just moving the cursor through the list of feeds, it sometimes decides to change focus back to the tree. I originally had it configured to mark categories read when I moved off them, but that quickly changed. It does a good job of displaying the feeds and overall isn’t bad (although it does suffer again from memory hog as it’s always running around 200mb which to me just isn’t right). I just installed FeedDemon and it looks interesting. What I like about it is the fact it tracks what I read which helps me find those feeds that I subscribed to, but couldn’t care less about a month later. Maybe FD will turn out to be my reader of choice so time will tell.
So now we have to deal with newsgroups. I read about a dozen groups along with a half dozen private Microsoft ones. That’s about my capacity as I just can’t keep up beyond that. RSS Bandit has a feature to read newsgroups which looked like a good idea at the time, but it’s threading model is horrible and it’s impossible to figure out where the thread started. There are some stand-alone newsgroup readers out there, but nothing that really jumps out at me. I was using Newshound, an Outlook add-in, but it suffers from beta block. It doesn’t support 2007 (yet).
It’s all very bothersome. One one hand, I could use something like Newshound and intraVnews as Outlook add-ins, which would give me pretty good RSS and Usenet readers all in the comfort of my Outlook. On the other hand, I could just delegate Outlook for emails and use FeedDemon for RSS and [insert cool news reader here] for Usenet.
So what’s a girl to do? Turn Outlook into the catch-all for information (like Scott has said a few times, just embed OneNote into the note feature and we might as well throw in a browser tab and we don’t need any other products) or have different tools for similar jobs. It’s all information at the end of the day, and I don’t see RSS feeds, newsgroup posts, or email as being that different in the grand scheme of things. Maybe we need a BDC for information on the desktop.
What do you guys recommend? How do you manage your information stack?
-
The ScrumMaster Returns
I’m currently being a ScrumMaster/mentor for a team on a new Smart Client project (I always think I should be rolling a 20–sided die with each build). It’s a big step for the team as they’re generally VB6 and non-OO/non-.NET developers. It’s also a pretty big step for the client as they’re new to Agile as I’ve been introducing little things over the last few months (unit tests, continuous integration, presentations on various patterns, etc.). Now the rubber hits the road and we’re full on Scrum (well almost, still need to convince them to estimate with story points intead of hours). The project officially kicked off Monday (when I was still off the grid somewhere in B.C.) and just I got back today to do some slight course corrections with the team (rebuilding the solution tree, focusing more on the domain and tests rather than the UI and back-end infrastructure, etc.). I’ll probably be blogging more on the project and how things are going and some lessons learned from the Scrum side of things (in between what SharePoint I can wiggle in between my daily stand-ups).
The other cool thing is that this is the first project I’m using Conchango’s Scrum for Team System plug-in in a non-pet project manner. I’ve managed a bunch of little projects since the plug-in came out, and even took some Enterprise projects that were “Scrum-like” (read: doing items iteratively, but not really so it never worked out) and built a “what-if” scenario with it in VSTS, but now I’m giving the tool a real run for it’s money (free!) and seeing where it can go. So far things are coming along, although there are a couple of glitches like the product backlog isn’t filling up on the dashboard (the report works though). I think it’s due to the fact that I didn’t assign relative weights to the items or something, but not sure. The burn-down is coming along (even if it’s only the 2nd day) as the team is really digging entering the work remaining rather than how many hours they scrutinized over what they did. While the burn-down is currently looking like a burn-up (the projected end date is a few months off our original iteration due to some flux in the estimates) I’m confident it’ll come down and start leveling out to what it should be.
If you haven’t checked out the plugin and want to do some real Scrum planning and tracking with VSTS, please check it out. I was strugging with the start of this project, screwing around with spreadsheets that I’ve used in the past and decided to try out the plug-in to fully manage the project. The PM is digging it as he can easily see where things are at and has no worries about what’s being done (and what’s to be done) and I find it works better than MSF for Agile and is simpler to use (although I’m still hunting around for a webcast where a Microsquishy explained MSF for Agile in Scrum terms which may change my opinion). The plugin is free and you can just add it to your VSTS setup quickly if you want to try it out. You download it here from Conchango’s site (which also has a lot of great documentation and resources on Scrum in general as it applies to the tool) and check it out.
-
Falling behind the RSS ball
Note to self: Don’t spend more than a few days away from a computer hooked up to the internet. It’s bad for your catch-up health when you get back.
I’m still slugging through 800+ emails, and then there’s a few days worth of RSS feeds that total about 3000+ posts I still need to read through.
It’s going to be a loooong day!
-
Swamped in Redmond this week
Just a note that I’m swamped in Redmond this week. We’re doing some work with Microsquishy, and frankly I’m finding that I’m getting back to the hotel at 10 to be back on campus by 8. Somewhere in between there I’m trying to catchup on about 200 emails I have, plus trying to get some code and whatnot written in the interim. Not a lot of time for this, so if any mails/questions fall by the wayside that you send me please understand I won’t be too responsive this week (or next week for that matter as I’ll be close to being off the grid as I’ve ever been). Thanks.
-
3-tier Architecture wtih ASP.NET 2.0
If you haven’t built a full-blown, multi-layer, 3–tier web application with ASP.NET yet then here’s your chance. In case you missed it back in June, Scott Mitchell has a 10 part series on doing such a thing. While I don’t agree with some of the approaches (I’m not a fan of strongly typed DataSets) he uses the series is well written, has a lot of great techniques on building a website with ASP.NET 2.0, and is a good read for those wanting to get into it.
Here’s the links the entire series:
- Tutorial 1: Creating a Data Access Layer
- Tutorial 2: Creating a Business Logic Layer
- Tutorial 3: Master Pages and Site Navigation
- Tutorial 4: Displaying Data With the ObjectDataSource
- Tutorial 5: Declarative Parameters
- Tutorial 6: Programmatically Setting the ObjectDataSource's Parameter Values
- Tutorial 7: Master/Detail Filtering With a DropDownList
- Tutorial 8: Master/Detail Filtering With Two DropDownLists
- Tutorial 9: Master/Detail Filtering Across Two Pages
- Tutorial 10: Master/Detail Using a Selectable Master GridView with a Details DetailView
There’s also a more detailed page here that has both the C# and VB.NET downloads available along with PDF versions of the articles. It’s a wonderful series and really goes deep into everything you need to know about building an Enterprise-scale web site.
Enjoy!
-
The sad state of Groove add-ons
Where have all the add-ons gone? Groove is an interesting tool. It came out of nowhere, powered by Ray Ozzie, and looked like Notes done right. And it was. The architecture was great, using it in small organizations was cool, and it was perfect for the road warrior sales guy to keep in touch with the mothership.
However it seems (to me anyways) that it’s a decaying piece of wood that nobody seems to care about. Version 3.1 is pretty good and it’s been added to the Office 2007 lineup, so MS must think this thing is going places (or the fact that Ray is going to be #1 at Microsoft). Today we was cleaning up a Groove workspace we use for little things (todo lists, wish lists of things we want to buy, places we want to go, family pics, etc.) and wanted more. I hadn’t checked Groove.net for a long time (probably 6 months or so) and figured there must be new add-ons that would be neat to get.
What a sad state of affairs. First, the tools page hasn’t really changed much in the last year. Second, there’s only a couple of vendors offering very little. What’s worse, a few of them have websites that don’t even work. Information Patterns for example only has 3 products on the go and when you look at their Toucan Collaborate tool (a productivity suite) the link to their dedicated site takes you to a parked domain. There was an interesting tool which was an implementation of Reversi, but when you go to NetsenderCorp’s site and try to download or order it (or any of their products), you get a 404 error.
This certainly isn’t anything like say the DotNetNuke community where Snowcovered keeps pumping out modules every week. Modules that work and that you can download or buy. While many people argue Groove is stepping on SharePoints toes with it’s offline capability (people always call Groove the “offline” SharePoint), it’s still a pretty neat platform. It’s highly extensible, you can build .NET apps to plug into it, and it seems to have a lot of potential. Now it seems to be the red-headed step-child of Office and thrown in for good measure (maybe to try to revitalise the Groove community).
All in all, either I’m looking at the old world and there’s some hidden corner on the web where Groove is thriving and producing all kinds of new content, or Ray and the Groove guys figure they’ve maxed out their investment and the community that never was just isn’t.
-
Busy as an MSBee
Very busy this week. Next week I’m at Microsquishy as we go into the dark corners of the MS underworld and do crazy development-type stuff all week long in the halls of Building 40. Not sure if I’m allowed to say what we’re doing so that’s as close as it gets for now.
As I’m winding this week up, I have a small team (3 devs, 1 architect, 1 BA, 1 business user, 1 QA) I’m mentoring and providing Architectural and development guidance to (and being their Scrummaster) for a new project with a client. It’s all very exciting as we’re going to be using the Smart Client Software Factory, ClickOnce deployment, continuous integration, TDD, etc. The whole nine yards with a team of devs who haven’t written a .NET application before (or had very little exposure to it). Sounds like a horror story, but I’ve done it before with newbie teams and it’s all good. So that’s got me running around like an MVP with my head cut off, getting them setup with tool and hooking them up with labs and such.
Finally the week after next (August 21–25) I’m off in the backwoods of BC on some family reunion thing (my first). Yeah, sounds like fun huh? A little bit of my sanity came back as I found out they have Wi-Fi at the camp which was going to save me many trips to the local ‘bucks for my internet fix. Yeah, off the grid is not in my vocabulary.
-
What is the Rock smoking?
In my always present ego-searching that I do, I come across odd instances of people referring to me or my blog. Case in point, I stumbled across this link on a site that (I guess) features interesting links he finds on the blog-o-sphere. His entry for this blog:
"nice blog about sharepoint .net and sometime OS X integration"
Hmmm. I don't remember blogging about OS X integration, ever. Is he talking about something else (like maybe MOSS?). I dunno.
The only mention of Macintosh or OS X I've made was back during PDC here and I just causually mentioned (in the last paragraph) that my new Mac Mini kicked the llamas hiney. Other than that post (and now this one) that's the only mention of Mac anywhere here (not that I'm a Mac-homophobe or anything as I do own one).
I sometimes wonder what drives people to say things these days?
-
SharePoint Forums language translations
Just in case you see a weird error in the new SharePoint Forum Web Part with regards to translations, several strings were added to the *.LNG files to finish off the translations. Please check the default language file that comes with the install on the missing ones (I’ll try posting a more detailed blog later with a list of them in it) and update your own language file accordingly. Also if you can flip the updated language file back to me I’ll update the language pack file release and make it available to everyone. Thanks.
-
SharePoint Forums 1.2 Released to the wild
Okay, here it is kids, the August (1.2) release of the SharePoint Forums Web Part. This release includes the following features/fixes:
New
- Navigation menu to quickly jump to anywhere on the system
- Ability to place forums and categories in whatever display order you want
- Topic title/full body message search with links to topics/forums
- Ability to configure date format displays
Fixed
- Support for ASP.NET 2.0 installations on WSSv2 and SP2
- Order of forums is fixed to allow “bumping” of topics
- Post count display fixed on home page
- Last post date/time display fixed
Changed
- Topics now require a subject before being created
- When replying to a specific message (not the entire thread) you can now preview the message you’re replying to
Sounds like fun? Go grab it here. Just install the MSI over the old version. Your data files will not be affected. If you are upgrading from 1.1 then please follow these instructions (included in the DrinkMe.txt file) to perform the upgrade:
If you are upgrading from version 1.1 to 1.2 please do the following:
- Install the 1.2 MSI
- Add the following to web.config anywhere inside the <configuration> section:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="BilSimser.SharePoint.WebParts.Forums" publicKeyToken="e516dadc23877c32" />
<bindingRedirect oldVersion="1.1.0.0" newVersion="1.2.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime> - Perform and IISRESET
- Refresh the page where your Forums Web Part lives
- Voila! Version 1.2.
Note: If you’re still running 1.0 you can do the same, just change 1.1.0.0 to 1.0.0.0.
Please let me know via email if you have any questions/problems.
-
SharePoint Menus in C#
There are a lot of great articles out there that talk about how you can leverage the SharePoint style of drop-down menus (you know that cool looking javascript menu) and creating your own. All of them require you to write some HTML and/or JavaScript and embed this into a CEWP on a page. This is fine, but when you’re building your own Web Parts, you might want something a little simpler.
In working on the SharePoint Forums Web Part, I wanted to create a drop-down menu populated by some of the links in the system and the categories and forums so someone could easily jump to a forum immediately. Most forums use a standard web drop-down list but since I had the SharePoint menu javascript already at my disposal I thought I would use it. So I put together this little class that creates me a menu, lets me add items to it, then just spits out the HTML that I can render to my page. Here’s the full class:
1 /*2 * SharePointMenus.cs
3 * Dynamic SharePoint-style menus in C#
4 *
5 * Copyright (c) 2006 by Bil Simser, bsimser@shaw.ca
6 *
7 * This work is licensed under the Creative Commons
8 * Attribution-NonCommercial-ShareAlike 2.5 License.
9 *
10 * To view a copy of this license, visit
11 * http://creativecommons.org/licenses/by-nc-sa/2.5/
12 * or send a letter to Creative Commons, 559 Nathan
13 * Abbott Way, Stanford, California 94305, USA.
14 */
15
16 using System;
17 using System.Text;
18 using System.Xml;
19
20 namespace BilSimser.SharePoint.WebParts.Forums.Utility
21 {
22 /// <summary>
23 /// Small helper class that will build out a menu in
24 /// the SharePoint drop down style for adding to HTML output.
25 /// </summary>
26 public class SharePointMenu
27 {
28 #region Fields
29
30 private string _header;
31 private XmlElement _currentNode;
32 private XmlElement _rootNode;
33 private XmlDocument _xmlDocument;
34
35 #endregion
36
37 #region Constructors
38
39 public SharePointMenu(string title)
40 {
41 createHeader(title);
42 createXmlDocument();
43 }
44
45 #endregion
46
47 #region Properties
48
49 private string Header
50 {
51 set { _header = value; }
52 get { return _header; }
53 }
54
55 #endregion
56
57 #region Public Methods
58
59 public void AddMenuItem(string title, string url)
60 {
61 AddMenuItem(title, url, string.Empty);
62 }
63
64 public void AddMenuItem(string title, string url, string imageUrl)
65 {
66 XmlElement childNode = _xmlDocument.CreateElement("ie:menuitem", "http://www.tempuri.com");
67 childNode.SetAttribute("id", "MSO_MyMenu_Link1");
68 childNode.SetAttribute("title", title);
69 if(imageUrl != string.Empty)
70 childNode.SetAttribute("iconSrc", imageUrl);
71 childNode.SetAttribute("onMenuClick", string.Format("javascript:window.location.href='{0}';", url));
72 childNode.InnerText = title;
73 _currentNode.AppendChild(childNode);
74 }
75
76 public void AddSeparator()
77 {
78 XmlElement childNode = _xmlDocument.CreateElement("ie:menuitem", "http://www.tempuri.com");
79 childNode.SetAttribute("type", "separator");
80 _currentNode.AppendChild(childNode);
81 }
82
83 public void AddSubMenu(string title)
84 {
85 AddSubMenu(title, string.Empty);
86 }
87
88 public void AddSubMenu(string title, string imageUrl)
89 {
90 XmlElement childNode = _xmlDocument.CreateElement("ie:menuitem", "http://www.tempuri.com");
91 childNode.SetAttribute("type", "submenu");
92 if(imageUrl != string.Empty)
93 childNode.SetAttribute("iconSrc", imageUrl);
94 _currentNode.AppendChild(childNode);
95 _currentNode = childNode;
96 AddLabel(title);
97 }
98
99 public void CloseSubMenu()
100 {
101 _currentNode = _rootNode;
102 }
103
104 public void AddLabel(string title)
105 {
106 XmlElement childNode = _xmlDocument.CreateElement("ie:menuitem", "http://www.tempuri.com");
107 childNode.SetAttribute("type", "label");
108 childNode.InnerText = title;
109 _currentNode.AppendChild(childNode);
110 }
111
112 public override string ToString()
113 {
114 StringBuilder sb = new StringBuilder();
115 sb.Append(Header);
116 sb.Append(_xmlDocument.InnerXml);
117 return sb.ToString();
118 }
119
120 #endregion
121
122 #region Private Methods
123
124 private void createXmlDocument()
125 {
126 _xmlDocument = new XmlDocument();
127 _rootNode = _xmlDocument.CreateElement("menu", "http://www.tempuri.com");
128 _rootNode.SetAttribute("id", "MSO_MyMenu");
129 _rootNode.SetAttribute("class", "ms-SrvMenuUI");
130 _xmlDocument.AppendChild(_rootNode);
131 _currentNode = _rootNode;
132 }
133
134 private void createHeader(string title)
135 {
136 Header = string.Format("<div class=\"ms-HoverCellInActive\" "+
137 "onmouseover=\"this.className='ms-HoverCellActive'\""+
138 "onmouseout=\"this.className='ms-HoverCellInActive'\">"+
139 "<a id=\"MSO_MyMenuLink\" title=\"{0}\" style=\'CURSOR: hand\''+
140 "onclick=\"MSOWebPartPage_OpenMenu(MSO_MyMenu, this);\""+
141 "tabindex=\"0\">{0}<img alt=\"{0}\" src=\"/_layouts/images/menudark.gif\""+
142 "align=\"absBottom\"></a></div>", title);
143 }
144 #endregion
145 }
146 }
It’s not beautiful and there are a lot of improvements you can make. For example, some of the names are hard coded and should be generated dynamically and keeping track of the submenu levels should be something managed outside of the class. However it works and is easy to use.
To use it, just drop this class into your project and you can create a menu with a few lines of code like this:
7 public class TestMenu8 {
9 public void CreateHtmlMenu()
10 {
11 SharePointMenu menu = new SharePointMenu("My Menu");
12 menu.AddMenuItem("First Item", "http://www.microsoft.com");
13 menu.AddMenuItem("Second Item", "http://www.slashdot.org");
14 menu.AddSeparator();
15 menu.AddMenuItem("Last Item", "http://www.example.com");
16 }
17 }
That will get you a simple menu with a few items. Then in your RenderWebPart, or wherever you’re writing out the Web Part contents just write it out:
19 public override void RenderWebPart(HtmlTextWriter writer)
20 {
21 SharePointMenu menu = new SharePointMenu("My Menu");
22 menu.AddMenuItem("First Item", "http://www.microsoft.com");
23 menu.AddMenuItem("Second Item", "http://www.slashdot.org");
24 menu.AddSeparator();
25 menu.AddMenuItem("Last Item", "http://www.example.com");
26 writer.WriteLine(menu.ToString());
27 }
You can also create multiple level menus and submenus. Just call the AddSubMenu method(s) in the class (with or without an image). When you call AddSubMenu, any future calls to AddMenuItem will just add it to that submenu. When you want to back out of the menu, just call the CloseSubMenu method. Like I said, it’s not pretty as it really only supports 1 level of menus and will always back out to the root. However it can be modified and updated with a little work to support unlimited submenus, dynamic generation of IDs, etc. Use your imagination.
Here’s the menu for the SharePoint Forums Web Part which sparked creating this class. The categories and forums are dynamically generated based on what the user can see, etc. and the menu is built up based on options in the system and what the user can do. Looks pretty slick and only took a few lines of code:
So have fun with it and feel free to modify it. If you do extend/enhance it let me know and I’ll update the resource. You can grab the class file here for use in your program. It’s released under the Creative Commons License.
-
SharePoint Forums 1.2 Sneak-a-peek
The work is almost done on version 1.2 of the SharePoint Forums Web Part. The release will go out later tonight after I chow down some food and build the package. A full list of changes will accompany the post when the release goes out but here’s some screenies of what’s coming down the pipe.
Replies with previews:
Full text search on titles and messages:
Sortable Forums and Categories:
See ya in a few hours!
-
Custom date formatting with SharePoint Forums
I’m just getting ready to release version 1.2 of the SharePoint Forums Web Part. One feature was the ability to control the date and time display for users. This has been added and documented on the CodePlex wiki here.
The SharePoint Forums Web Part (version 1.2 and higher) provides the ability to set the default date/time format when displaying dates in the system. This is used on the last post of a topic, individual messages, and user profiles. Basically anywhere a date/time stamp is displayed you have control over the format.
The format is based on how your server is set so "d" on a US setting is Month/Day/Year whereas on a Canadian setting it's Day/Month/Year. The following values will produce the output shown for August 7, 2006 at 11:30:00 AM:
d - 8/7/2006
D - Monday, August 07, 2006
f - Monday, August 07, 2006 11:30 AM
F - Monday, August 07, 2006 11:30:00 AM
g - 8/7/2006 11:30 AM
G - 8/7/2006 11:30:00 AM
m,M - August 07
r,R - Mon, 07 Aug 2006 11:30:00 GMT
s - 2006-08-07T11:30:00
t - 11:30 AM
T - 11:30:00 AM
u - 2006-08-07 11:30:00Z
U - Monday, August 07, 2006 5:30:00 PM
y - August, 2006You can also create a custom pattern using the following formatters. This means that if you don't like any of the formats above, you can just create your own and really personalize the forums.
d - The day of the month. Single-digit days will not have a leading zero.
dd - The day of the month. Single-digit days will have a leading zero.
ddd - The abbreviated name of the day of the week, as defined in AbbreviatedDayNames.
dddd - The full name of the day of the week, as defined in DayNames.
M - The numeric month. Single-digit months will not have a leading zero.
MM - The numeric month. Single-digit months will have a leading zero.
MMM - The abbreviated name of the month, as defined in AbbreviatedMonthNames.
MMMM - The full name of the month, as defined in MonthNames.
y - The year without the century. If the year without the century is less than 10, the year is displayed with no leading zero.
yy - The year without the century. If the year without the century is less than 10, the year is displayed with a leading zero.
yyyy - The year in four digits, including the century.
gg - The period or era. This pattern is ignored if the date to be formatted does not have an associated period or era string.
h - The hour in a 12-hour clock. Single-digit hours will not have a leading zero.
hh - The hour in a 12-hour clock. Single-digit hours will have a leading zero.
H - The hour in a 24-hour clock. Single-digit hours will not have a leading zero.
HH - The hour in a 24-hour clock. Single-digit hours will have a leading zero.
m - The minute. Single-digit minutes will not have a leading zero.
mm - The minute. Single-digit minutes will have a leading zero.
s - The second. Single-digit seconds will not have a leading zero.
ss - The second. Single-digit seconds will have a leading zero.
f - The fraction of a second in single-digit precision. The remaining digits are truncated.
ff - The fraction of a second in double-digit precision. The remaining digits are truncated.
fff - The fraction of a second in three-digit precision. The remaining digits are truncated.
ffff - The fraction of a second in four-digit precision. The remaining digits are truncated.
fffff - The fraction of a second in five-digit precision. The remaining digits are truncated.
ffffff - The fraction of a second in six-digit precision. The remaining digits are truncated.
fffffff - The fraction of a second in seven-digit precision. The remaining digits are truncated.
t - The first character in the AM/PM designator defined in AMDesignator or PMDesignator, if any.
tt - The AM/PM designator defined in AMDesignator or PMDesignator, if any.
z - The time zone offset ("+" or "-" followed by the hour only). Single-digit hours will not have a leading zero. For example, Pacific Standard Time is "-8".
zz - The time zone offset ("+" or "-" followed by the hour only). Single-digit hours will have a leading zero. For example, Pacific Standard Time is "-08".
zzz - The full time zone offset ("+" or "-" followed by the hour and minutes). Single-digit hours and minutes will have leading zeros. For example, Pacific Standard Time is "-08:00".
: - The default time separator defined in TimeSeparator.
/ - The default date separator defined in DateSeparator.
% c - Where c is a format pattern if used alone. The "%" character can be omitted if the format pattern is combined with literal characters or other format patterns.
\ c - Where c is any character. Displays the character literally. To display the backslash character, use "\\".Note: these are all standard .NET formaters for DateTimeInfo, but documented here for users of the Web Part who are not .NET developers.
-
Comment spam on the rampage
Holy mother of Isis. What’s with the comment spam on weblogs these days? I’m getting close to 100 comments a day that are all spam. Luckily I have anonymous comments turned off and the spam filter on Community Server is recognizing these as spam and not publishing them. Still, is anyone else getting this much comment spam? It’s been quiet for sometime now but the last week or two things have just gone nuts. Talk about the coming apocalypse, Nostradamus sure didn’t see this one coming.
-
Internet Exploder 7 - An absolute train wreck
There have been a lot of great train wrecks throughout movie history that stick in mind. The Fugitive, Back to the Future III, and Under Siege 2 to name a few. Tonight I went through the painful process of creating my own train wreck, namely Internet Explorer 7 Beta 3.
I’ll admit that I don’t have the average desktop. I’m running both versions of the .NET framework, I have both the 2003 and 2005 flavours of Visual Studio, there’s the GAT and the GAX, a dozen or so SDKs and toolkits, Virtual Server and Virtual PC, Subversion, Groove, Windows Live Messenger, OneNote, PC-Cillin, and an army of small little tools, some that sit in my tray and are always running (like PureText). However I at least expect IE to at least work. A little?.
First off I run http://localhost as my home page. It’s just a static HTML web page with links to local projects so I don’t forget. Yeah, did you read that? Static HTML. No fancy scripts, no complicated ActiveX controls. Just a bleepin’ web page. And IE 7 says this to me:
WTF? You compiled my HTML page and got an error. How nice. I guess nobody told you that HTML pages don’t get compiled. I checked the source of the page again, just in case I did something silly like oh, I don’t know, added some code. Nope. A HTML compliant page with barely any text on it and no graphics (Jakob Nielsen would be proud of me). I could get past this but each and every time I tried to edit the options to allow “intranet” access (like that’s a bad thing?) it hung. Complete and utter hang. So much so that nothing would work or allow me to click on anything. I shut down the process and tried it again. Same thing.
No POS browser is going to get the better of me so I thought I would be smart. Rather than clicking in the error message area that pops up (or whatever that band of death is called) I thought I would be sneaky and go through the menu to invoke the option from there. Of course IE figured out what was going on and promptly hung again. And again. And again.
I did manage to grab the Google toolbar and navigate to the Google home page. Of course that’s about all I could do and even though the browser part of the app was refreshing, the rest of the screen (i.e. all of the controls) were stuck in UI limbo. Here’s a shot of all the other windows in my system overlapping and making browsing rather difficult, as I frantically clicked to get back some semblance of a browser:
No, that’s just not right. What really peeved me off was that it would hang no matter what option I tried (including responding to dialogs that it created for me). I even tried to un-install any extra toolbars I had (Developer toolbar and Google) but it still wasn’t a happy camper, and neither was I. In fact, just launching it and IE decided to max out my CPU for the rest of it’s pathetic existence:
Nice.
Okay, that was it. This waste of bytes had to go and go fast. I hunted it down in the Add/Remove Programs dialog (how nice of MS to put it somewhere easy to find) and immediately nuked it. Of course even on it’s final throes of death it still mocked me:
No, I did not install anything after IE. I just installed you! WTF? Apparently IE is not only impossible to run on my system but also brain dead because it *thinks* something has happened since it was installed a mere few minutes ago. I guarantee you it isn’t true so wherever it’s getting this information from, it’s wrong. Yes, Atlas is installed on my system but that was done weeks ago. Stupid program.
Once I finally got IE6 back and it was running I wanted to check on something. An interesting stat was IE vs FireFox running my static HTML page. Here they are side by side with my single static HTML page loaded:
The first number is the number of threads. IE has 12 running, FF has 8. Whatever, no biggie deal there. Then comes USER objects and GDI objects (I have these available in Process Explorer to track resource leaks in WinForm projects). IE has over triple the number of USER objects FF has and over double GDI objects (even Photoshop doesn’t have this many GDI objects!). Okay, I guess people don’t care but you would think the numbers would be small as the program doesn’t do much. I mean a browser is just a single window, a menu, a few buttons, a toolbar and a status bar. To me, that’s a lot of objects for such a simple UI but then I prefer to be a minimalist when it comes to these things. The last number is the memory footprint so no surprise there, IE gobbles up twice as much memory as FF and it’s not really doing anything yet.
I’ve bitched about this in the past and this is a new build of my OS so I can’t blame it on that. Lots of people run it successfully so the cheese stands alone here. I can only bring it down to brass tacks: my machine doesn’t like IE 7. Plain and simple. Of course the problem is that now, someday, I’m going to have to run this puppy as my default browser. Yes, by then it’ll be released and not a beta but this is B3 for petes sake. Shouldn’t it work in the majority of scenarios by now? Is my machine that odd and different. Is there some terrible tool running that IE just flat out refuses to work?
The mind boggles.
-
A rolling blog gathers a lot of knowledge
Most times I see myself as a giant katamari and the internet is all stuff I can just roll over, pick up, and make my brain grow bigger. This blog isn’t one of those things, but the article linked here is.
Michael Feathers has a great article on what he calls “sensing variables”, variables introduced into a long piece of code that can be used to verify refactorings. In a nutshell (hey, how did I get into this nutshell?) it captures the state of something for recovery later and allows you to introduce refactorings (say rearranging code) with the comfort that things haven’t changed without your intent.
Very cool as it’s always difficult to find a way to know the state of things when you’re trying to program for a stateless environment. The other thing is that while unit test rock my world, I find it very difficult sometimes to get them to poke and prod into classes to see what’s going on. I only rely on public properties and methods (I don’t believe in the reflection gig trying to test private accessors) and try to stay away from “out” parameters as often as I can (I really don’t like knowing a variable goes in one way and out another without knowing what’s going on in between) so I can only test what’s returned. Often it’s hard to write a test that really gets at the heart of what’s going on, without exposing everything and blowing off basic tenets of OO like encapsulation.
Anyways, great article and simple technique for this kind of thing. It’s easy to implement and works well so feel free to check it out here.