|
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
Monday, October 20, 2008
I'm preparing my presentations for DevTeach and have come to realize that there is a price to pay for creativity. Here are the abstracts for three of my presentations. Guerrilla Refactoring Greetings, comrades. Welcome to the resistance! Our quarry today is a shamefully designed application that grows fat with duplication and crusty with hard-codedness. It feeds on itself behind a shield of corporate deadlines, each one stricter than the last. The cowardly code mocks us with cries of "We don't have time! We don't have time!" while features are grafted onto the application haphazardly with no thought to future reform! But fear not! We have studied our enemy and have altered our attack plan accordingly. No more frontal assaults. We must liberate the code in focused skirmishes from its bourgeois oppressors! Patiently, we shall advance, tightening our grip in an ever-widening net of unit tests as we hunt down bugs like the dogs they are! So join me, brethren and sistren! With our allies, the Design Pattern Legionnaires and the Dependency Liberation Front, we will train you to take your rightful place in the movement toward a better world, where features are not divided into tasks that are meted out to the "database gal" and the "guy that does the UI because he has PhotoShop installed". Rather, they will be fully and utterly DEMOLISHED by fully armed, multi-functional, and domain-inspired programmers full of KNOWLEDGE AND FURY!!!1!!! Viva La Revolucion! (adapted, of course, from my original post on the subject) Implementing a Brownfield Ecosystem: A Cultural Extravaganza So you missed Folklorama again, eh? Of course, you did. It's in Winnipeg. Well, fear not, coders! In this session, we'll throw so much culture at you, you'll think you're back in high school biology. A very important aspect of Brownfield applications is shifting the culture of your team. This starts with your project's ecosystem. Many developers in Brownfield applications don't even realize how many hoops they jump through during the course of a day to do simple things like checking in code and building the application. In this session, we'll implement a full-fledged ecosystem for a Brownfield application. Working from the perspective of a single developer in a team, we'll cover topics such as the structure of your version control system, your check-in process, automated builds, and continuous integration. In the process, we'll talk about common pain points and common areas of friction that we can overcome with a few simple tool choices and a couple of mindset tweaks. I'll be getting by with a little help from my friends as we simulate a team environment using well-placed and impeccably groomed plants in the audience who will act as other developers in the team, some working with me, others not so much. Adding automated tests to an existing codebase It may seem like heresy but there are actual real-live applications out there in the world that don't have unit tests! No, really! And I know it sounds crazy, but you could be brought on to one of these applications and be tasked with adding automated testing to them. Rare as these circumstances may be, it'll help to be prepared for them. Because it won't be as fun as you are probably imagining right now. For example, how do you automate a test for a web form that connects to a database, displays a form, then posts that data back to the database? In this session, we'll look at techniques for integrating tests into an existing codebase that has never had them before. When do you write tests for existing code? How do you test a mammoth method/class? How do you know if you aren't breaking anything else? For answers to all these questions and more, read Michael Feathers' Working Effectively with Legacy Code attend this session.
You see my dilemma. I wrote these in a fit of imagination without stopping to wonder how I would differentiate one from the other. For example, how do I talk about refactoring without mentioning automated tests? How do I implement a brownfield ecosystem without doing same? In the end, I believe there will be overlap among the presentations. Indeed, I will probably use the same sample application for all three. I've justified it in my head by saying that the overlap is probably a good thing to help people see the interaction 'twixt the various concepts. Plus, it means I can focus on a single element in, say, the refactoring presentation and leave some of the details to the automated tests presentation. In any case, here is some clarification on the goal of each presentation: Guerrilla Refactoring: How can you refactor an application for maintainability when there is no explicit buy-in from the PM and/or the business (and/or the rest of the development team)? Implementing a Brownfield Ecosystem: A walk-through on adding source control, CI, and automated testing to an existing application. I'm planning to use plants in the audience to simulate a team environment to demonstrate what happens to you personally when others don't play by the rules. The "cultural" part comes because this is very much an exercise in changing your team's development culture. It was originally intended as a practical follow-up to Donald's Parachuting into a Brownfield Application presentation but I notice he's not doing it this time around. Adding automated tests to an existing codebase: Here, we'll dive deeper into automated testing for an existing app. It will be sort of a continuation of the brownfield presentation which will add the testing framework but not go any further. Admittedly, it will borrow heavily from Feathers' book but I see that as a good thing. For completeness, the fourth session I'm doing is on TDD for MVC applications. This one is more disparate than the others and should be a fun one as well, even if the abstract for it is the most boring of the four. Kyle the Synergistic
Monday, July 28, 2008
A couple of years ago, I had lunch with JP. It was before TDD had clicked for me and I was still very much a skeptic. As always, JP was passionate but not forceful in his explanation of it but by the end of the lunch, I still wasn't sold. So much so that I wrote a nice little write-up that, upon re-reading, is akin to looking at pictures of my choice of clothing in the 80s. (What can I say? Corduroy worked for me.) So it is with some bemusement that I reflect on almost the exact same conversation I had with another young developer three days ago, except that I played the part of JP and he played the part of the skeptic. I recognized each and every argument he posed because I made them myself at one point: It takes too long. What's the difference whether I test first or test after? There's too much code. It's safe and warm and fuzzy in my world and I won't have you telling me otherwise. The argument wasn't all that heated because we're both Canadian. But in the end, the only real argument that stuck with him is the same one that stuck with me originally: it works for me. I've been doing it for a while now and I like the results I get with it. Maybe I can't quantify the specific time and/or cost savings over time but I can tell you, it "feels" right. I like looking at the software I build with it. I like going back to it to add features. There's a much smaller learning curve when reviewing it after a few months. Maybe I can get that same feeling just by adopting a few decent development practices rather than a full-on design methodology. That may be so, though it seems to me that TDD allows you to adopt such practices a lot more naturally. So that you aren't forcing yourself to learn them. Rather, you learn TDD and other design principles seem that much clearer. Of course, TDD isn't exactly a walk in the park itself and it sure does help if you can learn alongside someone who has done it. And even now, I'd rank myself only about a 4 or 5 out of 10 on the "TDD Afficianado" scale (though if a recruiter asks, the answer is always 7). Past the book-learning phase but still not practised enough that I could, say, teach a course on it. Which I think is part of the barrier to entry. It's hard (for me, at least) to learn on one's own without some real-world work backing it up when the class/seminar is over. Often, there is a sense of hopelessness anyway at having your eyes opened to a new world, then returning to your regular job at Niagra Inc. At one point, I had some vague plan to do an online Skype thing with whomever wanted to join to talk about something like this. Not to teach but to share my findings and see if anyone else would either learn from it or enhance my knowledge of it. I do hope I have the wherewithal to follow through on it because I do so love the idea. Kind of a real-time version of Derik's www.dimecasts.net site (which continues to be awesome if you haven't checked it out). Anyway, I'm not entirely sure where I'm going here. Just liked the symmetry of the two conversations, I guess but I'll stop now. Let me know if you'd be interested in a Skype coding session, possibly sometime in September when I've fled this foreign corporate cowboy world again. Kyle the Circuitous
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, December 20, 2007
using MbUnit.Framework;
using Rhino.Mocks;
namespace Suvius.Applications.LoveDoctor.Tests
{
public class CritterFixationFixture
{
private ISpeciesSelectorPresenterView view;
private MockRepository mocks;
private SpeciesSelectorPresenter presenter;
[ SetUp ]
public void SetUp( )
{
mocks = new MockRepository( );
view = mocks.DynamicMock<ISpeciesSelectorPresenterView>( );
presenter = new SpeciesSelectorPresenter( view );
}
[ Test ]
public void Should_initialize_view_with_list_of_projects( )
{
using ( mocks.Record( ) )
{
view.LoadCritters( );
}
using ( mocks.Playback( ) )
{
presenter.InitializeView( );
}
}
}
public class SpeciesSelectorPresenter
{
private readonly ISpeciesSelectorPresenterView _view;
public SpeciesSelectorPresenter( ISpeciesSelectorPresenterView view )
{
_view = view;
}
public void InitializeView()
{
}
}
public interface ISpeciesSelectorPresenterView
{
void LoadCritters( );
}
}
How's THAT for throwing you into the fray? I can see all of you now going, "whoa, there, Coding Hillbilly! I ain't e'en got me a cuppa joe yet, buddy." Sorry, folks, that's how I roll. I think slow and act fast. You gotta keep up.
The test above sets up an expectation that LoadCritters will be called on the View when we called Presenter.InitializeView. In actual fact, this isn't being done. So here's the poser: why does this test pass?
All right, I'll tell ya because I like your look. And I don't want to risk any of you actually cutting and pasting that into an IDE and coming up with some cut 'n paste error I made. Plus I gave you a hint in the post title.
The test isn't actually passing. When it hits view.LoadCritters, it fails silently and kicks out of the test completely. The reason bein' SetUp isn't being called. The reason for *that* bein' I forgot the [ TestFixture ] attribute on the class itself.
Now as to the question on why the test doesn't fail with a null reference exception: That's an easy answer. I have no idea. If you add the [ TestFixture ] attribute and comment out the code that creates the view, it does fail with the expected exception.
So I'll leave that to my loyal and, I pray, generous readers.
Kyle the Deferred
Friday, December 14, 2007
Here's a little sidebar that I bet you didn't know. Actually, that was probably more accurate at my old site where my readership measured on a more, shall we say, intimate, scale. Anyway, I recently converted a project from NUnit to MbUnit just because I felt like it. (Oh, I'm kidding. I have reasons. Boring but valid ones. So don't sic the alt.net police on me.) In the past, that has involved adding the assembly reference for MbUnit, then performing a global search and replace of the appropriate using statement. Perhaps cleaning up the different Assert syntax but in general, it's a pretty painless process. This time, I got null reference errors in all tests in several class and to make a long story short (don't get used to it, I don't do it often), I traced it to the SetUp method:
[ SetUp ]
protected void SetUp( )
{
// Do stuff
}
Notice that it is protected, which apparently makes it all but invisible to MbUnit. I switched it to public and my world is fully tested again.
Initially, I thought it might be worthy of a bug report. But here is kind of what I typed out:
Hey dudes! You got yerself a fine 'n dandy little product here. But I got meself some tests here someone was nice enough to write out for me in NUnit and when I converted them to MbUnit, they done gone failed on me 'cause the SetUp method is protected even though... the tests themselves are...ummm... y'know...public...
Here's where I started to feel a little silly and considered that maybe the MbUnit folks knew what they're doing when they wrote the product (although I wouldn't mind it handling nullable types a little differently).
So I guess I don't really make this long story all that shorter. The Hillbilly is nothing if not contradictory.
Kyle the Taciturn
Friday, November 16, 2007
Today, we'll talk on the virtues of restraint, cleverly disguised as an exercise in TDD. Here's the bug that I snagged during the stand-up: When I try to delete a Mechanical Doohicky*, I get an unhandled exception. Careful and value-added analysis led to the following code that checks to see if the Product is being used by another object: public bool isPartUsed( IMechanicalDoohicky part )
{
foreach ( Heap heap in heaps )
{
if ( heap.Engine.Parts.Contains( part ) )
{
return true;
}
}
return false;
}
The issue should be readily apparent. Namely, heap.Engine might be null.
My first reaction to this was: easy fix. Check heap.Engine for null and spend the rest of the hour playing Whack-A-Mole (not the version you're thinking of).
But nay, coders, let's slow down and see if there's a way we can drag this billable time out a bit in a justifiable way**.
What we're going to do first is write a test to expose this little bug. For those of you who obsess over analogies, think of it as...oh, I dunno, fixing a leak in a dam. You don't want to shut the water off, then have to turn the water on to make sure the leak is fixed. OK, maybe I'll pass on the analogies. Hillbillies are just too real. But I'm writing the test anyway because it's trendy.
Over to our test harness, we add a test that will fail specifically because of this bug: private bool Should_not_fail_when_checking_if_part_is_used_in_heaps_with_no_engine( )
{
// ... set up part and heaps with one that has no engine ...
heaps.isPartUsed( part );
}
Back to our code and we update it to fix the bug: private bool isPartUsed( IMechanicalDoohicky part )
{
foreach ( Heap heap in heaps )
{
Engine engine = heap.Engine if ( engine != null )
{
if ( engine.Parts.Contains( part ) )
{
return true;
}
}
}
return false;
}
So it may seem like a lot of extra work to fix a seemingly simple bug, especially because of the ominous way I commented out all the set up code in the test. But the bug is fixed and I can now prove it without having to launch the UI and going through the pain of setting up the conditions that led to it. And if anyone mucks with the implementation of Engine, we can be reasonably confident this particular bug won't get re-introduced. Plus the test acts as a sort of documentation of the bug that we've fixed.
Final note: There is a reason I stored heap.Engine in a temporary variable other than avoiding duplication.
Kyle the Exposed
* Domain objects have been renamed to protect the innocent. I.E. me
** My lawyer has advised me that, due to my increased readership in the U.S., I should add that I am officially and legally kidding.
Wednesday, September 12, 2007
The hillbilly is feeling brief so I'll cut to the chase. The following test succeeds in NUnit but fails in MbUnit: decimal? nullableDecimal = 100m * 3.281m; Assert.AreEqual( 328.1m, nullableDecimal );The error in MbUnit: Equal assertion failed: [[328.1]]!=[[328.100]] The way to get this to pass in MbUnit (and still in NUnit) is: decimal? nullableDecimal = 100m * 3.281m; Assert.AreEqual( 328.1m, nullableDecimal.Value );If you write out the value of nullableDecimal to the console you will indeed see that it is 328.100 so I suppose technically, MbUnit is correct in claiming that the values aren't equal, although it sure looks like it's comparing string representations rather than actual values. So in this case, my opinion is that NUnit handles things better because it allows you to use the variable in a more natural way. I.E. It doesn't shove in your face the fact that the variable is nullable. Caveat: Don't take this as an endorsement for NUnit. The hillbilly still prefers MbUnit because he likes to pretend it stands for "Mountain boy unit". Kyle the Nullable
Wednesday, September 05, 2007
Quick nugget o' information today 'cause I'm still on the clock.
MbUnit has a couple of command line parameters I've been using lately: filter-namespace and filter-type. Both can be useful when you have several hundred tests and don't want to run all of them when you're working on one area of the app.
The informational nugget: when you use either of these parameters, it will include tests marked with the Explicit attribute in your test run. Assuming, of course, those explicit tests are included in the filter.
Kyle the Filtered
Tuesday, July 31, 2007
Just finished Jean-Paul Boodhoo's Nothin' But .NET course and everything you've heard about it is true. This was, I believe, the first course where I was paying rapt attention and still got lost. It was a fleeting moment but it was glorious! It was like the restaurant scene in The Meaning of Life where my brain is Mr. Creosote and JP is the Maitre d' trying to cram one more wafer of knowledge into it.
There are at least a dozen blog potential posts scattered throughout the week but I'll limit my own coverage to one since I believe Master Simser will probably cover the actual content better than I could. So I'll let him deal with any confidentiality conflicts with JP and instead, I'll post more personal highlights
Day One
JP covers factory methods, state-based vs. interaction-based tests, MbUnit vs. NUnit, guard clauses, and delegates. It's 8:35am.
At 6pm, we lose a couple of people who favour family over brain-scrambling. JP is adamant that it's cool but there's rejection in his eyes and for the remaining three hours, he covers only thirty-six topics, down from his usual rate of fifty-nine per hour.
At the end of the day (a respectable 7:15ish), I decide that what JP really needs is a soundtrack. I vow to remedy this tomorrow.
Day Two
8:30AM - I initiate the course soundtrack with Also Sprach Zarathustra.
1:00PM - Afternoon musical inspiration: Kids In The Hall Theme
6:15PM - I concede defeat and stop trying to keep up on my own laptop.
7:03PM - He invites me up to implement a test. I GOT TO TOUCH JP'S KEYBOARD! See entry for day five.
9:15PM - JP claims he's raring to keep going but prefixes this statement with a half-hidden yawn. We let him off the hook...this time.
Day Three
Today's musical selections: Feelin' Groovy (take note of the opening lyrics), Delilah (by request), and Linus & Lucy
8:30AM - JP claims not to have gotten any sleep last night. Despite this, I switch from tea to a double double with a shot of espresso at Starbucks to try to get my brain vibrating on the same frequency. Also, Jolt. JP orders water.
11:00AM - We take a break while he rants about properties. Or methods or operators or something. Frankly, I'm just glad he's distracted enough that I can catch up.
7:15PM - He laments that we've missed a couple of topics so he refactors the app we're working on so quickly, we travel back in time a few hours so that we can get back on track.
2:35PM - Maintainability trumps all other abilities. It takes me a while to determine he is talking about code and not children.
6:45PM - At one point in my life, I was able to play Flight of the Bumblebee on the piano fairly respectably. I may as well have been playing chopsticks compared to what it takes to keep up on my laptop. Interestingly, Bumblebee is probably an accurate description of the colour of JP's laptop.
8:15PM - It occurs to me that ReSharper has so many keyboard shortcuts, you could conceivably finish your application by mashing on your keyboard at random.
Day Four
Morning inspiration: He Ain't Heavy (He's My Brother) (by request)
9:30AM - We finish implementing the dependencies on the domain. JP claims, "Now we can *really* start moving"
1:00PM - He starts playing his own music to code to. *NOW* we're talking. There is a minor quibble about the selection but they are quickly smacked down (oddly enough, with a ReSharper shortcut).
3:35PM - I thought the music would lull him a little but he actually appears to be speeding up. He no longer moves his hands to type. Instead his hands hover over the keyboard and the appropriate keys press themselves out of respect. Or, more likely, fear.
6:55PM - We settle in for a long night because JP claims he has to catch a flight tomorrow night at 7:30.
10:45PM - We wrap up and I check in with the missus back home in the Bahamas who accuses me of neglecting my daughter, cheating on her, and eating too much cholesterol-laden pizza. Take note, prospective students, Nothin' But .NET is bad for the home life. Even if half your family is two countries away.
Day Five
9:45AM - JP's laptop has just undergone some minor emergency surgery for the last hour and a bit but steadfastly refuses to boot up. We hold a small service and JP performs a rousing rendition of The Rose with the rest of the class backing him up in five-part harmony. The Bumblebee is down and I volunteer my laptop as a surrogate for the day. It is promptly pimped out with "only the minimum [I'll] need to work effectively". I'm left with 2Gb of hard drive space and much software that will expire in thirty days.
1:25PM - Now I get it! He's pre-recorded everything he's done and is playing back at four times the normal speed and only *pretending* to type. Don't worry JP, you're secret is safe with the Hillbilly.
5:30PM - "Oh yeah, I cancelled my flight tonight. Is everyone okay to stay a little longer?" I'm too scared to leave as every single thing he covers feels like my entire career depends on my knowing it.
9:00PM - I have to physically pry JP off my laptop. He breaks free of my grip but amid cries of "Stop him! Before he refactors again!" we manage to subdue him with a Bumblebee to the left temple.
I jest, of course (which I mention only because I've name-dropped JP so much that I'm afraid some humourless Googler will come hunting me down). The course was a delight as my dear grandpappy would say if he had all his teeth and could speak (and were still alive). Especially give my recent odyssey into self-improvement, career-wise at least. Having tried self-study for the last few months on many of the topics, it was great to have them explained in a structured, albeit rapid-fire manner and to have a context for the many patterns discussed as well as some nuggets about how some of them fare in the real-world. And there is absolutely no denying JP's passion for what he does which in and of itself elevates this above many similar courses.
The hard part now is going to be reining myself in. Not since the three-day recap of the Hillbilly's family tree has my brain been so abuzz with possible avenues of opportunity. It will be a case of holding a hammer and trying not to look at everything like it was a nail. We were hit with so much so fast, there is an overwhelming urge to apply as much as we can as soon as we can so the knowledge does come dripping out of our ears.
And in that respect, I'm planning to spend some quality time with the resulting code over the coming weeks, adding remaining functionality and refactoring existing stories. Perhaps integrating NHibernate or Windsor just to say I did.
But I'm not looking forward to all the cash I gotta start laying out when all this trial software expires....
Kyle the Supersaturated
Friday, June 01, 2007
Lots of talk on the new Live Writer. I'm just glad I can finally use the Ctrl+RightArrow key combination again the way I'm used to. The real topic of the day is repositories. In my last post, I raved about ActiveRecord but in that little mini-project, I didn't actually use any repositories. I'll admit that I didn't actually do strict TDD on that project, nor did I even write tests for it. The reason being...actually, I don't need to explain myself to you people. I have my reasons, lame as they may be. But that was important because I now understand what Jimmy Nilsson was talking about in his chapter on PI for repositories. I'm back in PD mode implementing tests against my repositories and it now occurs to me: I'm going to need two repositories. A fake one for the tests and a real one that hits the database and wraps my use of ActiveRecord/NHibernate. (Yes, Nilsson talks about this very explicitly. Hillbillies ain't what you might call "book learners".) To explain the issue (and thus, to pad my post) here is a sample test: [ Test ]
public void ShouldBeAbleToRetrieveJobFromRepository( )
{
int jobNumber = 42;
MockRepository mockery = new MockRepository( );
using ( mockery.Record( ) )
{
IJob fakeJob = mockery.DynamicMock<IJob>( );
SetupResult.For( fakeJob.JobNumber ).Return( jobNumber );
jobRepository.AddJob( fakeJob );
}
IJob job = jobRepository.GetJob( jobNumber );
Assert.AreEqual( job.JobNumber, jobNumber );
}
So I implement a basic (read: fake) JobRepository that adds a Job object to List<IJob> and in GetJob, I iterate through the list and find it. In the real repository, it'll use ActiveRecord to pull it from the database.
But now I have a couple of questions. First, what's the best way to wire in the fake repository for the tests and the real one for production? Is this where something like the Windsor container comes into play (he said without totally knowing what it does)? You reference one repository in your test web.config and another in the production app.config?
Secondly, and more importantly, do I even need this test? Why am I testing to see if I can pull objects from a fake repository? I don't even think an interaction-based test would work here since the underlying implementation will be different for each repository.
In the book, Nilsson adds a layer of abstraction so that the repository itself will be the same in both scenarios. That pushes things out of the repository but you still need a fake implementation of something, in this case the abstract layer. In that case, it makes sense to test the repositories since they are always "real" implementations. But then I'm not so sure about testing the abstraction layer.
So...ummm...help, I guess.
Kyle the Faked-Out
Tuesday, May 29, 2007
OK, so why hasn't anyone told me of the rockage that is ActiveRecord? I was halfway through writing my first NHibernate XML file when Oren himself pointed me to StoryVerse, whose code is easy enough to follow that I was able to pick up a practical use of ActiveRecord without too much hassle. Gives some context to the attribute vs. XML file debate which was passing me by. And I have to say, the thought of bypassing XML file management is pretty appealing for a hillbilly with the first signs of carpal tunnel. Data schema dependency be damned, that's some bit of magic when you can dump a bunch of attributes on a class and not write a single line of data access code. Which brings me to the real reason for this here hootenanny. One of my classes is called FiscalSystem. Its implementation is not worth explaining except for one of its properties. To wit: StateProvince a class which in turn has a property called Country. I.E. A FiscalSystem belongs to a State/Province which belongs to a Country. (I didn't do the reverse relationship because the app is small enough that I don't need it.) So to get a FiscalSystem's country: myFiscalSystem.State.Country.Name. Next comes the query: Show me all FiscalSystem objects that apply to a particular country. Here's the query I tried: ICriterion[] criteria = new ICriterion[]
{
Expression.Eq( "State.Country.Id", countryID )
};
FiscalSystem[] systems = ActiveRecordBase<FiscalSystem>. FindAll( criteria );
Side note: In reality, all I actually wanted on my FiscalSystem class were StateName and CountryName properties because I didn't need the semantics of a full State or Country object anywhere else. But I was unable to determine a way of having ActiveRecord look up information from more than one table (i.e. State and Country) for my FiscalSystem class. So I had to create objects for each of them.
No dice. The error is long gone now but it went something like "Could not resolve property name 'State.Country.Id'". Works if I query at the State level, but not at the Country level. Which leaves me to believe it has something to do with reflection.
Incidentally, you get a similar error if you try to bind a GridView to a list of FiscalSystem objects and use a BoundField that references State.Country.Id (or anything at the Country level). You get around that be using a TemplateField instead with Eval( "State.Country.Id" ).
My alternate solution was to dig into HQL which is amazing in its own right: SimpleQuery<FiscalSystem> query = new SimpleQuery<FiscalSystem>( @"
FROM FiscalSystem fs
WHERE fs.ParentState.ParentCountry.Id = ?
", countryID );
FiscalSystem[] fiscalSystems = (FiscalSystem[])ExecuteQuery( query );
Man, there is a price to be paid living out of the loop down in the caribbean all these years.
Kyle the Impressionable
Monday, May 07, 2007
Apologies in advance if I've misspelled aspnet_compiler as aspnet_compilter anywhere in this post. For the life of me, I can't figure out why my fingers always want to add that 't'. Interesting little quirk. Many moons ago, I installed Scott Guthrie et al's Web Application Project, probably because I needed to for dasBlog to compile. Since then, I've kind of ignored it in my project list. Today I absent-mindedly added one to my solution thinking it was a regular VS2005 Website and I received errors in the build step that used aspnet_compiler to precompile the app: Could not load type 'SampleWebPage.aspx.cs'. I set up debugging this already knowing in the back of my mind that it probably had something to do with the type of web project it was. My first clue was that when I built the web project in Visual Studio itself, it was compiled using csc: Whereas the output for a Web Site build is as follows: The other hint was the property pages. For a Web Application Project, you get property pages that look like they do for a class library or WinForms app: For a Web Site: Plus they're called different things in the right-click menu (Properties vs. Property Pages). Finally, there's the actual error message itself. Web Applications Projects, of course, use an explicit namespace, Web Sites do not. And the big ol' honkin' namespace declaration at the top of the class was a pretty solid indication that this ain't no VS2005 Web Site. So the choices were pretty obvious: modify the build to accommodate the Web Application Project or use a Web Site. I chose two as the path of least resistance since I had but one (incomplete) page in it. Kyle the Rebuilt
Friday, May 04, 2007
In the comments of my recent Rhino Mocks post, James Kovacs pointed me to this post from Oren on interaction-based testing. In it, Oren gives guidelines for using Rhino Mocks to create fake objects. Namely, use DynamicMock instead of CreateMock and use SetupResult instead of Expect or LastCall. So that said, here is my original test: [ Test ]
public void JobNumberShouldNotBeZeroAfterReconstitution( )
{
int jobNumber = 42;
fakeAJob( jobNumber );
IJob job = jobRepository.GetJob( jobNumber );
Assert.AreEqual( job.JobNumber, jobNumber );
}
private void fakeAJob( int jobNumber )
{
Job job = jobFactory.CreateJob( );
JobRepository.SetFieldWhenReconstitutingFromPersistence( job, "jobNumber", jobNumber );
jobRepository.AddJob( job );
}
This is following pretty closely with Jimmy Nilsson's Applying Domain-Driven Design, at least in the early chapters which I'm currently scouring, including the problem he mentions about creating a job with an order number without specifying it in the constructor. SetFieldWhenReconstitutingFromPersistence uses reflection to set the field which he knows is bad but I haven't got to that part of the book yet.
Instead, I thought I'd have a go at creating a fake object in Rhino Mocks, leading to the following revised code: [ Test ]
public void JobNumberShouldNotBeZeroAfterReconstitution( )
{
int jobNumber = 42;
MockRepository mockery = new MockRepository( );
IJob fakeJob = mockery.DynamicMock<IJob>( );
SetupResult.For( fakeJob.JobNumber ).Return( jobNumber );
jobRepository.AddJob( fakeJob );
IJob job = jobRepository.GetJob( jobNumber );
Assert.AreEqual( job.JobNumber, jobNumber );
}
Both tests pass which was nice to see. I'm throwing this out partially as an example of how to create a fake with Rhino Mocks (because I had trouble finding any explicit ones) but mostly to get confirmation that I'm still testing what I want to test, which I think I am.
Tuesday, May 01, 2007
Holy crap, this TDD thing is one heckuva mindf*%#@! The concept, I can dig. Write your tests, get it to compile, then get it to pass. I thought it mapped nicely to my normal coding practice, which is to start writing code making up objects and methods as you go, then fill in the blanks. Turns out my method is working against me. I'm in the midst of writing my tests and I can't even get to the end of the test before I start creating my objects. For example, I started writing the following test: [Test]
public void CanCreateJob( )
{
Job job = JobFactory.CreateJob( );
Assert.IsNotNull( job );
}
The problem is, as soon as I pressed enter on the first line of code in this method, I went barrelling through the Domain project creating the Job class. Before I even finished writing the test, let alone trying to compile and run it. And I shouldn't even *have* a Domain project yet.
So everything you've heard is true. It's what people used to call a "paradigm shift" back when the phrase had some meaning. It takes discipline and people will probably poo-poo it if it scares them too much (was that really less than a year ago?).
On that note, after reading more extensively on the subject, I now tend to agree with a friend of mine, a developer-who-doesn't-blog-but-should who is fairly senior but relatively new to TDD, and who said that TDD is at the place OO was ten or fifteen years ago. That is, it's not quite widely accepted but eventually it will be the de facto standard (in one form or another).
And hopefully by that time, all the little golden nuggets about it will be codified so that I don't have to highlight them in various blog posts/books.
Tuesday, May 01, 2007
Sweet Jayzus, I have to reveal my ignorance more often if this is going to be the result. That's a faster turnaround than at your average Bahamian bank check-in counter! So, James, I hate to mention it for fear of looking stupid but I'm having a little trouble writing this tiny piece of software to predict the stock market....
Saturday, April 28, 2007
There's a good chance I'll lose some credibility with this post and that many people will file it under "stating the obvious". But a wise man once told me, "There aren't any stupid questions, are there?" so I'll proceed assuming someone has had the same trouble I did when I was learning mock objects, and more specifically, Rhino Mocks (or RhinoMocks or Rhino.Mocks depending on who you talk to). My main conceptual stumbling block was in the use of the term "expectation". To explain, here is the introductory example from the Rhino Mocks documentation: 1 [Test]
2 public void SaveProjectAs_CanBeCanceled()
3 {
4 MockRepository mocks = new MocksRepository();
5 IProjectView projectView = (IProjectView)mocks.CreateMock(typeof(IProjectView));
6 Project prj = new Project("Example Project");
7 IProjectPresenter presenter = new ProjectPresenter(prj,projectView);
8 Expect.Call(projectView.Title).Return(prj.Name);
9 Expect.Call(projectView.Ask(question,answer)).Return(null);
10 mocks.ReplayAll();
11 Assert.IsFalse(presenter.SaveProjectAs());
12 mocks.VerifyAll();
13 }
(Note: The example is a little dated due to Rhino Mocks' new CreateMock<T> but that wasn't the confusing part. Nor was the typo in line 4. Read on, reader, read on.)
I read/scanned the surrounding text several times trying to determine where the magic was happening. Focussing on line 8, here was my interpretation:
- At some point during this test, there will be a call to projectView.Title. When that happens, I expect it's value to be identical to prj.Name. If it isn't, the test fails.
That is, I thought the expectation itself was doing the testing. Ya, ya, stop laughing. Sue me for assuming the English definition of "expect". (OK, so the expectation is on the .Call, not the .Return; I'm using Reverse Polish Notation, SOP in the Coding Hillbilly handbook.)
What line 8 *really* says is (to the best of my knowledge at least):
- At some point during the test, I expect a call will be made to projectView.Title. When that happens, return prj.Name as its value. If the call isn't made, the test fails.
The good part is, realizing this made ReplayAll and VerifyAll that much less confusing particularly around the Replay State that the documentation assumes I know anything about.
It took a while for me to understand the Replay State. As far as I can tell, MockRepository.ReplayAll turns on a switch. When that switch is on, all calls to methods/properties on the mock objects are recorded and compared to the expectations (set before ReplayAll is called). Then when VerifyAll is called, the switch is turned off and we check to see if all expectations have been met.
Put another way:
- Create your mock objects
- Tell the mock framework which properties/methods you expect will be called. Along with this, you can specify which values you want to return when those methods/properties are called.
- Start recording (see line 10). For all code executed after this, start comparing it with our expectations.
- Execute code to be tested
- Stop recording (see line 12). See which expectations have not been met and fail the test if any exist.
So again, the test will fail if you set an expectation that a method will be called and it isn't. I believe the reverse is true as well. That is, if a method is called and you *haven't* set an expectation on it, the test will fail also. But don't quote me on that.
I'm sure all of this is being explained much more clearly as I type this at the Calgary Code Camp at either James Kovacs' or Terry Thibodeau's sessions but I'm posting it anyway to justify the time spent ignoring my daughter.
Saturday, April 14, 2007
One side effect of TDD seems to be a moderate to gargantuan increase in the number of projects in one's solution. And with almost half of those being Test projects, it was with no small amount of gratitude that I tracked down Steven Rockarts' post on creating a project template with references to NUnit and Rhino.Mocks built-in. One of the things Steven did when creating his template is something that has become automatic for most of us. To wit, the first step in creating a class library is to delete Class1. So after creating my project template (which by the way, required me to go to Tools | Customize, then the Commands tab, File menu to add the Export Template... menu item to the IDE first), I got to thinking that maybe I'd create my own Class Library template which doesn't include Class1. But being a forgetful Hillbilly, I would inevitably forget to use my template the first couple of times and creating a class library would take longer as I'd have to go through the extra stages of deleting my new project, cursing, then creating a new one with my template. Better to modify the master. Which on my machine, is located at: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplatesCache\CSharp\Windows\1033\ClassLibrary.zip Once there, I did the following: - Deleted class1.cs
- Opened classlibrary.csproj in a text editor and deleted the <Compile> element that referenced Class1.cs in the last <ItemGroup> element
- Opened csclasslibrary.vstemplate in a text editor and deleted the <ProjectItem> that referenced Class1.cs in the <Project> element at the bottom
Now my class libraries are created free of Class1. In addition to this, I also went to C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ProjectTemplates\CSharp\Windows\1033 and made the same modifications to the contents of the ClassLibrary.zip file. I'm not 100% certain but I think this is necessary if you ever need to re-install the VS templates (using devenv.exe /installvstemplates), which I did have to do earlier today when my Web User Control template stopped appearing in my list. In an even more ideal world, creating a class library would automatically prompt you for the name of your first class, doing nothing if you leave it blank. Haven't found a feature like that in ReSharper but I can't imagine this isn't possible in some third-party tool somewhere.
Wednesday, April 11, 2007
I do believe the light has come on, vis a vis Test Driven Development. Which isn't to say I've done it to a great extent. I've just finished refactoring a small WinForms app to Model-View-Presenter and the exercise was a bit of an epiphany for me.
So here are the things I've been told that I now "get":
- Model-View-Presenter aids in testability
Whoa, nelly, is my view ever stupid. Like brother-cousin Zeke stupid. The form that implements it references System, System.Windows.Forms, and my presentation namespace and that's it. All methods/properties do one of three things: manipulate the value(s) of a form element, manipulate the value(s) of a local variable, or raise an event.
I'd like to say I made that rule early on and stuck to it but the fact is, it just ended up that way as a consequence of the refactoring.
That leaves a presenter with references to a bunch of non-UI assemblies (System.Collections, System.Xml, System.IO and the like) that does everything the original form did. All easily tested should I feel the need to write the tests (and take heart, I do feel the need; possum steps, readers, possum steps).
- Refactoring and adding unit tests is not TDD
I was ever-so-slightly befuddled when I read this in JP's post. I knew you were supposed to write your tests firsts in TDD but I thought he was being overly semantic when he said adding unit tests to an existing project is not TDD. But I knew better than to throw down with a guy who has four kids so I kept my mouth shut. And thank David I did.
Had I done this application the TDD way from the beginning, I would have ended up (presumably) with the same core code I have now plus a bunch of supporting tests. And the tests would cover 100% of the code I had written because you don't write any code unless it is fulfilling a test.
But that doesn't necessarily mean that the application does everything it's supposed to; only everything that the tests cover. We make the assumption that the tests cover the functional specs (i.e. the user's expectations, which I think is a better term).
The point is, TDD is a design methodology, not a testing methodology. JP et al have lovingly tried to drill this into my skull since the beginning but until now, those were just words I knew the meaning of individually but didn't quite grasp as a whole.
The biggest thing for this exercise for me wasn't to become an advocate of Model-View-Presenter. Sure, I can probably defend it to some extent to a naysayer but I'm not one to hold fast to any given pattern/methodology/way of life. I don't have that kind of conviction in pretty much any aspect of my professional life.
No, the best thing about MVP for me is that it's a damn fun little package. There's something karmic about making a stupid view and a presenter that could be re-used (even if the chances of doing so are pretty remote). It keeps me on my toes trying to think about a problem non-procedurally, which will save me a ton of money in crossword puzzle books I usually buy when I'm working on a more mind-numbing contract. (For the record, I generally boycott Sudoku these days on the basis that I've been playing it for nigh on twenty years in puzzle books when it didn't have a sexy Japanese name.)
In short, MVP is fun. And it makes developing fun. The fact that it is useful is just semantics.
Final thought: In a comment to a previous post, alert reader, Tom Opgenorth, suggested a method for retrieving data from the view in a Parameter object. I did not do that in my refactoring and here's why: I already had a Preferences domain object that stored all the data I needed to pass back and forth. So originally, I refactored using that object as the DTO which (and correct me if I'm wrong) is kind of what Tom was suggesting.
But then, I refactored further and got rid of it altogether, choosing instead to expose getter properties on the view for each of the properties in the Preferences object. A couple of reasons for that. First, it felt awkward passing the entire DTO when I just wanted to work with a single property. Second, by removing the reference to the Preferences object, I remove the dependency the form had on my Domain layer altogether leaving it to deal only with primitive types.
Note that the Preferences object was small enough (half a dozen properties, give or take) that I could do this pretty easily. I can imagine more complicated scenarios where a DTO might be more appropriate.
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) |
|
|
|