tag:blogger.com,1999:blog-19737509477752625582008-09-04T08:33:35.482-04:00MXUnit Blog<a href="http://mxunit.org">MXUnit</a> is a unit test framework built by ColdFusion developers for ColdFusion developers. This represents some general rants and the like for project related stuff, unit testing, and CF in general.billhttp://www.blogger.com/profile/06624894387927690246noreply@blogger.comBlogger51125tag:blogger.com,1999:blog-1973750947775262558.post-63342871803893560172008-09-04T07:07:00.005-04:002008-09-04T07:37:33.353-04:00MXUnit Eclipse Plugin Test History FeatureI'm happy to announce the latest feature to the MXUnit Eclipse plugin: Test History. Every time you run a test (or directory of tests), it adds that test to your history. When you want to revisit a test, you can open up the history menu, find your test, and reload it. Here's a screen shot to demonstrate:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://mxunit.org/blog/uploaded_images/PluginTestHistory-747605.gif"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="http://mxunit.org/blog/uploaded_images/PluginTestHistory-747593.gif" alt="" border="0" /></a><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />There are a few differences between the JUnit and MXUnit implementations of this functionality, born out of my own frustration with the JUnit plugin. I decided that if I were going to add test history, then I was going to make it work how I always wished the JUnit plugin would behave.<br /><br /><ol><li>No duplicate entries. "com.my.SomeTest" will only ever appear once in the list, and will only reflect the last run. If you run that test 20 times, you won't get 20 entries in the list</li><li>When running a directory of tests, you see the full path to the directory in the list</li><li>It's easy to distinguish between single TestCase runs and Directory runs. Simply, directory runs start with a slash.</li><li>A more reasonable start value for remembered test runs. JUnit defaults to 10 items in the history. Dunno why. MXUnit defaults to 30. Considering that duplicates never appear, this seems like a reasonable default. In addition, changing the default is simple and obvious.</li></ol>There's one thing to note, and I'm still not sure how I feel about this because I haven't worked with it enough to form an opinion yet: Tests retain their relative position in the test history no matter how many times they are run. Let me explain:<br /><br />You run Test1.cfc at 7:12:00 AM. This test gets position 1.<br /><br />You run Test2.cfc at 7:12:15 AM. This test gets position 1. Test1 is bumped to position 2 (i.e. it is lower in the list of tests in the menu).<br /><br />You run /mytests/ at 7:13:30 AM. This test gets position 1. Test2 is bumped to position 2, and Test1 is bumped to position 3.<br /><br />So far, this is how you'd expect it to work. Here's where it changes:<br /><br />You then select Test1.cfc from the test history and re-run that test. Now, since this last run makes it "newer", in a sense, you might expect it to show up at the top of the list. However, I am currently opting to keep that test in its original position. This might seem weird, but I'll ask you to reserve judgment until you've had a chance to work with it a bit.<br /><br />As always, feedback is welcome.<br /><br />Happy testing!Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-1046796200617489212008-08-25T07:58:00.003-04:002008-08-25T08:17:19.732-04:00Eclipse running out of memory?At home, where I do most of my experimental development, I have a fairly substantial machine. It's windows, but don't hold it against me. It performs like a mofo. 64-bit, 4GB memory, Core2. At work, I have 32-bit, 2GB memory, Core2.<br /><br />I run Eclipse 3.3 with tons of plugins. I generally run eclipse from the command line with 512M heap, using the jdk1.6 JVM<br /><br />So imagine my surprise when I start getting OutOfMemory errors from eclipse at home. I traced it to the JBoss plugin, but couldn't figure out for the life of me why it was causing this behavior. I wasn't actively using any of the functionality in the plugin at the time I'd get the errors. I turned off auto-activation at startup for the jboss and a handful of other plugins, but to no avail.<br /><br />And this was getting in the way of me getting stuff done. Eclipse "manage configuration" is a flaky piece of shit, so disabling suspect plugins didn't work. Thus, I did what any red-blooded American would do: wipe out any folders in the plugins/features directories that referenced jboss. It was time for a fresh start.<br /><br />That fixed the memory problems and let me get back to work.<br /><br />But honestly.... could you live with that answer? I couldn't. So off I went. I asked the interwebs, and after nice traipse through various newsgroups and wikis and bugtrackers, I stumbled on a fix. Ooooooh, what a maddening f*cking fix.<br /><br />Here's the bottom line: my memory troubles went away when I added this to my startup file:<br /><br />-XX:MaxPermSize=256m<br /><br />That's all.<br /><br />By the way, here's where I ended up: https://bugs.eclipse.org/bugs/show_bug.cgi?id=92250. If you're interested, that bug explains pretty well why I was hitting the memory issues I had. It doesn't explain why it was JBoss plugin causing it... but right now, I don't care.<br /><br />I also installed the memory monitor plugin attached to that bug. It's no better than what you get from jconsole, but marginally more convenient than attaching eclipse to a jmx session and typing "jconsole".<br /><br />So, if you're getting OutOfMemory errors while running eclipse, and increasing the heap isn't cutting it (-vmargs -Xmx512M), it's possibly not a heap problem but a permgen problem.<br /><br />--marcMarc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-402923523339473222008-08-21T07:19:00.002-04:002008-08-21T07:29:41.133-04:00MXUnit 1.0.2 Now AvailableMXUnit 1.0.2 is <a href="http://mxunit.org/download.cfm">now available for download</a>. This release adds new functionality for mocking/injecting properties into objects at time of test, which arose from <a href="http://groups.google.com/group/mxunit/browse_thread/thread/cd19590dfb1c205c#">this discussion</a>. It's similar to the <a href="http://mxunit.org/doc/index.cfm?doc=injectmethod">injectMethod</a> functionality introduced in 1.0.1, but for properties instead of methods. <a href="http://mxunit.org/doc/index.cfm?doc=injectproperty">Details are here</a>.<br /><br />In addition, a few more bugs have been fixed for Railo and openBD compatibility.<br /><br />Finally, I want to point out again the eclipse dictionary and snippets that are available in the download. See the "cfeclipse" directory. In particular, check out the copysnippets.xml ANT file. It makes keeping your set of mxunit cfeclipse snippets much easier. It will not tramp on your existing shortcuts, as it only adds/overrides shortcuts whose snippets are in the mxunit package.<br /><br />The eclipse dictionary is not the entire mxunit api.... just the public stuff that you care about (assertions, etc). There are instructions in each directory for how to "install" the dictionary and snippets.<br /><br />Enjoy.<br /><br />marcMarc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-6965085392193109772008-08-14T10:18:00.002-04:002008-08-14T10:20:42.299-04:00Securing a RemoteFacade.cfcNathan Mische has a nice article on how to let the mxunit see a RemoteFacade.cfc file sitting behind a basic authentication scheme. Check it out here <a href="http://www.mischefamily.com/nathan/index.cfm/2008/8/13/Basic-Authentication-With-ColdFusion">http://www.mischefamily.com/nathan/index.cfm/2008/8/13/Basic-Authentication-With-ColdFusion</a>Mikenoreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-59343340775260346132008-08-05T07:29:00.002-04:002008-08-05T07:42:47.159-04:00MXUnit eclipse plugin update. This one's for BarneyA new version of the MXUnit plugin for Eclipse is now available. I know... you're so excited you can hardly contain yourself! Before you hit the update button, here's what's new:<br /><br /><ul><li>A new "Show Failures Only" button to filter out the passing tests and show you just the bad stuff. This is a persistent toggle, meaning that once you turn it on, subsequent test suites will continue to filter out passing tests until you toggle off. However, if ALL tests pass, the filter is ignored.<br /></li><li>A new feature just for Barney Boisvert (<a href="http://www.barneyb.com/barneyblog/2008/07/18/mx-unit-is-slick/">read here for the inspiration</a>). This is a small helper for people who use the project-level facade URL feature. This new feature is also documented in the integrated help within the plugin. Thanks for the suggestion, Barney!<br /></li><li>A bug fix. Have you ever seen it where you run a bunch of tests, and maybe the console view is open on the bottom of the workbench, and the scrollbars in the test run view get hidden? If you have suffered through this tragic, disgraceful behavior, you can sleep a little easier now. It is gone.</li></ul>Enjoy.Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-33448613791747040302008-07-31T13:16:00.002-04:002008-07-31T13:34:45.658-04:00Railo, Testing, and StogiesLast night, Gert Franz from Railo visited our <a href="http://baltimore-aug.org/index.cfm?event=meetings.showMeeting&amp;meetingId=5">Baltimore Adobe User Group</a>. First, thanks to Gert for spending so much time with us. Next, thanks to Nic Tunney for leading the group, as always. Finally, extra special thanks to Bob Clingan for setting it all up.<br /><br />So, real quick: Railo is impressive. It's damn near unbelievable that such a small team of people could create a product so solid and forward thinking. Some of my favorite things:<br /><br /><ul><li>Virtual file system. Treating an ftp site or an S3 instance as just another resource? BADASS.</li><li>Railo's notion of contexts. Each app is an isolated thing. We've been working around that problem with CF for a long time. With railo, it's simply not a problem</li><li>cfvideo is really, really cool. Now, I know that a lot of people don't want to see this in CF. And I know it won't be in the open source version of Railo. Still.... watching the kinds of sweet things Gert demoed, I can see its value</li></ul>And there's so much more but I don't want to blabber.<br /><br />Two other things: I asked Gert if they expected to keep up the same pace of development when it goes open source. One thing he said was that the first thing they do when they get a bug is write a test. They fix the bug, prove it's fixed with the test, and keep that test around forever. We try like hell to do this with MXUnit, by the way. Anyway, he said that he believes part of the reason they can move forward with features is that they just don't have a whole lot of bugs. I took it to mean that their use of TDD contributes to that quality. So... mad props, Gert!<br /><br />Finally, the coolest thing of all: the <a href="http://www.davidoff.com/davidoff/">Davidoff</a> site runs on Railo. For those not in the know, Davidoff are a luxury cigar. In fact, I'm so cheap I've never had one. But still... I thought that was rad.<br /><br />Here's what I came away with: up until now, my only experience with Railo has been working to get MXUnit running on it successfully. But now, after last night's presentation, I'm definitely going to see if I can get some of our apps at work running on it. I'm particularly interested in looking at the performance.<br /><br />When OBD came out, there was nothing about it that made me consider for even a second approaching the subject where I work. With Railo, though, I see definite potential and consider it worth my time to pursue this investigation.<br /><br />And it's not just because I got a free t-shirt.Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-39539062571077858082008-07-30T17:17:00.003-04:002008-08-01T07:08:35.832-04:00Coldbox and the CFEclipse Frameworks explorerI just tried installing CFEclipse 3.2 beta again, and I see that coldbox still isn't supported in the Frameworks explorer. So I'm like, "hey, I remember Mark saying that the frameworks explorer was a framework for exploring frameworks", so I decided to dig into the XML and see if I could get it going. So I'm poking around in the cfeclipse code, just getting familiar with what's going on, and I fire up a debug session, and I add a coldbox project, and I set the coldbox.xml.cfm file as a config file, and I open up the explorer, and there's all these rad icons in there. So I'm like, WTF?<br /><br />So I open up the frameworks config directory, and lo and behold, there's coldbox right in there already.<br /><br />Now, the file timestamps indicate that this was in there before 1.3.2 was released, so I'm not sure what might have happened that this stuff didn't make it into the build. Nonetheless, that saved me a lot of time because all the work was done. However, this didn't solve the central problem, because if the stuff ain't in the distribution, how do I get it in my current CFEclipse install?<br /><br />Here's what you do:<br /><br />1) download the attached file and unzip it into your eclipse\plugins\org.cfeclipse.cfml.frameworks_1.0.3 directory<br /><br />OR, if you want to get it from source:<br /><br />1) check out the cfeclipse frameworks directory from SVN. If you want to save yourself the headaches, just read this (http://trac.cfeclipse.org/cfeclipse/wiki/CheckingOutCFEclipse) and do the team project set thing. it's a snap.<br /><br />2) that will get all the cfeclipse code into your workspace. NOT your current plugins directory... your workspace.<br /><br />3) go into your workspace, navigate to the frameworks project, and copy the config and icons directories into another temp location<br /><br />4) remove the .svn stuff from there<br /><br />5) now, take those directories and copy them into your eclipse\plugins\org.cfeclipse.cfml.frameworks_1.0.3 directory<br /><br />6) restart eclipse.<br /><br />7) find a coldbox project, find the coldbox.xml.cfm file, right click, set as config file, and bam. Done!<br /><br />Now, looking at how frameworks explorer works, I gotta tell you: it is unbelievably cool. Mad props to you, Mark Drew, for this gem.<br /><br />esher, out!<a href="http://mxunit.org/blog/org.cfeclipse.cfml.frameworks_1.0.3.zip">org.cfeclipse.cfml.frameworks_1.0.3.zip</a>Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-39023216974472097982008-07-29T22:07:00.003-04:002008-07-29T22:26:09.239-04:00It's not you, Arturo...A little over a year ago, I sprung 5 bucks on an Arturo Fuente. Until then, I'd never spent that much on a stogie.<br /><br />It was a life-altering cigar. Literally. I've spent more money on Fuentes since then than I ever would've spent on stogies had I stuck with my 1.20/stick cheapo La Fincas. Not life altering in the same way that marriage or children are, not even close. But still... Fuentes have been my staple stogie, a friend I return to time and again.<br /><br />Since then, I've bought boxes of Monte Cristos, smoked kickass Cohibas, Ashtons, and all manner of brands in between. I won 3 monster Gurkha "shaggy" cigars at an event at Cross Street Tobacco in Federal Hill, stickering at 15/stick, and they were magnificent. To the hunchbacked guys huddled over their rolling tables in the forests of Nicaragua and Honduras, a hearty "Thank you!"<br /><br />My wife and kids are out of town this week, at my in-laws. I couldn't go b/c of work, but I miss them terribly. So I decided to while away the time with good beers (Dogfish), programming, and cigars. My wife and daughters come home tomorrow (Thank God!), so I decided to spend the last night of my short-lived quasi-bachelorhood with a new stogie.<br /><br />I'm sorry, Arturo. It's not you. I still love you. But damn...<br /><br />Onyx Maduro, you .... *gasp*.... complete me. (super ghey reference, I know)<br /><br />Lord, what a stogie. A 2-hour smoke, burnt down to the very nub, and excellent all the way. Most stogies start to suck after an hour or so. They get hot, tarrish, nasty. But this one, jeeeeesh.<br /><br />I'm in awe.<br /><br />I married my bride at 4 on a Saturday afternoon. My firstborn, Alexis, was born at 10:15 on a Saturday Night. My dear little Sidney was born in the wee hours on a Thursday. Those nights are reserved in the "Best Saturday and Thursday nights of my life".<br /><br />Tonight, thanks to you Onyx, gets the Wednesday slot.Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-3653119752516030372008-07-29T17:13:00.002-04:002008-07-29T17:15:45.904-04:00MXUnit 1.0.1 is now available!<a href="http://mxunit.org/">MXUnit</a> 1.0.1 is now available for download. This is a small patch release and includes:<br /><br /><ul><li>var scope fixes</li><li>injectMethod functionality for simple mocking</li></ul><br />That last one deserves some mention. Mock frameworks like Brian Kotek's <a href="http://coldmock.riaforge.org/">ColdMock</a> and Mike Steele's <a href="http://cfeasymock.riaforge.org/">CFEasyMock</a> are excellent tools for mocking collaborators. However, sometimes you want to spoof/override a function in your object under test for the purposes of testing some other function inside that same object. Say you have a function that hits the DB and you'd rather it not. Or you have a function that returns a query and you need to force it to return specific data rather than rely on the state of the database. If those functions are inside your object under test, a mock framework might not be much help here.<br /><br />That's why injectMethod was introduced... to solve the problem of mocking functions inside the object under test. <a href="http://mxunit.org/doc/index.cfm?doc=injectmethod">A full write-up is available here</a>.<br /><br />By way of history, this functionality came out of a <a href="http://mxunit.org/blog/2008/04/adventures-in-mocking-part-1.html">previous post on mocking</a>. I'll be providing a follow-up to that post, in the days to come, to show how that specific unit test changes as a result of injectMethod.<br /><br />Happy testing.Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-86017817747595322432008-07-22T12:47:00.001-04:002008-07-22T12:47:19.252-04:00Code and Preso: Database Test Patterns for Unit Testing<p>Last week's presentation at <a href="http://coldfusionmeetup.com">http://coldfusionmeetup.com</a> was wrought with difficulties, but hopefully <em>some</em> of the concepts came across the frazzled wire ok and folks found a thing or two useful.</p> <p>The code and presentation materials are available here: <a title="http://github.com/virtix/zoo/tree/master" href="http://github.com/virtix/zoo/tree/master">http://github.com/virtix/zoo/tree/master</a></p> <p>Click the &quot;download&quot; button and&#160; you'll be given an option to download either a zip or tar. I've added Mike Rankin's code illustrating how to use SQL Server 2005 snapshots inside of MXUnit to keep your local database sandbox in a known state for testing. This is a great utility for those on SQL 05!</p> <p>best, <br />-bill</p> billhttp://www.blogger.com/profile/06624894387927690246noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-91743582280636171942008-07-11T14:40:00.003-04:002008-07-11T15:19:27.673-04:00New MXUnit goodies and a few backwards compatibility bustersThere are a few small enhancements in the 1.0 mxunit release i wanted to talk about... and one change that will break some of your tests.<br /><br />1) definitely check out the cfeclipse directory in the download. it's got snippets, a dictionary file, and instructions for both. for you ANT fans, there's a copy.xml file in there that should make bringing the snippets and keyCombos into your existing stuff a snap.<br /><br />2) we don't encourage the (over)use of makePublic(), but it's nice in a pinch. One thing I never liked about it was that when you used it, the "public" version of your private function had a different name. For example, "doSomething" got turned into "_doSomething". That has been changed so that the name defaults to be the same as the function you're making public. If you have existing code that uses makePublic(), IT WILL BREAK!. Simply remove the underscore from the function call and all is well<br /><br />3) if you were using any of the assertEmpty.... functions in the "MXUnitAssertionExtensions" component, they now behave exactly the opposite of how they did previously. Now, they do what they claim to do, i.e. assert something is empty. We had it backwards before. Blame it on the beer, scotch, stogies, and kids.<br /><br />4) there's a new injectMethod() function in TestCase. For now, ignore it. I have a little more tweaking to do before it works properly for lightweight mocking. We'll be putting out a patch for it within the next few days, after we hear from other issues that people have.<br /><br />5) the plugin and framework should now work with open bluedragon. If you have any problems with OBD and mxunit, please let us know. As for railo, I tried it as soon as it released, there was a bug in railo (because we were doing some dumb stuff that exposed the bug), and railo fixed it but i haven't given it another shot. I'm fairly certain that at this point mxunit will not work with railo... at least, the plugin probably won't. on a side note, i am extremely impressed with railo's responsiveness! extremely. I posted this "issue" one day and i think it was fixed the next. Like my daddy used to say, I didn't know whether to shit or go blind. Regardless, it's because of their responsiveness that we will indeed be making a hearty effort to get mxunit fully working with railo very shortly. <br /><br />6) the ant task has been updated and now respects the "excludes" attribute. for me this was a big deal. thanks bill!<br /><br />So, any issues with the new release, please post it to the mxunit group or file a bug report at the googlecode site.<br /><br />thanks!Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-84682729416584509992008-07-11T12:41:00.001-04:002008-07-14T10:13:05.457-04:00MXUnit 1.0 Now Available!<p>A really long time in the making, los hombres at MXUnit.org (<a href="http://mxunit.org">http://mxunit.org</a>) are finally distributing the first production release of their open source Unit Test Framework and Eclipse Plugin for ColdFusion Developers. </p> <p>The big truth is that we want to get this version out because we are chomping at the bit to make MXUnit much more robust and flexible -&quot;We're figuring on biggering and biggering ...&quot; - Dr. Suess </p> <p><strong>Improvements since RC-1</strong>: <br />* More documentation and tutorials <br />* Dictionary and snippets for CFEclipse <br />* assertXPath(...) for testing generated html, xml, and anonymous web pages <br />* assertSame()/assertNoteSame() - Thanks Mark Mandel! <br />* HTML runner and remote web runner for test suites <br />* Test Blaster - recursive test stub generator <br />* Ant task: errorproperty/failure property, and more ... <br />* J2EE CFML open source (Railo/OBD) comparability issues addressed - Thanks Adam Haskel! <br />* And one, maybe two minor bug fixes ;-) </p> <p><strong>Upcoming Features</strong>: <br />* Selenium Integration <br />* Annotations for flexibility <br />* Improved TestSuite constructs <br />* Test report generation (in lieu of JUnit reports) <br />* And more ... </p> <p><strong>Upcoming Events</strong>: <br />* ColdFusion Meetup - &quot;Database-centric Testing w/MXUnit&quot;, Thursday, July 17, 2008. 12:00 Noon EDT <br />* Adobe Max 2008 - &quot;Advanced Patterns for ColdFusion Test Automation&quot;, Wednesday, November 19, 9:30 AM&#160; PST </p> <p><strong>Key Features</strong>: <br />* Easy to see your data with cfoutput, cfdump, and debug() <br />* Easy to run single test functions <br />* Easy &quot;directory runner&quot; for running entire directories of tests <br />* Easy to test private functions in your components <br />* Ability to switch to message-first style assertions to help ease transition from other frameworks <br />* A plethora of output formats from which to choose <br />* Ant Integration <br />* A team actively improving the framework, making testing easier, and providing abundant documentation </p> <p>Download the latest version of the framework here: <a href="http://mxunit.org/download.cfm">http://mxunit.org/download.cfm</a> and update your MXUnit Eclipse plugin using the following url: <a href="http://mxunit.org/update">http://mxunit.org/update</a></p> <p>Special thanks out to Sean Corfield all the folks who really kicked the tires and gave good honest feedback! Keep 'em coming ...</p> <p>Thanks! <br />The Guys at MXUnit.org</p> billhttp://www.blogger.com/profile/06624894387927690246noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-14836016135548986582008-07-01T13:01:00.007-04:002008-07-01T14:04:45.389-04:00Using MXUnit to Test SQL Server Database Logic<p>I need a way to test database logic <em>and</em> to persist those tests. In the past, I would open query analyzer type in some batch code, hit F5 and visually inspect the results. If all looked good, I would move it to a stored procedure, view, CF code, or some other object. There are a number of problems with this approach, and the one that bugs me the most is that the work product of this process becomes lost. Though I may have something working in the end, the pieces and what I was thinking at the time are gone, and at my age, that means gone <u>forever</u>.</p> <p>Enter MXUnit ... using an xUnit framework to test a database? Why not? Ideally, it would be nice to have an xUnit database tool that allowed me to test snippets of batch code. I know there's sqlunit and dbunit that may work, but where I'm at, our db's are tied down pretty tight, but given a cf datasource and MXUnit, I can get pretty far. I can also wrap stored procedures up in tests and print out reports of database test suite runs for the dbas (more on this one later).</p> <p>Here's a little gem that popped up the other day. Did you know you can define a CURSOR object inside of CFQUERY? I didn't, but here's is part of something I wanted to test:</p> <br /><pre style="font-family:courier;background-color:#eaeaea;border:1px ridge black"> <br />---- Super Simple SQL Date Stuff ---- <br />DECLARE @fiveMinutesAgo datetime, @rightnow datetime <br />SELECT @rightnow = GETDATE() <br />SELECT @fiveMinutesAgo = DATEADD (n, -5, @rightnow)<br />PRINT @rightnow <br />PRINT @fiveMinutesAgo<br />---- End ----<br /></pre><br /><br /><p>This simply prints the time the batch was run and that time less 5 minutes - mine eyes verify the correctness. Not too bad; certainly not automated; but, it has a smell that is becoming increasingly intolerable for me. </p> <p>Here's the same thing written as an MXUnit test:</p> <br /><pre style="font-family:courier;background-color:#eaeaea;border:1px ridge black"><br />&lt;cffunction name="testCallingDbDateFunctions"&gt;<br /> &lt;cfquery name="q" datasource="logparser"&gt; <br /> DECLARE @fiveMinutesAgo datetime, @rightnow datetime <br /> SELECT @rightnow = GETDATE() <br /> SELECT @fiveMinutesAgo = DATEADD (n, -5, @rightnow) <br /> <br /> DECLARE dbTestCursor CURSOR FOR <br /> SELECT @rightnow as nowDate, <br /> @fiveMinutesAgo as fiveMinutesAgo; <br /> OPEN dbTestCursor; <br /> FETCH NEXT FROM dbTestCursor <br /> CLOSE dbTestCursor; <br /> DEALLOCATE dbTestCursor <br /> &lt;/cfquery&gt; <br /><br /> &lt;cfscript&gt; <br /> rightNow = q.nowDate; <br /> exepectedDate = dateAdd('n', -5, rightNow); <br /> fiveMinutesAgo = createODBCDateTime(q.fiveMinutesAgo); <br /> debug(q); <br /> assertEquals(exepectedDate,fiveMinutesAgo,<br /> "If broken, could be a date bug in CF or SQL."); <br /> &lt;/cfscript&gt;<br />&lt;/cffunction&gt;</p> <br /></pre><br /> <p>The debug output looks like this:</p> <p><a href="http://lh4.ggpht.com/virtix/SGpi21x6niI/AAAAAAAAACw/jsv1und6Tig/s1600-h/image%5B2%5D.png"><img style="border-width: 0px;" alt="image" src="http://lh4.ggpht.com/virtix/SGpi3l038rI/AAAAAAAAAC4/ZUNv8O3cyyk/image_thumb.png?imgmax=800" border="0" height="74" width="244" /></a> </p> <p>This looks like a lot of code to test such a simple piece of logic, and it is. But, the cool things about this for me, were (1) I can create a CURSOR object within CFQUERY and the name of the query becomes a reference to the CURSOR, and (2) I can use MXUnit and ColdFusion to run and persist database logic tests.</p> <p>More on database testing soon ...</p> <p>-bill</p>billhttp://www.blogger.com/profile/06624894387927690246noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-87692941589140211952008-06-25T08:14:00.003-04:002008-06-25T08:21:16.004-04:00On twitter... and feeling slightly dirty about itPeep dis: within the past year, we hired this young kid named Mike. I call him Facebook (because he's young, natch) and he calls me Gramps. It's great. Anyway, i bust his balls because he's on this twitter thing, and he "tweets", and i'll be damned if that's not the fairiest sounding word ever verbed. Every time I type it I want to kick my own ass.<br /><br />So I'm at cfunited and I'm wondering, "is that little bastard doing any work today while I'm gone?" I sign up for twitter and try to follow him, seeing if he's tweeting away every 5 minutes instead of doing whatever it is he does at work. Well, he wasn't tweeting, so I assume he was on youtube or some other time drain.<br /><br />Anyway, here's what I'm wondering, now that I'm on this thing: what's the value? What will it bring my life that I don't already have? In what ways are people using it that helps you out day to day? I'm not asking as a smartass. I'm asking because I really want to know. <br /><br />i'm at twitter.com/marcesher<br /><br />I don't know why yet. Maybe after every 10 tweets (dammit!) they'll send me a free dunkin donuts gift card or something.Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-20389468106178457002008-06-24T13:56:00.000-04:002008-06-24T13:57:27.223-04:00CFUnited: Automating the Build & Deploy Process with ANTI was presenting on an old ghetto laptop that couldn't run connect (although in all fairness to the P.O.S. machine, connect is a total hog.). Anyway, the most important part other than my charm and grace is the code, right? So the presentation, with all promised code, jar file dependencies, etc is <a href="http://mxunit.org/doc/index.cfm#cfmeetup">here at the bottom of the page</a>.<br /><br />My "wingman", the goatee'd fellow asking really good questions, asked me another great one the following day and I thought it bore mentioning here. During the 2nd part of the presentation, I demo'd macrodef and used an example of looping over a query of servers, and for each server, running a bat file that would open up a connection to that server. Then, it'd copy the code. Then, it'd run that same bat file and close the connection to the server.<br /><br />Currently, our environment is completely locked out of the prod environment, rightfully so. And so in my example, I was saying that the bat file in question would temporariliy open up a connection to each production server and then close that connection.<br /><br />My goatee'd wingman asked, "but that doesn't prevent you from running that bat file any other time, does it?" And he's absolutely right. Here's a case where I'm hoping that our network admins will work with us, understanding that we have nothing to gain by opening up those connections except during the deploy process. But that remains to be seen. This is definitely a case of "let's compromise".<br /><br />So, thanks to all who showed up for the session, and extra special thanks for the brave folks who stuck around for the 2nd half of it!<br /><br />For the curious and bored, here's the code in question. The contents of OpenOrClose.bat are irrelevant here... it's the concept we were discussing that matters. The "deployToAllServers" target is the big daddy.<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>&lt;project name="CFUnited (j): SQL, for, MacroDef, and exec" basedir="." default="getServers"&gt;<br /><br /> &lt;target name="init"&gt;<br /><br /> &lt;property name="app.name" value="Client1App" /&gt;<br /><br /> &lt;property name="dev.root" location="DEVSERVER" /&gt;<br /> &lt;property name="test.root" location="TESTSERVER" /&gt;<br /> &lt;property name="locations.dev.clientroot" location="${dev.root}\${app.name}" /&gt;<br /> &lt;property name="locations.test.clientroot" location="${test.root}\${app.name}" /&gt;<br /><br /> &lt;property name="locations.dev.customtags" location="${dev.root}\CustomTags" /&gt;<br /> &lt;property name="locations.test.customtags" location="${test.root}\CustomTags" /&gt;<br /><br /> &lt;property name="locations.test.deploy" location="${locations.test.clientroot}\deploy" /&gt;<br /> &lt;property name="locations.test.deployzip" location="${locations.test.deploy}\${app.name}.zip" /&gt;<br /><br /><br /><br /> &lt;!-- read all our 'secure' properties from this file; this defines the sql.userid and sql.password properties --&gt;<br /> &lt;property file="unames.properties" /&gt;<br /><br /> &lt;!-- create a classpath for ANT to use for finding and running the jdbc driver(s) --&gt;<br /> &lt;property name="jdbclibdir" location="lib" /&gt;<br /> &lt;path id="jdbc.classpath"&gt;<br /> &lt;fileset dir="${jdbclibdir}"&gt;<br /> &lt;include name="**/*.jar" /&gt;<br /> &lt;/fileset&gt;<br /> &lt;/path&gt;<br /><br /> &lt;!-- these properties would be better placed in a .properties file --&gt;<br /> &lt;!-- this will use the jtds.jar in the classpath --&gt;<br /> &lt;property name="sqlserver.driver" value="net.sourceforge.jtds.jdbc.Driver" /&gt;<br /> &lt;property name="sqlserver.url" value="jdbc:jtds:sqlserver://localhost:1436/ANT;instance=NetSDK" /&gt;<br /><br /> &lt;!-- this will use the mysql-connector jar in the classpath --&gt;<br /> &lt;property name="mysql.driver" value="com.mysql.jdbc.Driver" /&gt;<br /> &lt;property name="mysql.url" value="jdbc:mysql://localhost/ANT" /&gt;<br /><br /> &lt;!-- this is where the sql resultset will be stored --&gt;<br /> &lt;property name="db.output" value="serverslist.txt" /&gt;<br /><br /> &lt;!-- http://sourceforge.net/projects/ant-contrib --&gt;<br /> &lt;taskdef resource="net/sf/antcontrib/antlib.xml" classpathref="jdbc.classpath" /&gt;<br /><br /> &lt;/target&gt;<br /><br /><br /> &lt;target name="getServers" depends="init" description="Queries a db for a list of servers"&gt;<br /><br /> &lt;sql driver="${mysql.driver}" url="${mysql.url}" userid="${sql.username}" password="${sql.password}" classpathref="jdbc.classpath" print="yes" output="${db.output}" showheaders="false" showtrailers="false"&gt;<br /> select ServerIP from servers where ActiveFlag=1<br /> &lt;/sql&gt;<br /><br /> &lt;/target&gt;<br /><br /><br /> &lt;!--<br /><br /> IMAGINE: You have a database table of servers to which you'll deploy.<br /> You want to query for the "active" servers, and for each server<br /> Copy your code onto it. This would assume an active network connection between<br /> the machine you're on and the servers to which you are deploying<br /> --&gt;<br /><br /><br /><br /><br /> &lt;target name="loopOverServers" depends="init,getServers"&gt;<br /> &lt;loadfile srcFile="${db.output}" property="serverlist" /&gt;<br /> &lt;for list="${serverlist}" param="server" delimiter="${line.separator}"&gt;<br /> &lt;sequential&gt;<br /><br /> &lt;echo&gt;Copying to @{server}\Apps\${app.name}&lt;/echo&gt;<br /><br /><br /> &lt;copy toDir="@{server}\Apps\${app.name}" preserveLastModified="true" includeEmptyDirs="false"&gt;<br /> &lt;fileset dir="${locations.test.clientroot}" /&gt;<br /> &lt;/copy&gt;<br /><br /> &lt;tstamp&gt;<br /> &lt;format pattern="MM/dd/yyyy hh:mm aa" offset="-5" unit="year" property="customtagfilter" /&gt;<br /> &lt;/tstamp&gt;<br /> &lt;copy toDir="@{server}\CustomTags" preserveLastModified="true" includeEmptyDirs="false"&gt;<br /> &lt;fileset dir="${locations.test.customtags}"&gt;<br /> &lt;date datetime="${customtagfilter}" when="after" /&gt;<br /> &lt;/fileset&gt;<br /> &lt;/copy&gt;<br /> <br /> &lt;/sequential&gt;<br /> &lt;/for&gt;<br /><br /> &lt;/target&gt;<br /><br /><br /><br /><br /><br /><br /> &lt;!-- NOW IMAGINE:<br /><br /> You want to do the same as above, but you need to open and close the connection to each server<br /> because your network people run a tight ship, and they don't want connections open<br /> between your dev environment and your other environments except<br /> during the brief time it takes to copy code<br /><br /> --&gt;<br /><br /> &lt;target name="deployToAllServers" depends="init,getServers"&gt;<br /> &lt;!-- open the connections using a bat file provided to you by your network admins;<br /> we'll call the bat file using a self-made task that we create with macrodef --&gt;<br /> &lt;OpenOrCloseConnections action="open" /&gt;<br /><br /> &lt;!-- call the loopOverServers target and copy all code --&gt;<br /> &lt;antcall target="loopOverServers" /&gt;<br /><br /> &lt;!-- close the connections --&gt;<br /> &lt;OpenOrCloseConnections action="close" /&gt;<br /> &lt;/target&gt;<br /><br /><br /> &lt;!-- MACRODEF: this little gem lits you define your own tasks! --&gt;<br /><br /> &lt;macrodef name="OpenOrCloseConnections"&gt;<br /> &lt;attribute name="action" default="close" /&gt;<br /> &lt;!-- imagine: this OpenOrClose.bat is provided to you by your network people to open<br /> up the appropriate connections, and then close them, for the life of your script.<br /> In this way, you can directly copy files across the network during a brief window of<br /> access and then automatically remove that access once the deployment is over --&gt;<br /> &lt;sequential&gt;<br /> &lt;exec executable="cmd"&gt;<br /> &lt;arg value="/c" /&gt;<br /> &lt;arg value="OpenOrClose.bat" /&gt;<br /> &lt;arg value="-@{action}" /&gt;<br /> &lt;/exec&gt;<br /> &lt;/sequential&gt;<br /> &lt;/macrodef&gt;<br /><br /><br /> &lt;!-- FINALLY, IMAGINE:<br /><br /> Imagine a table called "Deployments" and a table called "J_Deployments_Servers". And<br /> every time you deploy, you insert a row into the deployments table, get the ID<br /> of that deployment, and for each server you copy code to, you insert a row<br /> into J_Deployments_Servers with some audit information (IP address of machine<br /> from which the deployment was run, the exact time of the copy, etc).<br /><br /> How would you do that using sql and macrodef up in your loopOverServers target?<br /><br /><br /><br /> --&gt;<br /><br /><br /><br />&lt;/project&gt;<br /></code></pre>Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-28819853952332976222008-06-23T20:33:00.015-04:002008-06-24T08:36:14.081-04:00CFUnited Wrapup #341All the young hipsters have been busily writing up their cfunited wrapups, so I'm jumping into the fray mostly because it will force me to review all I learned. I want to get one or two sentences about each session to force concision. There's nothing like writing about something to help you crystallize your thoughts. Ideally I'll get out of the blather I'm about to spew at least a 3-minute sales pitch for why my company should consider sending people next year or why people I work with should consider going even if the company doesn't buck up.<br /><br />This whole business of "who should go to conferences?" is a topic i have a lot of thoughts on, but it is a post for another time. Like you care, I know.<br /><br />So, here goes:<br /><br /><span style="font-weight: bold;">keynotes:</span> I'm not gonna dis michael smith's keynote b/c i want to present at cfunited next year. As for ben/adam's adobe keynote, the "announcements" have been covered ad nauseum by all the other cool kids. I have two additional thoughts: #1) those of you dying for hibernate.... have you ever worked with it? do you know what the implications are? I've built some fairly sizable java apps in spring and hibernate, and I tell you this: it's no magic bullet. More on that for another post maybe. And #2) talk about nail-in-the-coffin for CFEclipse. Up until now the teasing has been mild. Annoying, but mild. Now it's bordering on hand-down-your-pants. I asked adam haskell if he'd consider contributing to cfeclipse. He summed it up thusly: "Did you hear today's keynote?" Nuff said. <span style="font-family:courier new;">Project Explorer --> org.cfeclipse.cfml --> right click --> close project.</span> I hope I'm wrong, but I doubt it.<br /><br /><span style="font-weight: bold;">Session #1: Jeremy Kadlec, Advanced Search with SQL Server</span><br /><br />Bottom line: It's easy! I had no idea how powerful full text searching was in sql server. Must consider, though, the performance implications of inserts/updates when the text index is set to auto-build, similar to the considerations of loading up tables with indexes. For me, this will come down to load testing. ain't no way to guess through this one... just gotta do it and see what happens. Also, must remember to add mssqltips.com to the ol' feed reader.<br /><br /><span style="font-weight: bold;">Session #2: Bill Shelton, Patterns for test automation</span><br /><br />As Bill said, "that's a pretty grown up title!". So was the session description. Still, it was obvious that much of the audience needed an Intro to Unit Testing presentation first. This is fine, but it slowed bill down b/c he had to answer a lot of questions ("what's an assertion?"). But he did manage to have at least enough time for to get across probably the two nuggets of gold he was going for: #1) dependency injection is critical for getting the most out of unit testing. #2) how to use cfeasymock. It's is so much clearer to me now having seen him use it. I don't know why, but the docs for cfeasymock just confuse me. Bill's example -- probably because he thoroughly explained exactly the kinds of dependencies he was trying to get around -- was crystal clear. Maybe it's because i was sitting in the front row, but his example nailed it for me. Bill works on ubuntu so couldn't give the preso over connect, but I do hope he can find the time to do the recording soon.<br /><br /><span style="font-weight: bold;">Session #3: Hal Helms, Object Oriented best practices</span><br /><br />Hal seemed sick. But his message -- question the pseudo-OO that some folk preach -- came across nicely. I love Hal's "question everything" approach. He makes you a better programmer just by reinforcing the idea that it's OK not to believe everything you read, that you gotta think for yourself and earn your answers. Practical example: "bean" objects that are just bags of getters and setters, with no behavior and just data, aren't very useful for us CFers and are more pain, less gain. This is in line with Hal's general "don't start with the data(base)" philosophy. Anemic domain = bad. Objects that do stuff = good. I'm going to give hal's 6-line-maximum method approach a shot and see how it changes the way I think about code.<br /><br /><span style="font-weight: bold;">Session #4:</span> Nic Tunney was supposed to talk on section 508 and RIA, and I was going to go to that. But he didn't show, so I called it a day.<br /><br /><span style="font-weight: bold;">Session #5: Adam Haskell, Code Reviews and Mentoring BOF</span> (birds of a feather)<br /><br />You know what: I have too many thoughts on this one. I want to let it gestate and write about it down the road. Interestingly (to me and only me), this BOF was probably the session whose content is nearest to my heart. So I want to say more at a later time. MAD props to Adam for leading this extremely important and eye-opening session.<br /><br />But tell me: why can't conference organizers have the event staff put the rooms into a circle or square for the BOFs? c'mon, people. pay the extra 100 bucks and get the rooms properly configured.<br /><br /><span style="font-weight: bold;">Thursday, Session #1: Clark Valberg and Hal Helms, Prototyping for Smarties.</span><br /><br />Here's an example of how chance encounters change the course of things. On Wednesday, I was standing in the lunch line with no one to talk to, and behind me was this girl and two dudes. Girl is on the phone. She hands it to dude #1. Within 10 seconds I'm thinking to myself "this is possibly the funniest conversation I've ever heard". Girl was Anne Nelson (Hi Anne!). Dude was Clark Valberg, who called himself the sometimes "office minstrel". Damn is he funny. Anyway, next morning I'm reviewing the schedule and I saw he was talking with Hal, and although I hadn't originally planned to attend this session, I figured that i just had to see it. Something about meeting him in the lunch line. Anyhoo....<br /><br />This session was useful for me but would've been about 10 times more useful for the project management department where I work. I'm going to try really hard to get a few PMs to watch the connect preso with me. It was excellent. Hal's story about Joe the Warehouse guy really drove home the notion of the near-impossibility of nailing down specs, and coding to them, when the specs are driven by a select few and not the people using the software. Classic case of "this is what we thought we needed but it's not what we needed". We fight this where I work all the time and so it's a topic that's becoming really hot. This one hit home.<br /><br />Now, I know that the one person out there reading this is gonna be like "duh, go agile". To which I reply, try turning around a battleship just because it's going to hit the little tiny fishies in its way. Change is slow in big companies. Change is slower when leaders aren't strong. Change is excrutiatingly, unbearably slow when leaders simply cannot lead. Read into that what you will.<br /><br /><span style="font-weight: bold;">Session #2: Mark Drew, Fresh Air with Aptana</span><br /><br />I've seen 3 or 4 presentations on AIR. Maybe b/c of seeing them, or maybe b/c mark drew was so great, this one was the best of the lot. Not the concept of AIR, but the "how to get started cranking out apps". I think seeing him go from scratch, through creating a fairly realistic app, is what made this one so good. For me, there's nothing like a "start at the start" kind of presentation to make an abstract thing more concrete. As a speaker, here's what you want: you want the audience to think "yeah, I could go home and get started with this right away", and that's what I felt after this session. When you present, you're not just competing for the hour during which your session is held, but for the limited free time that your attendees can commit to new things when they get back to work.<br /><br /><span style="font-weight: bold;">Host My Site Keynote:</span> I sat in the lounge room shooting the breeze with some of my new favorite people, Adam Haskell and the kroger crew (9-v Lauren, Tony, Bob, Steve, Dustin, and Chad). Hostmysite could've given away free beer and I'd still have had a better time doing exactly what i was doing.<br /><br />Without doubt the best thing about conferences, if you're not afraid of people, is meeting new people. I spent a lot of time with the wonderful gal and guys from kroger over the 3 days and am very thankful for that. If I have a regret it's that I didn't get to throw down those massive hotel beers with em. Maybe next time.<br /><br /><span style="font-weight: bold;">Session #3: End to end performance tuning.</span><br /><br />Honestly, it felt really rushed. I actually don't remember much useful stuff from the first part, but maybe that's because I was tired. I'm going to download the slides and review it again, though. One cool thing was hearing Nate Nelson talk about the stuff I'm absolutely clueless about, i.e. hardware.<br /><br />My 2-sentence summary: use trusted cache, index your databases, and the type of raid you choose will be a trade-off between redundancy and performance (unless you like blowing wads on raid-10). Exactly one week ago when hearing "raid 10" I would've responded like a drooling idiot. Thanks for showing me the light Nate.<br /><br /><span style="font-weight: bold;">Session #4: Patrick Quinn, Server Down and how to react.</span><br /><br />I had to leave about 15 minutes early to prepare for my presentation, so I missed the good stuff at the end. I really liked his notion of "crash patterns" though. This is one I'll be reviewing down the road as I try to ramp up with more load testing. One thing I'm still waiting for though is some smarty to finally tell me "here's how to find what's holding your memory". I haven't had this satisfactorily answered yet. Probably because it's not so cut and dry. But please, someone smart, can you please tell me how to figure this out? In 5 minutes? Otherwise, I'm going to have to get all Java Profiler on your ass, and you don't want that. When I open up YourKit and point it at CF, puppies die and angels lose their wings. For the good of the little children, please, answer this question.<br /><br /><span style="font-weight: bold;">Session #5: Me, Automating stuff with ANT</span><br /><br />I had a wonderful time. But damn I wanted to see Adam Howitt's presentation on amazon ec2, which was going on at the same time. Oh well... the things you suffer for your craft.<br /><br /><span style="font-weight: bold;">Friday, Session #1: Dan Wilson, Refactoring procedural to OO</span><br /><br />OK, I'm biased here. Within 10 minutes of meeting Dan Wilson at the speaker dinner on Tuesday, he offered me a beer (note to teratech: please, buck up for some drinks next time). The rest was history and if I lived in bumf**k north carolina, where dan lives, i think we'd be fast friends. that's a joke dan will get because I live in bumf**K pennsylvania, aka "pennsyltucky", so the degree of bumf**kness is debatable and kind of funny, in a "more people have been arrested for having sex with sheep in my state than yours, buddy" kind of way.<br /><br />Also, Dan was incredibly gracious by engaging my wife in conversation while i geeked out with mike brunt at the crime museum talking clustering beside the corpse. If it weren't for dan, I don't know who'd have been more bored, the dead guy or my wife.<br /><br />Anyway, dan's a very articulate speaker and had great examples. The contrast of the original kalendar app, in all its nasty spaghetti glory, with the newer OO version, was stark and told the story better than an hour lecture could have. I think I may ask dan for permission to use this presentation as the basis for some teaching/learning where I work. Or i might just steal it.<br /><br /><span style="font-weight: bold;">Session #2: Michael Collins, deploying into large scale environments.</span><br /><br />This one, I think, was just not what I was looking for. I was hoping for more practical examples of how to move all your code from a central place into 1000 servers during a production deployment. I didn't get that. That's not Michael's fault, just a difference in expectations. If you don't know what car files are, this was a good talk. One thing he did support, though, was keeping code on a SAN, which we're fighting for at work. So having someone from adobe say "yes, this is a good thing" will go far for me. He did talk about creating sort of a "reference" ear file that you could deploy. Now, I might just be a complete mouth-drooling douchebag, but if i have a farm of dozens/hundreds/thousands of servers, is moving a 200MB ear file across the network to all those servers realistic? i didn't think so either. maybe that's why he pushed shared/network code so much.<br /><br /><span style="font-weight: bold;">Session #3, Luis Majano, Intro to Coldbox</span><br /><br />Mine eyes have seen the glory, and it is coldbox!<br /><br />Holy freakin' crap. Tell you this, if you're going to do event-driven programming, you gotsta have an execution monitor. This framework feels like it was built by 10 people who want nothing more than to help you do your job kickass, day in, day out. I spoke with 5 or 6 different people right after the presentation, and the reaction was eerily the same: "this is the first framework that actually excites me and makes me want to use it". This will sound teh ghey, but during the presentation I was thinking of <a href="http://www.oopsla.org/oopsla2007/podcasts/invited-talks/keynote0102-kathy-sierra.mp3">kathy sierra's mantra</a> of "helping your users kick ass". Coldbox isn't "look how smart Luis is" (very). It's "look how good you can be".<br /><br />Very soon I'm going to have a (rare) opportunity to do a greenfield app at work, and I'll be putting coldbox to the test with it. This is the same feeling I had about Spring about 2 years ago, and I've not regretted that decision one bit.<br /><br /><span style="font-weight: bold;">Session #4: Mike Brunt, High-availability CF Clustering</span><br /><br />Bottom line, load test. I really liked mike's no-nonsense demo of watching a cluster work under load. He brought servers down, put them back online, and we saw the results. Also, mad props to kurt wiersma who, as an audience member, gave the 30-second explanation of how to do URL recording in JMeter (my load test app of choice). I never knew that!!! thanks Kurt. Although that app mike was using was pretty darn sweet.<br /><br /><span style="font-weight: bold;">Session #5: Elliot Sprehn, Internals of CF</span><br /><br />Um, uh. I'm no community fanboy, eagerly reading blogs all hours of the night, keeping my finger on every pulse and generally being "with it". But I read my fair share. Where the hell did this dude come from? A few weeks ago I see his name on Ben Nadel's blog, he's talking good smack, and I'm like "I gotta see this dude's talk at cfunited". So i switched to it on my schedule, and holy bejesus is this dude good. Outstanding talk. Note to the youngins in the crowd: you gotta work reeeeeeeeaaaaaaallll hard to get as good as elliot.<br /><br />I will not do his talk a disservice by attempting to sum up any of it. It's best seen from the source. But i will say that his point about "hey, people, this undocumented stuff ain't gonna change" is one I'm more than willing to listen to. But seriously, if you only have one hour to live, and you're not doing anything else, go watch this connect presentation. Fascinating.<br /><br /><span style="font-weight: bold;">Saturday, Session #1: Sean Corfield, Event Driven programming</span><br /><br />This was the only session I could catch on Saturday. I think some people might have seen it as an advertisement for edmund. I didn't. I thought he did a fine job of describing event-driven programming in the context of a real framework that supports it. I'm going to try to give more thought later to why exactly this presentation made sense to me, because by rights it should've confused the hell out of me, but it didn't. I think probably it's having spent so much time programming eclipse. Although I honestly believe if I get one thing out of this presentation it's a fresh perspective on eclipse plugin programming. Go figure.<br /><br />I'm still dubious about debugging event-driven apps, though, without a proper mechanism for doing precisely that, especially asynch events. This is why the coldbox execution monitor is so appealing, by the way. I've done "debugging through logging", and it's about as enjoyable as a pick-axe to the sphincter.<br /><br />Here's high praise, not that it means much coming from me but what the hell: on the way home after the talk, my wife asked me the wifey-est of wife questions: "are you ok? what's wrong?" nothing! I was just in real deep thought, driving slow up 295 thinking of sean's talk.<br /><br />Sunday, 6:30 - 8:30 PM: Monster 8-inch Gurkha shaggy cigar and glass of balvenie 12-year to celebrate the excellent week I had.<br /><br />This week: back to the daily grind, but reinvigorated and full of ideas.<br /><br />Thanks to all the presenters and great people I met. Truly a pleasure!<br /><br /><span style="font-weight: bold;">My Regrets</span><br /><br /><ul><li>Not meeting chris scott. I've read enough by and about chris scott to know he's the real deal. He seems like a "wild brain", a higher mind, and I wish I had the chance to talk to him and see how his mind works. I hope I get to do that some time.</li><li>Not having a better laptop for my presentation. See, I usually borrow a machine from work b/c they're badass, but they were all in use this week. So I had to use my wife's work laptop, which barely runs eclipse. Connect kills it dead, so I couldn't record my presentation while it was happening, and I can tell you it's far better live than when I deliver it without an audience.</li><li>Not taking pictures. What was I thinking? I wish I had a picture flexing with Ben Nadel. O'course, his arms are bigger than my head, but still, it'd have been cool.</li><li>Spending 10 bucks for a bottle of delirium tremens. Stoudts abbey triple, while not as refined and "complex" and sophisticated, is definitely just as enjoyable to my uncivilized pallate and at 36/case is far less expensive<br /></li></ul><br /><span style="font-weight: bold;">Next Steps</span><br /><br /><ul><li>Download all the slides from the presentations I went to and review them again in a week.</li><li>Reinstall that plugin-virus Aptana and start using the code samples.</li><li>Set up a load test for our flagship app. Nothing fancy, something simple. Just get started.</li><li>We bought seefusion a long time ago... get it set up on our production servers and not just development.</li><li>Have a talk with my bosses about getting more people involved next year.</li><li>Talk with my mentee at work about him presenting next year.</li><li>Pour a 40 on the curb for my dead homey cfeclipse</li><li>Find one large method in one component and break it down into methods of 6-lines. Decide: what'd I gain?</li><li>Find one component that's not tested because of dependency difficulty and put cfeasymock to work on it. Decide: what'd I gain?</li><li>Schedule a meeting with PM to watch Clark and Hal's presentation.</li><li>Check out mssqltips.com</li><li>Start thinking of a topic to present next year. I'm thinking "Java Profiling your CF Server".</li></ul><br /><br />Again, thanks to everyone I met, everyone who presented, and the fine folks at teratech for putting on the show!<br /><br />MarcMarc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-49944562528208494942008-06-13T13:49:00.002-04:002008-06-13T14:02:47.745-04:00My CFUnited ScheduleThis is the first time I've been to cfunited since 2002, when it was CFUN. I really wanted to go this year, and since I wasn't in time to get it on my company's budget for this year, I figured the only way I was going was if I were a speaker. So I'm giving an ANT presentation Thursday at 4. I'll be covering all the basics of packaging apps and deploying them, but also getting into neat stuff like using antlib for looping, scriptdef for string parsing, sql, etc. I'm really excited about presenting this topic, primarily because I see how much time ANT has saved us over the past year or so. I hope to channel the spirits of my cf ant heroes (Luis Majano, Mike Henke, Jim Priest) and give a kickass presentation. Plus, since it's the last presentation of the day, I'm free to stick around and talk more ant after the session (at the bar, with beers. beers and stogies, even better).<br /><br />OK, so enough advertisements. Here's what I'm going to this year. I'm going to try to stick around Saturday and attend some sessions, too, but I didn't sign up for any since I'm not sure yet.<br /><br /><div id="schedules" class="printable"> <div class="schedule-block" id="day-Wednesday"> <h3> <span>Wednesday, June 18, 2008</span> </h3> <div id="day-Wednesday-schedule"> <table class="data-table schedule" cellspacing="0"> <tbody><tr> <th class="start-time">Start</th> <th class="end-time">End</th> <th class="speaker">Speaker</th> <th class="title">Session</th> <th class="track">Track</th> </tr> <tr><td class="start-time">8:30AM</td> <td class="end-time">9:00AM</td> <td class="speaker">Michael Smith</td> <td class="title">TeraTech Keynote</td> <td class="track track-KEY">KEY</td></tr> <tr><td class="start-time">9:00AM</td> <td class="end-time">10:30AM</td> <td class="speaker">Ben Forta</td> <td class="title">Adobe Keynote Presentation</td> <td class="track track-KEY">KEY</td></tr> <tr><td class="start-time">11:00AM</td> <td class="end-time">12:00PM</td> <td class="speaker">Jeremy Kadlec</td> <td class="title">Building Advanced Search Capabilities for your web site with SQL Server</td> <td class="track track-DDT">DDT</td></tr> <tr class="special-session"><td class="start-time">12:00PM</td> <td class="end-time">1:30PM</td> <td class="speaker"><br /></td> <td class="title">Lunch</td> <td class="track track-"><br /></td></tr> <tr><td class="start-time">1:30PM</td> <td class="end-time">2:30PM</td> <td class="speaker">Bill Shelton</td> <td class="title">Patterns for ColdFusion Test Automation</td> <td class="track track-DDT">DDT</td></tr> <tr><td class="start-time">2:45PM</td> <td class="end-time">3:45PM</td> <td class="speaker">Hal Helms</td> <td class="title">Object Oriented Best Practices</td> <td class="track track-ADV">ADV</td></tr> <tr><td class="start-time">4:00PM</td> <td class="end-time">5:00PM</td> <td class="speaker">Phill Nacelli</td> <td class="title">Leveraging Basic Object Oriented Concepts in ColdFusion</td> <td class="track track-GCF">GCF</td></tr> <tr class="special-session"><td class="start-time">6:30PM</td> <td class="end-time">8:30PM</td> <td class="speaker"><br /></td> <td class="title">Networking Event</td> <td class="track track-"><br /></td></tr> <tr><td class="start-time">8:30PM</td> <td class="end-time">9:30PM</td> <td class="speaker">Mark Drew</td> <td class="title">Improving Quality through Code Reviews and Mentoring</td> <td class="track track-BOF">BOF</td></tr> </tbody></table> </div> </div> <div class="schedule-block" id="day-Thursday"> <h3> <span>Thursday, June 19, 2008</span> </h3> <div id="day-Thursday-schedule"> <table class="data-table schedule" cellspacing="0"> <tbody><tr> <th class="start-time">Start</th> <th class="end-time">End</th> <th class="speaker">Speaker</th> <th class="title">Session</th> <th class="track">Track</th> </tr> <tr><td class="start-time">8:15AM</td> <td class="end-time">9:15AM</td> <td class="speaker">Kurt Wiersma</td> <td class="title">Leveraging Popular ColdFusion Frameworks To Make Better Applications</td> <td class="track track-FWC">FWC</td></tr> <tr><td class="start-time">9:30AM</td> <td class="end-time">10:30AM</td> <td class="speaker">Mark Drew</td> <td class="title">Fresh AIR: Getting to grips with Aptana and AIR apps</td> <td class="track track-RIA">RIA</td></tr> <tr><td class="start-time">10:45AM</td> <td class="end-time">12:00PM</td> <td class="speaker">Lou Honick</td> <td class="title">HostMySite Keynote Presentation</td> <td class="track track-KEY">KEY</td></tr> <tr class="special-session"><td class="start-time">12:00PM</td> <td class="end-time">1:30PM</td> <td class="speaker"><br /></td> <td class="title">Lunch</td> <td class="track track-"><br /></td></tr> <tr><td class="start-time">1:30PM</td> <td class="end-time">2:30PM</td> <td class="speaker">Matthew Woodward</td> <td class="title">Real World Flex and ColdFusion</td> <td class="track track-RIA">RIA</td></tr> <tr><td class="start-time">2:45PM</td> <td class="end-time">3:45PM</td> <td class="speaker">Patrick Quinn</td> <td class="title">Server Down - How To Prevent It, And How To React To It</td> <td class="track track-DDT">DDT</td></tr> <tr><td class="start-time">4:00PM</td> <td class="end-time">5:00PM</td> <td class="speaker">Marc Esher</td> <td class="title">Automating the build/deployment process with ANT</td> <td class="track track-DDT">DDT</td></tr> <tr class="special-session"><td class="start-time">7:00PM</td> <td class="end-time">9:30PM</td> <td class="speaker"><br /></td> <td class="title">ColdFusion Celebration</td> <td class="track track-"><br /></td></tr> </tbody></table> </div> </div> <div class="schedule-block" id="day-Friday"> <h3> <span>Friday, June 20, 2008</span> </h3> <div id="day-Friday-schedule"> <table class="data-table schedule" cellspacing="0"> <tbody><tr> <th class="start-time">Start</th> <th class="end-time">End</th> <th class="speaker">Speaker</th> <th class="title">Session</th> <th class="track">Track</th> </tr> <tr><td class="start-time">8:30AM</td> <td class="end-time">9:30AM</td> <td class="speaker">Dan Wilson</td> <td class="title">Refactoring to Object Oriented Programming in ColdFusion</td> <td class="track track-ADV">ADV</td></tr> <tr><td class="start-time">9:45AM</td> <td class="end-time">10:45AM</td> <td class="speaker">Mike Nimer</td> <td class="title">Flex 3 For ColdFusion Developers</td> <td class="track track-RIA">RIA</td></tr> <tr><td class="start-time">11:00AM</td> <td class="end-time">12:00PM</td> <td class="speaker">Multiple Speakers</td> <td class="title">Demo Durby</td> <td class="track track-KEY">KEY</td></tr> <tr class="special-session"><td class="start-time">12:00PM</td> <td class="end-time">1:30PM</td> <td class="speaker"><br /></td> <td class="title">Lunch</td> <td class="track track-"><br /></td></tr> <tr><td class="start-time">1:30PM</td> <td class="end-time">2:30PM</td> <td class="speaker">Luis Majano</td> <td class="title">ColdBox Framework 101</td> <td class="track track-FWC">FWC</td></tr> <tr><td class="start-time">2:45PM</td> <td class="end-time">3:45PM</td> <td class="speaker">Mike Brunt</td> <td class="title">High Availability - Clustering ColdFusion</td> <td class="track track-DDT">DDT</td></tr> <tr><td class="start-time">4:00PM</td> <td class="end-time">5:00PM</td> <td class="speaker">Elliott Sprehn</td> <td class="title">Internals of the Adobe ColdFusion Server</td> <td class="track track-ADV">ADV</td></tr> <tr><td class="start-time">4:00PM</td> <td class="end-time">6:00PM</td> <td class="speaker"><br /></td> <td class="title"><br /></td> <td class="track track-"><br /></td></tr> </tbody></table> </div> </div></div><br />See you there!Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-70065874614416972542008-06-04T10:32:00.009-04:002008-06-05T05:00:24.061-04:00GIT with it<span style="font-size:100%;">Unlike Linus Torvalds, I won't call you stupid and ugly if you use CVS or Subversion. If you use VSS, you already know you are stupid and ugly - from 9-5/M-F </span><span style="font-weight: bold; font-style: italic;font-size:100%;" >I</span><span style="font-size:100%;"> am </span><span style="font-style: italic; font-weight: bold;font-size:100%;" >very</span><span style="font-size:100%;"> stupid and ugly for just this reason. And, the thought of being able to have a distributed network of trust and knowledge such that we can pull intellectual assets (source code and binaries) directly</span><span style="font-style: italic;font-size:100%;" > </span><span style="font-weight: bold; font-style: italic;font-size:100%;" >from each other</span><span style="font-size:100%;"> as opposed to a single repository is a fascinating concept and challenges me to think differently. It becomes more of a graph model for collaboration, or a web, rather than some more linear constraint where we are dealing with a single definitive reference for software resources. This is not so say the there doesn't need be some kind of centralization at times, but when a team is geographically dispersed, sometimes on-line, sometimes off, being able to build software and collaborate efficiently just makes sense.<br /><br />Git claims and delivers performance. It is fast. It's merging capabilities are supposed to be strong; and that's next on my list, as that, for me, can be the most painful part of source code management. The security built into git is also very good, from what this inexperienced hack can tell.<br /><br />I'm brand new to git but have set up a source tree for MXUnit at <a href="http://github.com/">GitHub</a>. I hope to keep it in sych with the existing main Subversion repository. Note that MXUnit has no plans yet to move to git, we're checking it out.<br /></span><span style="font-size:100%;"><br /><span style="font-weight: bold;">Git – Fast Version Control System</span><br /><a style="font-weight: bold;" href="http://git.or.cz/"><span style="font-weight: normal;">http://git.or.cz/</span></a><br /><br /><span style="font-weight: bold;">Git on MySys (Windows fork)</span><br /><a href="http://code.google.com/p/msysgit/">http://code.google.com/p/msysgit/</a><br /><br /></span><span style="font-weight: bold;font-size:100%;" >Google Tech Talk: Linus Torvalds on git</span><span style="font-size:100%;"><br /><a href="http://www.youtube.com/watch?v=4XpnKHJAok8">http://www.youtube.com/watch?v=4XpnKHJAok8</a><br /><br /></span><span style="font-weight: bold;font-size:100%;" >MXUnit pulic Git repository</span><span style="font-size:100%;"><br /><a href="http://github.com/virtix/mxunit/tree/master">http://github.com/virtix/mxunit/tree/master</a><br /><br />I'd be interested to hear about your experience with git ...<br /><br />thanks,<br />bill</span>billhttp://www.blogger.com/profile/06624894387927690246noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-86128406931811417152008-06-03T07:42:00.005-04:002008-06-03T08:25:19.100-04:00How I set up CFEasyMock<a href="http://cfeasymock.riaforge.org/">CFEasyMock</a> is the latest mock framework for CF. It's based on the popular <a href="http://www.easymock.org/">EasyMock framework for Java</a> and is quite a nice port from what I can tell. If you set it up exactly as the author has packaged it, and if you already have cfcunit installed, then you should have no problems getting started.<br /><br />If you're like me and aren't keen on the way it's distributed, and if you want to change CFEasymock's own unit tests to use MXUnit, here's what to do<br /><br /><span style="font-weight: bold;">Changing directory structure</span><br /><br />I don't like how it has 4 separate directories for a single framework. It's built like so:<br /><br /><ul><li>easymock</li><li>reflect</li><li>samples</li><li>test</li></ul>Why are samples and test outside of easymock? I don't know.<br />Why is reflect outside of easymock? because it's technically a separate project. OK, fair enough. I'll live with that (for now). So here's what I did:<br /><br /><ol><li> Download latest cfeasymock</li><li> copied reflect into my webroot</li><li> copied easymock into my webroot</li><li> copied test and sample into that easymock directory</li><li> copied easymock word doc into easymock directory</li><li> copied reflect word doc into reflect directory</li><li>created a new eclipse project for the easymock directory<br /></li></ol><span style="font-weight: bold;">Setting it up to use MXUnit</span><br /><br /><ol><li>In Eclipse, selected the "sample" and "test" directory</li><li>Hit CTRL-H to pop up the search box</li><li>enter org.cfcunit.framework.TestCase into the "Containing Text" box. </li><li>Filter on *.cfc</li><li>Select "Selected Resources" radio button if it's not already selected</li><li>Click Replace button</li><li>Enter mxunit.framework.TestCase into the "With" box. </li><li>Click "Replace All"</li></ol><br /><span style="font-weight: bold;">Running the tests in MXUnit</span><br /><br />As of now, all but two of the tests should go OK. The two that won't are in ResultTest.cfc because they use an assertion from CFCUnit that MXUnit doesn't copy verbatim, the "AssertClass" assertion. MXUnit has an AssertIsTypeOf(), though, so we'll just swap the two.<br /><br />In testCreateReturnResult, change the cfset assertClass.... to this:<br /><br />&lt;cfset assertIsTypeOf(returnResult, "easymock.Result", "value returned from createThrowResult() is not of type easymock.Result" ) /&gt;<br /><br />In testCreateThrowResult, change the cfset assertClass to this:<br /><br />&lt;cfset assertIsTypeOf(throwResult, "easymock.Result", "value returned from createThrowResult() is not of type easymock.Result" ) /&gt;<br /><br />Now, all tests should pass when you run them in MXUnit.<br /><br /><span style="font-weight: bold;">Running the "Samples"</span><br /><br />In the Samples directory there are two test cases: TestClassUnderTest and TestUser. If you run them in the directory structure we've just created, they'll fail with a "Could not find the ColdFusion Component or Interface "sample.User".... and so on. So we just need to prefix the "sample" with "easymock." to get the right component path.<br /><br /><ol><li>In Eclipse, highlight the "sample" directory</li><li>Hit CTRL-H, do a replace-all of "sample." with "easymock.sample."</li></ol><br />That'll get "TestUser" to run OK.<br /><br />From there, I tried running the TestClassUnderTest tests, but I got errors about "Could not convert the value of type class coldfusion.runtime.TemplateProxy to a boolean. And for now, I don't have the time to debug it. If I get some time I'll post back here.<br /><br />With that bit of cleanup out of the way, I'm ready to start easymocking. I look forward to having this tool in the toolbelt. As I get time, I hope to add more posts on using easymock in the real world.<br /><br />--marcMarc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-36816378153785942642008-05-18T15:56:00.003-04:002008-05-18T16:36:55.176-04:00Heading to Webmaniacs, and Bleeding for my ArtI know, the title is a tad histrionic. But hear me out: the <a href="http://www.rougehotel.com/index.html">hotel rouge</a> looks really, really cool. But then I looked at the parking rates: 20 bucks for day parking, and 30 bucks for overnight. WTF?!<br /><br />Still, even though the parking will bleed me, I'm super excited about the sessions, and about presenting our workshop on mxunit.<br /><br />I plan on getting into DC about 8:00 tonight (Sunday), then heading down to the hotel bar for a beer. DC is a great beer town so I hope the bar lives up to the city's reputation.<br /><br />So, if you're in the hotel bar tonight and see a short bald/blonde dude looking really surly about paying out the yingyang for parking.... Come drink a beer with me!Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-12850052810746892222008-04-23T12:26:00.004-04:002008-04-23T13:59:53.208-04:00Adventures in Mocking, Part 1For the longest time, I never used mocks in unit tests. I guess I never truly understood them. Today, I finally "got" it. Or, at least, I now get one slice of the pie.<br /><br />Here's a function I want to test. It's named rollBackFastPlanFromProduction:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>&lt;cffunction name="rollBackFastPlanFromProduction" output="false" access="public" hint="rolls back a fastplan from production if no orders are pending. Returns a struct with keys success and message." returntype="struct"&gt;<br />&lt;cfargument name="planid" required="true" type="numeric"&gt;<br />&lt;cfargument name="plandocid" required="true" type="numeric"&gt;<br />&lt;cfargument name="languageid" required="true" type="numeric"&gt;<br /><br />&lt;cfset var prevmsg = ""&gt;<br />&lt;cfset var msg = ""&gt;<br />&lt;cfset var s_result = StructNew()&gt;<br /><br />&lt;!--- Before doing anything make sure pending orders won't hold this request up... ---&gt;<br />&lt;cfset var OutstandingOrders = selOutstandingOrdersList(arguments.planid,arguments.plandocid,arguments.languageid)&gt;<br /><br />&lt;cfif len(trim(OutstandingOrders)) EQ 0&gt;<br /> &lt;!--- If no outstanding orders come back, proceed... ---&gt;<br /> &lt;cfset prevmsg = UpdatePlanStatus(PlanID=arguments.PlanID,PlanDocID=arguments.PlanDocID,PlanStatusID=2,checkPlanStatusChange=0)&gt;<br /> &lt;cfset msg = "Plan rolled back from Production. " &amp; ListLast(prevmsg,";")&gt;<br /> &lt;cfset s_result.success = true&gt;<br />&lt;cfelse&gt;<br /> &lt;cfset s_result.success = false&gt;<br /> &lt;cfif ListLen(OutstandingOrders) GT 1&gt;<br /> &lt;cfset msg = "Rollback unsuccessful! Orders with confirmation numbers: #OutstandingOrders# still unfulfilled!"&gt;<br /> &lt;cfelse&gt;<br /> &lt;cfset msg = "Rollback unsuccessful! Order with confirmation number: #OutstandingOrders# still unfulfilled!"&gt;<br /> &lt;/cfif&gt;<br />&lt;/cfif&gt;<br /><br />&lt;cfset s_result.message = msg&gt;<br />&lt;cfreturn s_result&gt;<br />&lt;/cffunction&gt;<br /></code></pre><br />This is pretty simple code: basically, see if there are any outstanding orders. If Not, run the updatePlanStatus function; otherwise, just set a message and return the result struct. (NOTE: this method of returning structs is not best practice but it follows a model we have at work and thus I'm choosing consistency in this case. )<br /><br />Ok, so, I want to test this code. All I want to know is:<br /><br /><ol><li>When there is a single outstanding order, does the update function NOT run, and does the struct return a false "success" key</li><li>When there are multiple outstanding orders, does that same behavior apply<br /></li><li>When there are no outstanding orders, does the update function run and return a true "success" key<br /></li></ol>That's pretty much it. I'm not interested in the particulars of the message. And I surely don't care about the particulars of the results of UpdatePlanStatus... that code may or may not be tested elsewhere but it's not this test function's job to test that.<br /><br />Right away, here are the problems:<br /><br /><ol><li>how to create the conditions for a single outstanding order</li><li>how to create the conditions for multiple outstanding orders</li><li>how to get the UpdatePlanStatus function to NOT do anything (it updates the DB, and I don't want to touch the DB in this test)</li><li>how to get all of this to work without needing real data</li></ol>This, in fact, is one of the core problems you seem to always run into in unit testing: how to set up your conditions without jumping through a million hoops.<br /><br />Back in the day, I'd have done this:<br /><ol><li>Go into the database and find an ID in the appropriate table that would give me a single outstanding order</li><li>Do the same for multiple outstanding orders</li><li>Find an ID for no outstanding orders</li></ol>Don't worry about the details: the point is that I'd have found data that matched the criteria I was trying to achieve and then, in the unit test, I would have literally used those IDs as arguments to my functions.<br /><br />This would NOT have solved my problem of not wanting to actually run any DB updates. It'd have just gotten the logic to work out.<br /><br />Well... it'd have gotten it to work out until the data in the DB changed. Then, all I'd have is a brittle test. The thing is that you want your tests to work all the time, even a month after you wrote them. Creating tests that depend on certain conditions in the DB itself are a sure way to brittle tests. I know because I've written dozens upon dozens of these kinds of tests.<br /><br />Now then, onto Mocks.<br /><br />For this post, I'm not going to use a mock framework. Just good old fashioned ColdFusion.<br /><br />Here are my goals:<br /><br /><ol><li>replace the UpdatePlanStatus function inside my object under test with a new version of the function that simply returns the string "updated".</li><li>replace the selOutstandingOrdersList function with 3 different functions, depending on what i'm trying to test. In one test, it'll return an empty string. In another test, it'll return a single number. And finally, it will return a comma-delimited list of numbers.</li></ol>First, my setUp function:<br /><br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>&lt;cffunction name="setUp" returntype="void" access="public" hint="put things here that you want to run before each test"&gt;<br />&lt;cfset pdd = createObject("component","#application.cfcroot#.business.PlanDataDelegate")&gt;<br />&lt;cfset pdd.init(dsn=application.maindsn)&gt;<br />&lt;!--- so we can easily mock! ---&gt;<br />&lt;cfset pdd.mixin = mixin&gt;<br />&lt;/cffunction&gt;<br /></code></pre>So we have an object we'll name "pdd".<br /><br />But the fun part here is this:<br /><br />set pdd.mixin = mixin<br /><br />WTF?<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> &lt;cffunction name="mixin" access="private"&gt;<br /> &lt;cfargument name="method" type="string" required="true"&gt;<br /> &lt;cfargument name="value" type="any" required="true"/&gt;<br /> &lt;cfset this[method] = value&gt;<br /> &lt;cfset variables[method] = value&gt;<br />&lt;/cffunction&gt;<br /><br /></code></pre><br /><br />There, that's better. I created a new, private function inside my test named "mixin". So, up in setUp, I'm attaching my new function to the pdd object.<br /><br />What mixin does is a tiny bit of CF magic. Mad props to Nathan Strutz, because without <a href="http://www.dopefly.com/techblog/entry.cfm?entry=227">this post</a> I'd still be banging my head against the wall. More on that later. To see what it does, let's look at the next chunk of code. A new, mock function:<br /><br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> &lt;cffunction name="updatePlanStatus" access="private"&gt;<br /> &lt;cfreturn "updated"&gt;<br />&lt;/cffunction&gt;<br /></code></pre><br />It's private so that MXUnit won't run it. All it does is return the string "updated". This means nothing until you look way back up top and see that the function under test makes a call to updatePlanStatus. And that's one of the functions I want to overwrite. So... how to get my new function into my pdd object? Mixin!<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> &lt;cffunction name="PlanRollBackShouldFailWithSingleOutstandingOrder" returntype="void" hint=""&gt;<br /> &lt;!--- mock em so we don't touch the DB ---&gt; <br /> &lt;cfset pdd.mixin("updatePlanStatus",updatePlanStatus)&gt;<br /> &lt;cfset pdd.mixin("selOutstandingOrdersList",selOutstandingOrdersListOneResult)&gt;<br /> &lt;cfset s_result = pdd.rollBackFastPlanFromProduction(1,1,1)&gt;<br /> &lt;cfset debug(s_result.message)&gt;<br /> &lt;cfset assertFalse(s_result.success,"msg was #s_result.message#")&gt;<br /> &lt;cfset assertTrue(NOT findNoCase("updated",s_result.message),"#s_result.message# should not contain updated")&gt;<br />&lt;/cffunction&gt;<br /></code></pre><br />See the call to pdd.mixin("updatePlanStatus",updatePlanStatus)? That's the magic. I'm overwriting the original function with my new braindead mock. This way, I don't touch the DB and I know if that function was run because if it is, it'll return the string "updated". But let's not worry about that right now, because it's honestly not important. The important part is that I've now got a really simple way to do mocking in CF when I need it.<br /><br />Note the next line in the function:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>pdd.mixin("selOutstandingOrdersList",selOutstandingOrdersListOneResult)</code></pre>You should now be wondering, "What's selOutstandingOrdersListOneResult"?<br /><br />Here it is, a new private function added to my test case:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> &lt;cffunction name="selOutstandingOrdersListOneResult" access="private"&gt;<br /> &lt;cfreturn 1111&gt;<br /> &lt;/cffunction&gt;<br /></code></pre><br />So, as you see, it just returns a number, 1111 in this case. Thus, in the test above, what I want to ensure is that when the rollbackFastPlanFromProduction function is called, and it calls selOutstandingOrdersList, that that function returns a single-element list.<br /><br />Guess what I do to test multiple orders? If you're thinking "Create a new function that returns a multi-element list, and mixin that function instead... you're catching on nicely.<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> &lt;cffunction name="selOutstandingOrdersListTwoResults" access="private"&gt;<br /> &lt;cfreturn "1111,2222"&gt;<br /> &lt;/cffunction&gt;<br /></code></pre><br />And then, my new test:<br /><br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code> &lt;cffunction name="PlanRollBackShouldFailWithMultipleOutstandingOrders" returntype="void" hint=""&gt;<br /> &lt;!--- mock em so we don't touch the DB ---&gt; <br /> &lt;cfset pdd.mixin("updatePlanStatus",updatePlanStatus)&gt;<br /> &lt;cfset pdd.mixin("selOutstandingOrdersList",selOutstandingOrdersListTwoResults)&gt;<br /> &lt;cfset s_result = pdd.rollBackFastPlanFromProduction(1,1,1)&gt;<br /> &lt;cfset debug(s_result.message)&gt;<br /> &lt;cfset assertFalse(s_result.success,"msg was #s_result.message#")&gt;<br /> &lt;/cffunction&gt;<br /></code></pre><br />Now, I said I'd get back to why I'm thanking Nathan Strutz. I started this test with really simple code I thought would work:<br /><br />set pdd.updatePlanStatus = updatePlanStatus<br /><br />i.e. I had my new, private updatePlanStatus function and I wanted to overwrite the existing version in the object. Pretty simple, right? and when I called pdd.updatePlanStatus() directly, it did indeed return the string "updated". However, when the rollbackFastPlanFromProduction() function called updatePlanStatus, it ran the original code. <br /><br />Normally, I'm pretty good at figgerin' out this stuff. But after a half hour or so, I went all last-resort: I googled it. I don't know how, but I ran into Nathan's post, and he discusses what I honestly didn't know: that when functions call each other internally, they're calling the version of the function that lives in the variables scope. And when you, dear programmer, call a function on an object directly, you're calling it in this scope. So what I did not understand was that functions had two lives, so to speak: a life in variables scope, and a life in this scope. When I was overwriting the function using the direct cfset, I was overwriting it in the external -- this -- scope. But not the internal one.<br /><br />This brings me to the mixin function: the key line is this one:<br /><br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>&lt;cfset variables[method] = value&gt;</code></pre>This little beaut right here overwrites the internal life of the function, and that's how I can create the mocks.<br /><br />So, thanks Nathan!<br /><br />Now, I leave you with some thoughts:<br /><br /><ol><li>this mixin business seems pretty handy, and I'd probably want to do it in all kinds of objects. Am I going to have to copy that function into every testcase? </li><li>And wouldn't it be nice to NOT have to create those simple little private functions? </li><li>And wouldn't it be nice to say "Hey, object, in this case, when you had a single outstanding order, did you NOT run that updatePlanStatus function? And in that other case, where you had no outstanding orders, did you run it?</li></ol>This is where a framework for creating mock objects becomes valuable. And that's where I'll pick up next.<br /><br />Until next time... happy testing.Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-21711208911676457252008-04-17T12:58:00.004-04:002008-04-17T13:39:00.749-04:00Great example of Hard-to-Test CodeWhen we talk about designing for testability, it seems pretty abstract. We talk about "encapsulation", like everyone knows what it means. Here's an example of code i just stumbled into that demonstrates not being designed for testability and not being encapsulated<br /><br />Our guest of honor, the function:<br /><br />&lt;cffunction name="getAllUserPlanDocs" hint="returns all plandocs user has permission to" returntype="query" output="false"&gt;<br /> &lt;cfset var plandocs=""&gt;<br /> &lt;cfset var safename=""&gt;<br /><br /> &lt;cfquery name="planDocs" datasource="#dsn#"&gt;<br /> select BunchOfStuff<br /> FROM planDocuments pd<br /> LEFT JOIN PlanDocGroups pdg on pdg.DocGroupID=pd.DocGroupID<br /> WHERE pd.PlanDocID IN (#session.user.getUserPlanDocList()#) <br /> ORDER BY pd.SortOrder,pd.PlanDocName<br /> &lt;/cfquery&gt; <br /><br /> &lt;cfreturn planDocs&gt; <br />&lt;/cffunction&gt;<br /><br />Now, bear in mind, this is probably 3 year old code. I haven't checked source control, but I very likely wrote this code. Who knows? Only source control, and I can't handle the truth right now.<br /><br />So, back then, we weren't doing any unit testing at all. In fact, this is the first time I've written a test for the component to which this function belongs, so I fired that up.<br /><br /> &lt;cfcomponent extends="some.parent.testcase"&gt;<br /> &lt;cffunction name="setUp"&gt;<br /> &lt;super.setUp()&gt;<br /> &lt;/cffunction&gt;<br /><br /> &lt;cffunction name="wizardPlanDocShouldNotBeVisibleUserPlanDoc"&gt; <br /> &lt;cfset q_pd= plan.getAllUserPlanDocs()&gt;<br /> &lt;cfset debug(q_pd)&gt;<br /> &lt;/cffunction&gt;<br /> &lt;/cfcomponent&gt;<br /><br />(Note: I am not an advocate of using a generator to jam out stub tests for existing, legacy code. That's why this is the first test i'm writing for this particular component).<br /><br />All I'm doing here is writing a little test and dumping out the query because I want to see my data first. I just like that approach. I am fully expecting just to get a few-row cfquery dump. So, I go to run this function, and I get an error:<br /><br />"Incorrect syntax near )"<br /><br />WTF?<br /><br />So I open the file from the stack trace view in the plugin, and it takes me to this line in the query:<br /><br />WHERE pd.PlanDocID IN (#session.user.getUserPlanDocList()#)<br /><br />Son of a bitch.<br /><br />This, dear readers, is why people always say "don't use persistent scopes in your code". This violates encapsulation. Because now, the component under test needs to know about some other component in the session scope. Which, obviously, my simple little test doesn't have.<br /><br />So now, my simple little test has to grow, probably by 2x or more, because now i gotta get a session.user object set up and make sure it returns an appropriate list so that line of the query stops failing.<br /><br />But... I'm not going to get mad at myself for writing shitty code 3 years ago. It happens.<br /><br />Here's the lesson, which I'm writing to myself today to remind myself: When you design test-first, or design to make things easier to test, you'd probably not write code like this. Instead, you'd write the function such that it took the User's plandoc list, or maybe even took in a user object. This way, you inject the data you need into the function rather than have the function need to look outside the component for it (in this case, it's looking out into a session scope object).<br /><br />You might be thinking "but if you create the user object to inject in, then that's pretty much the same amount of work, isn't it?" Yes, yes it is.<br /><br />Right now, I'm not at liberty to change this code. Too many touchpoints and I'm not gonna go find them all. So I'm just going to have to make an uglier test case in order to test this functionalty.<br /><br />Coming to think of this, this is actually a good place where mocks are helpful. And I don't mean even using a mock framework or anything like that. I'm talking low-rent ghetto mocks you can use thanks to the glory of CF and mix-in methods. To wit:<br /><br /> &lt;cffunction name="wizardPlanDocShouldNotBeVisibleUserPlanDoc"&gt;<br /> &lt;!--- function under test uses user.getUserPlanDocList ---&gt;<br /> &lt;cfset session.user = createObject("component","com.argus.framework.retail.business.User")&gt;<br /> &lt;!--- I don't want to have to authenticate the user or go through all the rigamarole... so I'm mocking it<br /> just trump the getUserPlanDocList function with one I'm creating right inside this test case ---&gt;<br /> &lt;cfset session.user.getUserPlanDocList = getUserPlanDocList&gt;<br /> &lt;cfset q_pd = request.plan.getAllUserPlanDocs(71)&gt;<br /> &lt;cfset debug(q_pd)&gt;<br /> &lt;/cffunction&gt;<br /> <br /> &lt;cffunction name="getUserPlanDocList" access="private" hint="I'll use this as a mix-in to mock the getUserPlanDocList function on the user object"&gt;<br /> &lt;cfreturn "1,2,3,4,5,6,7,8,9,10"&gt;<br /> &lt;/cffunction&gt;<br /><br />So here, I just created a new private function inside my testcase that returns a string. I then mix that function into the user object, thereby trumping the one that is in there for real. This way, I can create that static list I would've created anyway to test this code rather than worry about finding a user in the database whose set of permissions, etc will match the conditions I'm trying to recreate.<br /><br />Thank you, ColdFusion. Couldn't have done that in Java. (I know, I know... EZMock).<br /><br />So, my problem is largely solved. When I get the time, I'll get Mike Steele's CFEasyMock set up here at work and I'll swap out my little ghetto mock with a proper mock. Not because it'll change the behavior, but because the intentions will be clearer. It's much easier to udnerstand what I'm doing here if you see "user = CreateMock()......andReturn("1,2,3,4,5,6") " instead of this mix-in approach.<br /><br />There you have it. Testing unencapsulated legacy code, how to avoid such stuff, and how testing could help you avoid this kind of design problem in the first place.<br /><br />Enjoy.Marc Esherhttp://www.blogger.com/profile/05942611191966201181noreply@blogger.comtag:blogger.com,1999:blog-1973750947775262558.post-60708376148807971162008-04-11T15:47:00.004-04:002008-04-11T15:55:55.621-04:00MXUnit at CFUnited 2008Not only a great excuse to get out of work, walk down the street, and hang out with y'all, but a great way to really learn some cool <span class="blsp-spelling-error" id="SPELLING_ERROR_0">ColdFusion</span> stuff. Oh yeah, we plan to show off some cool stuff, t