|
LATEST POSTS
Friday, February 26, 2010
More and more often these days, I’ve been having a crisis of faith conscience when it comes to TDD, or rather, interaction testing in general. It happens more so on projects where I’m the only developer or there is a very small team of strong developers. And with the latest project being in GWT, where you are practically beaten down by all the guidance espousing separation of concerns, it’s even more pronounced. Consider a screen that display a list of pet names for your truck. When you click the “add” button, it displays a dialogue box with a blank form. If you’re using an MVP pattern, this is likely what would happen when the page is loaded, from a testing point of view: - Load a list of data from the datastore
- When it’s retrieved, send the list to the view
- Add a click handler to the “add” button that does something
In this case, I’m using gwt-dispatch, which is a command pattern implementation for GWT. So the first point is executed by creating a command and dispatching it like so: dispatcher.execute( new GetPetNamesCommand(), new GetPetNamesCallback( ));
The GetPetNamesCallback class looks like so:
public class GetPetNamesCallback extends AsyncCallback<GetPetNamesResult>
{
@Override
public void callback( GetPetNamesResult result ) {
display.showList( result.getPetNames( ) );
}
}
And the code to add the click handler when the page is loaded:
display.getAddNewLink().addClickHandler(new AddNewClickHandler( ));
(This is a passive view, I think, so the presenter handles all the event wire-up.)
Going to the testing side of things, I have a test context class called When_initializing_pet_names_list_presenter and within it, I have these tests:
- should_request_data_from_data_store
- should_wire_up_add_new_button
In each of these tests, I dutifully go about my business mocking out a view and a dispatcher and creating a presenter. I set up expectations and the real meat of the test is in this line (using Mockito syntax):
verify( dispatcher ).execute( isA( GetPetNamesCommand.class ), isA( GetPetNamesCallback.class ) );
That is, we verify that we executed the dispatcher with appropriate arguments. There’s a similar test for the second method and in another class, When_pet_names_have_been_retrieved, there’s a method that tests the GetPetNamesCallback class to ensure, also dutifully, that display.showList was called.
When creating this code, I did it the other way around, writing the tests first and fleshing out the details. That makes no never mind here. The end result is the same no matter which way you go about it.
Where my crisis of conscience comes in is when I review the code I’ve written based on these tests. This screen isn’t very complicated. If I were to bang out the code for it without tests, I can’t imagine doing it any other way.
But someone else could, of course. And maybe they aren’t as disciplined as I am about keeping concerns separated. Fair enough. At the moment, they aren’t working on my project though. It’s just me and it will be me for the foreseeable future. Besides which, I’ve done enough thinking on how smart or dumb the next guy is going to be.
Furthermore, these tests feel too granular. Like they’re too focused on implementation and that they’d break at the slightest refactoring. Like say, if we switched to an architecture where the view wires up its click handler and raises events. Now I have to go and fix all these tests.
Maybe that’s short-term thinking and it will lead to a lapse in judgment one day where I write untestable code to get something out the door.
Maybe that’s true but maybe it’s not. I don’t think I’m advocating against interaction testing in any case. Just wonder what value it holds once you’ve been doing it long enough that you can’t write untestable code if you tried, even if there aren’t any actual tests.
In any case, this is just interaction testing. There are plenty of cases where unit testing can be useful in verifying complex business rules (like whether it’s okay to have duplicate pet names for both your truck and your sister-wife).
What I see more value in is a higher level integration tests. Having recently taking Selenium for a spell, it seems more useful. What happens when the user lands on the “Pet Names List” page? Why, it should have this text showing in its HTML somewhere. What about when you click the “add” button? Well, then the page should include a form in it.
In this case, if anything changes in the bowels of the application, these tests remain untouched and, more importantly, still very relevant. If you have to change these tests, it’s as the result of a user interaction change and I think that warrants a change in your tests more than which flavour of MVP you decide to use.
This isn’t really a position I’ll defend to the death, or even to the slightly injured. As it is, I’m not altogether happy about how I’ve explained my position. Should probably let it sit for a bit but I don’t have that kind of patience. In any case, more and more when I run my test suite and see everything running green, I feel a little bit Pavlovian.
Kyle the Trained
Wednesday, February 13, 2008
Many thanks to honorary hillbilly, Aaron Jensen for both nudging me into helping out when I lamented on the AutoMockingContainer/ASP.NET MVC combination and for helping me through my first official patch submission to an open source project. He was very gracious with my lapse in matching formatting and spacing standards. The end result is that you (and, more importantly, I) can now use the AutoMockingContainer to create controllers in ASP.NET MVC. Whether or not you think that's a good thing, I'm feeling mighty proud of myself either way. The issue previously was that when the container tried to create the controller, the default behaviour was to create dynamic mocks for any properties it could. Controllers have two properties, ControllerContext and TempData, that can't be mocked without help, primarily because they don't have default constructors. The fix was trivial in the way that Ayende would say something was trivial. That is, it wasn't trivial for me at all but it sure looks like it was in the finished code. By default, Windsor will try to instantiate an object using the DefaultComponentActivator. One of the methods it calls is SetupProperties which, as one might expect, loops through the object's properties and resolves any that it can. And the AutoMockingContainer assumes that it can resolve any and all comers without any outside help. The fix was to create a custom component activator that derives from DefaultComponentActivator but that overrides SetupProperties so that it does nothing: public class AutoMockingComponentActivator : DefaultComponentActivator
{
public AutoMockingComponentActivator(ComponentModel model, IKernel kernel, ComponentInstanceDelegate onCreation,
ComponentInstanceDelegate onDestruction)
: base(model, kernel, onCreation, onDestruction)
{
}
protected override void SetUpProperties(object instance, CreationContext context)
{
}
}
Then, thanks to Castle's extensibility, that activator can be wired into the Kernel in the AutoMockingContainer (bolded code is new):
public void Initialize()
{
Kernel.AddSubSystem(SubSystemConstants.NamingKey,new AutoMockingNamingSubSystem(this));
Kernel.AddFacility("AutoMockingFacility", new AutoMockingFacility(this));
Kernel.ComponentModelCreated += Kernel_ComponentModelCreated;
}
void Kernel_ComponentModelCreated(Castle.Core.ComponentModel model)
{
model.CustomComponentActivator = typeof(AutoMockingComponentActivator);
}
Retrofit some tests (oops! I mean I wrote those first) and boom-bada-billy-bing, I'm off to the races. At the expense of about four billable hours.
Kyle the Activated
Tuesday, January 15, 2008
Floridians beware! The Hillbilly descends on your fair state in the next few weeks: FlaDotNet - West Palm Beach, January 22, 2008 6:30pm Introducing MVC in .NET OnLoad, OnPreRender, OnRender, OnInit. Traditional web forms development makes it sound like you're some crazed Santa Claus born from the imagination of Tim Burton. But it doesn't have to be that way. In this session, Kyle will free you from the shackles of the ASP.NET event model and show you a shiny, happy world. A world where web developers write application code, not plumbing. A world where "ViewState" is the eighth word you can't say on television. Such a world does exist. And Kyle can take you there with the help of Microsoft's shiny, new implementation of Model-View-Controller. And you will see a place where the air is clean, easily maintained, and testable. South Florida Code Camp - Miramar, February 2, 2008 7:30am - 5:45pm Not one, not two, but THREE doses of the Hillbilly: Introduction to TDD, Mocking, and Dependency Injection I know, I know, why would we need TDD? It’s not like requirements ever change, right? Users are always crystal clear when describing them and once they’re written down, everyone *knows* they’re set in stone. So this session will be more of an academic exercise for those purely hypothetical situations where, say, your client claims he “forgot” about a key feature one week before delivery and that it “absolutely has to” be in the application and, wouldn’t you know it, it’s going to touch every single layer of the application. Crazy talk, maybe. But indulge a budding theorist and join me for a demonstration of TDD in action and how it allows you to wholesale replace entire sections of your application with confidence. Along the way, we’ll discuss how you can test components entirely in isolation from each other through the power of mocking and dependency injection. ASP.NET MVC and AJAX No abstract for this one because I was originally going to re-hash my Introducing MVC in .NET presentation before David Hayden decided he wanted me to work for my money. But unlike Justice Gray, I'm folding like a new shirt and tweaking my presentation. Brownfield Software Development: Inheriting Code What a mess this code is! If only the mucky-mucks would let me rewrite it from scratch. (I’d *never* make the same mistakes the previous team did.) But alas, I’m stuck adding features and making bug fixes to this convoluted codebase that has no unit tests, has no CI process, and is not even completely in the source code repository. And I’m scared to make changes because the first time I tried, some users couldn’t log in, the app crashed for others, and for one unfortunate person, a big gaping hole opened up under his desk when he tried to enter an order. In this session, we’ll take an existing application and move it to a state where we can make changes with confidence. We’ll implement a continuous integration process and start you on the addictive road to refactoring. And once you get your first hit, you’ll never start a greenfield application from scratch again. I'm rather excited about the last one even if it seems the least sexy of the topics. I think it has the most potential to help people in the NOW instead of some hypothetical future. Kyle the Re-presented
Thursday, January 10, 2008
I resolved to pace myself a little better in '08 on this blog. But then, I also resolved not to keep any of my resolutions. Mostly because I like the circular reference. I'm delving a little more deeply into the ASP.NET MVC framework these days in preparation for a couple of upcoming presentations. And I've really liked using Jacob Llewallen's AutoMockingContainer in other projects so naturally, I wanted to take that into MVC and use it to create my controllers: controller = container.Create<SongControllerTester>( mocks );
(NOTE: I'm using the Test Specific Subclasses espoused in Phil Haack's post on the subject but it doesn't matter if you use the subclasses or the actual base controllers.)
This fails with the error: MissingMethodException: Can't find a constructor with matching arguments.
Luckily, the AutoMockingContainer's source is available. Even more luckily, so is Windsor's. So after some digging, here is what appears to be happening:
When the AutoMockingContainer attempts to create SongControllerTester class, it adds the class's type to its own internal Windsor container. It then attempts to resolve that type, thus retrieving an instance of the object from the container.
During that retrieval process, Windsor loops through the object's properties and tries to resolve the ones that it can (for the properties that are objects). Typically, they can't be resolved unless they've also been added to the container. But the cool thing about the AutoMockingContainer is that it wires itself into this process and will create dynamic mocks for the objects that represent these properties. However, these objects must have a default constructor.
The MVC Controller object has a ControllerContext property and the ControllerContext object does not have a default constructor. Hence, no dynamic mock for it.
And unfortunately, I haven't found a way around this yet. Marking ControllerContext as non-dynamic, stubbed, or non-mocked won't work, nor am I able to get the AutoMockingContainer to create an instance of ControllerContext (there is no setter on its Controller property).
So in lieu of the amount of time I've already spent looking into this, I'll have to switch to more traditional methods of creating my controllers and mocking their dependencies.
Kyle the Mockable
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent
my employer's view in any way.
Copyright © 2010 Kyle Baley. All rights reserved.
|
|
|
CATEGORIES
|
|
LATEST POSTS
POPULAR POSTS
|
|
ARCHIVE
| March, 2010 (1) |
| February, 2010 (8) |
| January, 2010 (2) |
| December, 2009 (3) |
| November, 2009 (2) |
| September, 2009 (5) |
| August, 2009 (4) |
| July, 2009 (2) |
| June, 2009 (5) |
| May, 2009 (6) |
| April, 2009 (5) |
| March, 2009 (6) |
| February, 2009 (2) |
| January, 2009 (6) |
| December, 2008 (5) |
| November, 2008 (2) |
| October, 2008 (5) |
| September, 2008 (9) |
| August, 2008 (5) |
| July, 2008 (7) |
| June, 2008 (6) |
| May, 2008 (11) |
| April, 2008 (13) |
| March, 2008 (13) |
| February, 2008 (12) |
| January, 2008 (19) |
| December, 2007 (16) |
| November, 2007 (8) |
| October, 2007 (23) |
| September, 2007 (15) |
| August, 2007 (8) |
| July, 2007 (6) |
| June, 2007 (11) |
| May, 2007 (19) |
| April, 2007 (14) |
| March, 2007 (3) |
| February, 2007 (4) |
| January, 2007 (7) |
| December, 2006 (5) |
| November, 2006 (9) |
| October, 2006 (11) |
| September, 2006 (14) |
| August, 2006 (11) |
| July, 2006 (15) |
| June, 2006 (8) |
| May, 2006 (10) |
| April, 2006 (12) |
| March, 2006 (3) |
| February, 2006 (7) |
|
|
|