<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Denis Hennessy</title>
  <link href="http://hennessynet.com/atom.xml" rel="self" />
  <link href="http://hennessynet.com/" />
  <updated>2009-11-29T00:24:30+00:00</updated>
  <id>http://hennessynet.com/</id>

  <author>
    <name>Martin Andert</name>
    <email>martin@mehringen.de</email>
  </author>

  
    <entry>
      <title>Unit Testing on the IPhone</title>
      <link href="http://hennessynet.com/2009/05/24/unit-testing-on-the-iphone.html" />
      <updated>2009-05-24T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2009/05/24/unit-testing-on-the-iphone</id>
      <content type="html">&lt;p&gt;Writing unit tests for an IPhone application is unfortunately a bit more involved than it need be. Here’s how you can get it going for your project (it’s really worth your while doing this at the start of the project, and writing tests as you implement new features but you already know that, right?).&lt;/p&gt;

&lt;p&gt;Google have a project called Google Toolbox for Mac located at &lt;a href='http://code.google.com/p/google-toolbox-for-mac'&gt;http://code.google.com/p/google-toolbox-for-mac&lt;/a&gt;. Download the latest version and unpack it directly into your project. Rename the directory to &lt;strong&gt;google-toolbox-for-mac&lt;/strong&gt; (it will make it easier to follow along).&lt;/p&gt;

&lt;p&gt;Open your project in XCode and then choose &lt;code&gt;Project&lt;/code&gt; &amp;gt; &lt;code&gt;New Target...&lt;/code&gt; Pick Cocoa Touch Application as the template and name the target &lt;strong&gt;Unit Tests&lt;/strong&gt;. Choose &lt;code&gt;Project&lt;/code&gt; &amp;gt; &lt;code&gt;Set Active Target&lt;/code&gt; &amp;gt; &lt;code&gt;Unit Tests&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Choose &lt;code&gt;Project&lt;/code&gt; &amp;gt; &lt;code&gt;Add to Project...&lt;/code&gt; and pick the following files (make sure each are added to the &lt;strong&gt;Unit Tests&lt;/strong&gt; target):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestMain.m
google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestDelegate.m
google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestDelegate.h
google-toolbox-for-mac/UnitTesting/GTMSenTestCase.m
google-toolbox-for-mac/UnitTesting/GTMSenTestCase.h
google-toolbox-for-mac/GTMDefines.h
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Right-click on Unit Tests in the Targets group and choose &lt;code&gt;Add&lt;/code&gt; &amp;gt; &lt;code&gt;New Build Phase&lt;/code&gt; &amp;gt; &lt;code&gt;New Run Script Build Phase&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Paste this text into the Script box:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;${&lt;/span&gt;&lt;span class='nv'&gt;INPUT_FILE_DIR&lt;/span&gt;&lt;span class='k'&gt;}&lt;/span&gt;/google-toolbox-for-mac/UnitTesting/RunIPhoneUnitTest.sh
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Try a build now (&lt;code&gt;Cmd-B&lt;/code&gt;) and look in the build log. You should see a line like this:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;Executed 0 tests, with 0 failures (0 unexpected) in 0.002 (0.002) seconds
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Switch back to your normal target and verify that it still builds OK. This is probably a good time to commit everything into source control.&lt;/p&gt;

&lt;h3 id='adding_a_unit_test'&gt;Adding a unit test&lt;/h3&gt;

&lt;p&gt;Choose &lt;code&gt;File&lt;/code&gt; &amp;gt; &lt;code&gt;New File...&lt;/code&gt; and select &lt;strong&gt;Objective-C test case class&lt;/strong&gt; from the Mac OS X Cocoa group. Call it &lt;strong&gt;FooTest.m&lt;/strong&gt; if you’re testing a Foo class. Edit the FooTest.h file and replace the existing #import line with:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='cp'&gt;#import &amp;quot;GTMSenTestCase.h&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That’s it! Add individual test methods and run the tests by doing a build (&lt;code&gt;Cmd-B&lt;/code&gt;)&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Sending emails in the background with cron and rake</title>
      <link href="http://hennessynet.com/2009/04/16/sending-emails-in-the-background-with-cron-and-rake.html" />
      <updated>2009-04-16T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2009/04/16/sending-emails-in-the-background-with-cron-and-rake</id>
      <content type="html">&lt;p&gt;Many web applications have to run periodic tasks and it’s really important to decouple these tasks from the clients request thread. One well-proven way to manage these is to use cron to execute the tasks. I had to do this recently to send reminder emails to authors in &lt;a href='http://statushub.com'&gt;StatusHub&lt;/a&gt; and thought I’d share how easy it was.&lt;/p&gt;

&lt;p&gt;First, create a rake task to provide an easy interface to the tasks. This makes it easy to test them out or manually run them. I created a file in lib/tasks called cron.rake:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;namespace&lt;/span&gt; &lt;span class='ss'&gt;:cron&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
  &lt;span class='n'&gt;desc&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Send email reminders to all authors on upcoming and overdue reports&amp;quot;&lt;/span&gt;
  &lt;span class='n'&gt;task&lt;/span&gt; &lt;span class='ss'&gt;:send_reminders&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='ss'&gt;:environment&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt;
    &lt;span class='no'&gt;Report&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;send_reminders&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here, Report is one of my models and send_reminders is a class method on that:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nc'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='nf'&gt;send_reminders&lt;/span&gt;
  &lt;span class='no'&gt;Report&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;due_drafts&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find_each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
    &lt;span class='no'&gt;Mailer&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;deliver_report_due&lt;/span&gt; &lt;span class='n'&gt;report&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The Mailer model is a subclass of ActionMailer::Base and has methods for each type of message you want to send. This one looks like:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;report_due&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='n'&gt;recipients&lt;/span&gt; &lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;author&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;email&lt;/span&gt;
  &lt;span class='n'&gt;from&lt;/span&gt;       &lt;span class='s2'&gt;&amp;quot;StatusHub&amp;lt;support@statushub.com&amp;gt;&amp;quot;&lt;/span&gt;
  &lt;span class='n'&gt;subject&lt;/span&gt;    &lt;span class='s2'&gt;&amp;quot;Your report entitled &amp;#39;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;title&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;#39; is &lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;overdue?&lt;/span&gt; &lt;span class='p'&gt;?&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;overdue&amp;#39;&lt;/span&gt; &lt;span class='p'&gt;:&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;due&amp;#39;&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;
  &lt;span class='n'&gt;sent_on&lt;/span&gt;    &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;
  &lt;span class='n'&gt;content_type&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;
  &lt;span class='n'&gt;body&lt;/span&gt;       &lt;span class='ss'&gt;:report&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:url&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;design_report_url&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:host&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;report&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;author&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;account&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;host&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Calling &lt;code&gt;Mailer.deliver_report_due&lt;/code&gt; invokes the &lt;code&gt;report_due&lt;/code&gt; method and then renders &lt;code&gt;report_due.html.erb&lt;/code&gt; into the body of the email. You can now test the task with:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='err'&gt;$&lt;/span&gt; &lt;span class='n'&gt;rake&lt;/span&gt; &lt;span class='n'&gt;cron&lt;/span&gt;&lt;span class='ss'&gt;:send_reminders&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On the production server, the job can now be automated by adding the following like to your crontab (edit with &lt;code&gt;crontab -e&lt;/code&gt;):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;00 04 * * * cd /app/statushub/current &amp;amp;&amp;amp; /usr/bin/rake RAILS_ENV=production cron:send_reminders
&lt;/pre&gt;
&lt;/div&gt;</content>
    </entry>
  
    <entry>
      <title>Dressing for your wireframes</title>
      <link href="http://hennessynet.com/2009/03/08/dressing-for-your-wireframes.html" />
      <updated>2009-03-08T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2009/03/08/dressing-for-your-wireframes</id>
      <content type="html">&lt;p&gt;I was at the &lt;a href='http://events.carsonified.com/fowa/2009/dublin/content'&gt;Future of Web Apps Dublin&lt;/a&gt; conference on Friday and a one of the speakers (I think it was Des from &lt;a href='http://contrast.ie/'&gt;contrast.ie&lt;/a&gt;) showed a screen from a wireframing tool called &lt;a href='http://www.balsamiq.com/'&gt;Balsamiq&lt;/a&gt;. Here are my thoughts after using it for a little while…&lt;/p&gt;

&lt;p&gt;At its heart, Balsamiq is a vector drawing tool with a library of built in ’smart’ shapes like OmniGraffle or Visio. The key difference is that instead of having realistic-looking controls to place on the page, the controls are drawn in a cartoony style. A typical page looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/mytube.gif' alt='' /&gt;&lt;/p&gt;

&lt;p&gt;The fact that the design is not pixel-perfect is actually a huge time saver. I’ve often started in OmniGraffle with the intent of just sketching out how the page should be laid out or how the user should interact with it, and then ended up many hours later tweaking colours and fonts and alignment because I can’t ignore the fact that they’re ‘wrong’. Well, in Balsamiq, the effect is much more like using a whiteboard – when’s the last time you rubbed out what you just wrote on a whiteboard because the font was wrong??&lt;/p&gt;

&lt;p&gt;The timesaving in the design of a page also carries over to group review of the page. The fact that it’s ‘just a wireframe’ forces people to evaluate the design at that level.&lt;/p&gt;

&lt;p&gt;In terms of negatives, the only gripe I have is that the interface is written using Adobe AIR which means that it definitely has a non-native feel to it. Nothing fatal, just a bit jarring in places.&lt;/p&gt;

&lt;p&gt;Updates: Someone also recommended trying Konigi stencils, found here: &lt;a href='http://konigi.com/tools/omnigraffle-wireframe-stencils'&gt;http://konigi.com/tools/omnigraffle-wireframe-stencils&lt;/a&gt;&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Adding SSL support for signup and login to a Rails app</title>
      <link href="http://hennessynet.com/2008/12/12/adding-ssl-support-for-signup-and-login-to-a-rails-app.html" />
      <updated>2008-12-12T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2008/12/12/adding-ssl-support-for-signup-and-login-to-a-rails-app</id>
      <content type="html">&lt;p&gt;First, get an SSL Cert. In a quick search, GoDaddy seem to be one of the cheaper options at $29.99/year. Pick the ‘Standard SSL’ option.&lt;/p&gt;

&lt;p&gt;Important: When prompted for a passphrase, just press enter. Otherwise you’ll have to enter it every time you start apache.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;openssl genrsa -des3 -out clikboard.key 1024
&lt;span class='nv'&gt;$ &lt;/span&gt;openssl req -new -key clikboard.key -out clikboard.csr
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;On the GoDaddy site, click on the certificate you’ve just bought and you will be taken to a screen where you confirm the contact details and a place to host the certificate signing request. Paste the contents of clikboard.csr into this area. Click next and shortly afterwards you’ll receive an email with a link to the certificate zipfile (containing, in my case, 2 files – &lt;code&gt;gd_bundle.crt&lt;/code&gt; and &lt;code&gt;clikboard.com.crt&lt;/code&gt;). Copy these onto your production server.&lt;/p&gt;

&lt;p&gt;On the server, edit &lt;code&gt;/etc/apache2/sites-available/clikboard&lt;/code&gt; (replace this with your site config) to make a copy of the &lt;strong&gt;VirtualHost&lt;/strong&gt; section and replace the :80 with :443 and add the following lines:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nb'&gt;SSLEngine&lt;/span&gt; &lt;span class='k'&gt;on&lt;/span&gt;
&lt;span class='nb'&gt;SSLCertificateFile&lt;/span&gt; &lt;span class='sx'&gt;/home/denis/ssl/clikboard.com.crt&lt;/span&gt;
&lt;span class='nb'&gt;SSLCertificateKeyFile&lt;/span&gt; &lt;span class='sx'&gt;/home/denis/ssl/clikboard.key&lt;/span&gt;
&lt;span class='nb'&gt;SSLCertificateChainFile&lt;/span&gt; &lt;span class='sx'&gt;/home/denis/ssl/gd_bundle.crt&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, enable SSL and restart apache:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;sudo a2enmod ssl
&lt;span class='nv'&gt;$ &lt;/span&gt;sudo /etc/init.d/apache2 force-reload
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Verify that the site now works with both http and https urls. Of course, all this is apache stuff and really nothing to do with rails. The last part of getting it working with your rails app is actually trivial:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;./plugin/install ssl_requirement
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, follow the instructions in &lt;code&gt;vendor/plugins/ssl_requirement/README&lt;/code&gt;.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Creating a Logo for your web 2.0 service</title>
      <link href="http://hennessynet.com/2008/12/04/creating-a-logo-for-your-web-2-0-service.html" />
      <updated>2008-12-04T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2008/12/04/creating-a-logo-for-your-web-2-0-service</id>
      <content type="html">&lt;p&gt;As we were approaching the launch date for &lt;a href='http://clikboard.com'&gt;http://clikboard.com&lt;/a&gt; (at least once we were adding items to the Lighthouse list slower than we were implementing them), we realised that we needed a cool logo that people would remember and associate with the site. After briefly flirting with the idea drawing it ourselves, we decided to go with an outside designer.&lt;/p&gt;

&lt;p&gt;I had heard of design contests before and thought this would be an interested excuse to try them out. Here’s what happened…&lt;/p&gt;

&lt;p&gt;First, I picked &lt;a href='http://99designs.com'&gt;http://99designs.com&lt;/a&gt; to run the contest. They’re pretty new but have a clean, transparent service and have a good number of designers available. The contests are fixed to run for exactly one week which seemed perfect. The cost for running a contest is $39 (assuming you don’t add any extras). You have to set a prize amount which is what you pay for the winning design. I set it to $300 which seemed typical.&lt;/p&gt;

&lt;p&gt;Second, I wrote a design brief. This is pretty important because you have to give a design something to start from. The way it seems to work is that they come up with concepts from the brief, and then evolve the designs based on your feedback.&lt;/p&gt;

&lt;p&gt;Once the contest was launched, we had the first designs within hours! We realised that it was really important to send meaningful feedback as quickly as possible so that the designers could act on it. Because the designers were from all over the world, new designs were coming in right around the clock!&lt;/p&gt;

&lt;p&gt;By the end of the week, we had 45 designs. You can see them at our &lt;a href='http://99designs.com/contests/13773'&gt;contest page&lt;/a&gt; – In the end we went with this design (it captured the ‘click’ and ‘board’ part best and has a distinctive favicon):&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/medium-logo.png' alt='logo' /&gt;&lt;/p&gt;

&lt;p&gt;Overall, the experience was very positive. In the past I’ve worked with designers where you just don’t ‘click’ (no pun) and it’s hard to get him to understand what you’re looking for. By working with a group and scoring the designs as you go, it seems easier to steer the design and get what you want.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Switching rails apps from sqlite to mysql</title>
      <link href="http://hennessynet.com/2008/10/05/switching-rails-apps-from-sqlite-to-mysql.html" />
      <updated>2008-10-05T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2008/10/05/switching-rails-apps-from-sqlite-to-mysql</id>
      <content type="html">&lt;p&gt;Since Rails 2.0.2, Sqlite has been the default database for new projects. This is great to get you started but at some stage you’re likely to want to move to a bigger DB like mysql or postgres. Here’s the steps involved:&lt;/p&gt;

&lt;p&gt;Edit your &lt;code&gt;config/database.yml&lt;/code&gt; file and make it look like this (change the database names, username and passwords to match your environment):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;development:
  adapter: mysql
  encoding: utf8
  database: myapp_development
  username: root
  password:
  socket: /tmp/mysql.sock

test:
  adapter: mysql
  encoding: utf8
  database: myapp_test
  username: root
  password:
  socket: /tmp/mysql.sock

production:
  adapter: mysql
  encoding: utf8
  database: myapp_production
  username: root
  password:
  socket: /tmp/mysql.sock
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, recreate your databases and tables:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;rake db:create:all
&lt;span class='nv'&gt;$ &lt;/span&gt;rake db:migrate
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That’s it!&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Making sure rspec + rcov report on all your code</title>
      <link href="http://hennessynet.com/2008/10/01/making-sure-rspec-rcov-report-on-all-your-code.html" />
      <updated>2008-10-01T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2008/10/01/making-sure-rspec-rcov-report-on-all-your-code</id>
      <content type="html">&lt;p&gt;One of the things that easily catches people out with rcov is that it doesn’t show you the coverage of all your code, it just shows the coverage of the code touched by your tests. So, if you completely forget to add tests for a new controller, that won’t appear in your coverage report.&lt;/p&gt;

&lt;p&gt;Here’s an easy fix: save the following code as &lt;code&gt;spec/app_spec.rb&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dirname&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;__FILE__&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;/spec_helper&amp;#39;&lt;/span&gt;

&lt;span class='n'&gt;dir&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;dirname&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='bp'&gt;__FILE__&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='no'&gt;Dir&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='no'&gt;File&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;expand_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;dir&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;/../app/**/*.rb&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;].&lt;/span&gt;&lt;span class='n'&gt;uniq&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;each&lt;/span&gt; &lt;span class='k'&gt;do&lt;/span&gt; &lt;span class='o'&gt;|&lt;/span&gt;&lt;span class='n'&gt;file&lt;/span&gt;&lt;span class='o'&gt;|&lt;/span&gt;
  &lt;span class='nb'&gt;require&lt;/span&gt; &lt;span class='n'&gt;file&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;It simply includes all the ruby files under your /app directory so that they’re reported properly by rcov.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Grouped Links in a Rails application</title>
      <link href="http://hennessynet.com/2008/09/26/grouped-links-in-a-rails-application.html" />
      <updated>2008-09-26T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2008/09/26/grouped-links-in-a-rails-application</id>
      <content type="html">&lt;h3 id='background__grouped_links'&gt;Background – grouped links&lt;/h3&gt;

&lt;p&gt;Normally, when people talk about paging in a web application, they mean providing next/previous links and direct links to particular page numbers. Here’s some examples you should be familiar with:&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/digg.png' alt='Digg' /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/google.png' alt='Google' /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/flickr.png' alt='Flickr' /&gt;&lt;/p&gt;

&lt;p&gt;This style of paging is supported very well by the &lt;strong&gt;will_paginate&lt;/strong&gt; plugin. However, some types of information don’t readily lend themselves to this type of paging. When listing people, for instance, it’s natural to want to jump directly to the ‘M’ names, just like you would with a telephone directory. For this type of information, what you want is a set of links that take you directly to the page with the first occurrence of a particular letter:&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/letters.png' alt='Letters' /&gt;&lt;/p&gt;

&lt;p&gt;The greyed letters indicate that there are no entries beginning with that letter.&lt;/p&gt;

&lt;p&gt;I’ve needed this for a recent project and implemented it as a patch to &lt;strong&gt;will_paginate&lt;/strong&gt;, it’s available at &lt;a href='http://github.com/dhennessy/will_paginate/tree/master'&gt;http://github.com/dhennessy/will_paginate/tree/master&lt;/a&gt; for anyone who feels like trying it out.&lt;/p&gt;

&lt;h3 id='adding_grouped_links_to_an_existing_project'&gt;Adding grouped links to an existing project&lt;/h3&gt;

&lt;p&gt;The grouped links plugin depends on you having a grouping attribute on the data you want to display. For the example above, this might be the first letter of the name; for a recipe application, it might be the primary ingredient. The point is that this has to exist in the database as a separate column. One easy way to maintain this is to add a &lt;strong&gt;before_save&lt;/strong&gt; filter to your model that computes the grouping attribute:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;before_save&lt;/span&gt; &lt;span class='ss'&gt;:update_group_letter&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;update_group_letter&lt;/span&gt;
  &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;first_letter&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='nb'&gt;name&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In your controller, you create a list of results as follows:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='vi'&gt;@people&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Person&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;paginate&lt;/span&gt; &lt;span class='ss'&gt;:page&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='n'&gt;params&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:page&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:group_by&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;first_letter&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;  &lt;span class='ss'&gt;:order&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;name ASC&amp;#39;&lt;/span&gt;
&lt;span class='vi'&gt;@people&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;add_missing_links&lt;/span&gt;&lt;span class='p'&gt;((&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;.&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;Z&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_a&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;Normally, the paginate method will add a grouped link for every distinct value of the &lt;strong&gt;group_by&lt;/strong&gt; attribute. The &lt;strong&gt;add_missing_links&lt;/strong&gt; method in this example will add additional links for letters that don’t already exist (which looks better in this case).&lt;/p&gt;

&lt;p&gt;Finally, add the following to your view:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='cp'&gt;&amp;lt;%=&lt;/span&gt; &lt;span class='n'&gt;will_paginate&lt;/span&gt; &lt;span class='vi'&gt;@people&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:draw_if_single&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='kp'&gt;true&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:next_previous_links&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='kp'&gt;false&lt;/span&gt; &lt;span class='cp'&gt;%&amp;gt;&lt;/span&gt;&lt;span class='x' /&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;The draw_if_single option forces the links to be displayed, even if the results all fit on a single page. Note that the resulting links will have #A, #B, etc appended to them so that you can jump to a particular row (providing you set the anchor when the first letter changes in your list). As you can see, this is very similar to the way you’re already using the &lt;strong&gt;will_paginate&lt;/strong&gt; plugin.&lt;/p&gt;

&lt;p&gt;Health Warning: this plugin should probably be treated as experimental as I may change things a bit as I get feedback from real users.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Adding rspec to a new rails project</title>
      <link href="http://hennessynet.com/2008/08/20/adding-rspec-to-a-new-rails-project.html" />
      <updated>2008-08-20T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2008/08/20/adding-rspec-to-a-new-rails-project</id>
      <content type="html">&lt;p&gt;There aren’t as many quick guides to using rspec as there should be. So, to help anyone wanting a fast jumpstart, here’s how to add rspec to a new project:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;./script/plugin install git://github.com/dchelimsky/rspec.git
&lt;span class='nv'&gt;$ &lt;/span&gt;./script/plugin install git://github.com/dchelimsky/rspec-rails.git
&lt;span class='nv'&gt;$ &lt;/span&gt;./script/generate rspec
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That’s all there is to it! You can run your rspec tests with this command:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;rake spec
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;.. or, with code coverage output:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;rake spec:rcov
&lt;span class='nv'&gt;$ &lt;/span&gt;open coverage/index.html
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now you’re ready to write some tests. To get started, take a look at the examples at &lt;a href='http://rspec.info/documentation/rails/'&gt;http://rspec.info/documentation/rails/&lt;/a&gt;&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Pay More, Save Less</title>
      <link href="http://hennessynet.com/2008/07/25/pay-more-save-less.html" />
      <updated>2008-07-25T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2008/07/25/pay-more-save-less</id>
      <content type="html">&lt;p&gt;I saw this today on monster.ie. Notice anything unusual about the last two rows?&lt;/p&gt;

&lt;p&gt;&lt;img src='/images/monster-prices.png' alt='monster prices' /&gt;&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Handling Irish postcodes in web forms</title>
      <link href="http://hennessynet.com/2008/06/27/handling-irish-postcodes-in-web-forms.html" />
      <updated>2008-06-27T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2008/06/27/handling-irish-postcodes-in-web-forms</id>
      <content type="html">&lt;p&gt;I saw the following in an Apple iTunes web form today.&lt;/p&gt;
&lt;h3&gt;Billing Address&lt;/h3&gt;&lt;p style='color:red;'&gt;The Postcode may be empty, or for Dublin, enter a one or two digit code, or a digit followed by a letter.&lt;/p&gt;
&lt;p&gt;Why is this a big deal?&lt;/p&gt;

&lt;p&gt;There is no official country-wide postcode system in Ireland. We have a small set of codes for areas of Dublin instead (apparently because of a dispute with the postal worker when the government tried to introduce them). Successive governments have done no better.&lt;/p&gt;

&lt;p&gt;Anyway most web users in Ireland are conditioned to enter &amp;#8216;00000&amp;#8217; or the like into zipcode fields when required. This form from Apple is the first web site I’ve every seen to actually get it right. Well done.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Programming competition time again</title>
      <link href="http://hennessynet.com/2008/05/08/programming-competition-time-again.html" />
      <updated>2008-05-08T00:00:00+01:00</updated>
      <id>http://hennessynet.com/2008/05/08/programming-competition-time-again</id>
      <content type="html">&lt;p&gt;The dates have just appeared for this years ICFP contest:&lt;/p&gt;

&lt;p&gt;July 11, 2008 — July 14, 2008&lt;/p&gt;

&lt;p&gt;The weekend is now blocked out in my calendar and family, friends and pets warned to expect no care and attention. I’m hoping to organise a bigger team this year (2-3 people from last year was too small) – I think 5-6 might be the optimum size.&lt;/p&gt;

&lt;p&gt;Here are some past contests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href='http://www.icfpcontest.org/'&gt;2007, organized by Utrecht University&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.boundvariable.org/'&gt;2006, organized by CMU&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://icfpc.plt-scheme.org/'&gt;2005, organized by PLT and friends&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.cis.upenn.edu/proj/plclub/contest/'&gt;2004, organized by Penn&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.dtek.chalmers.se/groups/icfpcontest'&gt;2003, organized by Chalmers&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://web.cecs.pdx.edu/%7Esheard/2002IcfpContest/'&gt;2002, organized by OGI&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://cristal.inria.fr/ICFP2001/prog-contest/'&gt;2001, organized by INRIA Rocquencourt&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.cs.cornell.edu/icfp/'&gt;2000, organized by Cornell and Bell Labs&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.eecs.harvard.edu/nr/icfp/problem.html'&gt;1999, organized by Harvard and Virginia&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href='http://www.ai.mit.edu/extra/icfp-contest/'&gt;1998, organized by Universite de Montreal and MIT&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
    </entry>
  
    <entry>
      <title>Unit testing Spring/Hibernate code using JMock</title>
      <link href="http://hennessynet.com/2008/02/27/unit-testing-spring-hibernate-code-using-jmock.html" />
      <updated>2008-02-27T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2008/02/27/unit-testing-spring-hibernate-code-using-jmock</id>
      <content type="html">&lt;p&gt;Unit testing a simple Java class like, say, a four-function calculator is trivially easy using JUnit. However, things get a lot harder when you’re using complex support frameworks like Spring or Hibernate. Here are some guidelines for using JMock to help isolate the framework and focus on the code you’re testing.&lt;/p&gt;

&lt;p&gt;Before you start, take a minute to consider whether it’s truly a unit test you’re looking for. The key question is whether your code is really independent from the environment, or if it depends on some clever capability of the framework or database. For example, suppose it generated some complex SQL and then used the results of that SQL; you should consider whether your tests might need to interact with a real database to be sure that your code is correct. If that’s the case, then you probably shouldn’t be using mocks.&lt;/p&gt;

&lt;p&gt;Mocking objects in java is actually quite hard to do (much harder than Ruby or Smalltalk) but JMock helps a lot. JMock (&lt;a href='http://www.jmock.org'&gt;http://www.jmock.org&lt;/a&gt;) has improved hugely in version 2 – it’s practically a rewrite from version 1. Also, I’m going to use JUnit 4 which allows us to use annotations.&lt;/p&gt;

&lt;p&gt;Let’s assume we’ve just written Shipper class with a method that computes the weight of a shipment by adding the weights of the individual components and the weight of a standard shipping box. There’s a catalog service that can retrieve the weight of a component and a config service that stores the standard shipping box weight. Spring is used to wire together the services and Hibernate is used to persist the data. Here’s what our class might look like (the database locking is just to make it interesting):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='kn'&gt;package&lt;/span&gt; &lt;span class='n'&gt;com&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;sample&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.hibernate.Session&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.hibernate.SessionFactory&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.hibernate.LockMode&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.springframework.orm.hibernate3.SessionFactoryUtils&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;Shipper&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;Config&lt;/span&gt; &lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;Catalog&lt;/span&gt; &lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;SessionFactory&lt;/span&gt; &lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;setConfig&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Config&lt;/span&gt; &lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;config&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;setCatalog&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Catalog&lt;/span&gt; &lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;catalog&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;setSessionFactory&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;SessionFactory&lt;/span&gt; &lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='k'&gt;this&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;sessionFactory&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;updateTotalWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Shipment&lt;/span&gt; &lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt;&lt;span class='o'&gt;[]&lt;/span&gt; &lt;span class='n'&gt;ids&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;Session&lt;/span&gt; &lt;span class='n'&gt;session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;SessionFactoryUtils&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getSession&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='kc'&gt;true&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;refresh&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;LockMode&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;UPGRADE_NOWAIT&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;

        &lt;span class='kt'&gt;int&lt;/span&gt; &lt;span class='n'&gt;weight&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getStandardShippingWeight&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
        &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ids&lt;/span&gt; &lt;span class='o'&gt;!=&lt;/span&gt; &lt;span class='kc'&gt;null&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
            &lt;span class='k'&gt;for&lt;/span&gt; &lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;String&lt;/span&gt; &lt;span class='n'&gt;id&lt;/span&gt; &lt;span class='o'&gt;:&lt;/span&gt; &lt;span class='n'&gt;ids&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
                &lt;span class='n'&gt;weight&lt;/span&gt; &lt;span class='o'&gt;+=&lt;/span&gt; &lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;id&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
            &lt;span class='o'&gt;}&lt;/span&gt;
        &lt;span class='o'&gt;}&lt;/span&gt;

        &lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;setWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;weight&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Now let’s look at the test code for this class (I’ve deleted most comments from both files to make them easier to follow). Our objective in the test is to verify the ‘happy route’ through the method, as well as the edge cases with a null or empty list:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='kn'&gt;package&lt;/span&gt; &lt;span class='n'&gt;com&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;sample&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;static&lt;/span&gt; &lt;span class='n'&gt;org&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;junit&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;Assert&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.junit.runner.RunWith&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.junit.Before&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.junit.Test&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.jmock.integration.junit4.JMock&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.jmock.integration.junit4.JUnit4Mockery&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.jmock.Mockery&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.jmock.Expectations&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.jmock.lib.legacy.ClassImposteriser&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.hibernate.SessionFactory&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
&lt;span class='kn'&gt;import&lt;/span&gt; &lt;span class='nn'&gt;org.hibernate.Session&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

&lt;span class='nd'&gt;@RunWith&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;JMock&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ShipperTestCase&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='n'&gt;Mockery&lt;/span&gt; &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;Shipper&lt;/span&gt; &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;Catalog&lt;/span&gt; &lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;Config&lt;/span&gt; &lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;SessionFactory&lt;/span&gt; &lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;
    &lt;span class='n'&gt;Session&lt;/span&gt; &lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;;&lt;/span&gt;

    &lt;span class='nd'&gt;@Before&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;prepareMocks&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;context&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;JUnit4Mockery&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
            &lt;span class='o'&gt;{&lt;/span&gt;
                &lt;span class='c1'&gt;// Enable mocks of concrete classes&lt;/span&gt;
                &lt;span class='n'&gt;setImposteriser&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;ClassImposteriser&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;INSTANCE&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
            &lt;span class='o'&gt;}&lt;/span&gt;
        &lt;span class='o'&gt;};&lt;/span&gt;
        &lt;span class='n'&gt;catalog&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;mock&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Catalog&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;config&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;mock&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Config&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;sessionFactory&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;mock&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;SessionFactory&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;mock&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;Session&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;

        &lt;span class='c1'&gt;// Create our test object and wire up mock services to it&lt;/span&gt;

        &lt;span class='n'&gt;shipper&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;Shipper&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;
        &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;setCatalog&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;setConfig&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;setSessionFactory&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='nd'&gt;@Test&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;emptyOrNullList&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;Shipment&lt;/span&gt; &lt;span class='n'&gt;shipment&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;Shipment&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;

        &lt;span class='c1'&gt;// Set up the expected behaviour in the support services&lt;/span&gt;
        &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;checking&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;Expectations&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
            &lt;span class='o'&gt;{&lt;/span&gt;
                &lt;span class='n'&gt;allowing&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;openSession&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;allowing&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getSessionFactory&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;ignoring&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;

                &lt;span class='c1'&gt;// Set required expectations for test to pass&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getStandardShippingWeight&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;8&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
            &lt;span class='o'&gt;}&lt;/span&gt;

        &lt;span class='o'&gt;});&lt;/span&gt;
        &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;updateTotalWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='kc'&gt;null&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;8&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;());&lt;/span&gt;
        &lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;setWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;
        &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;updateTotalWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='o'&gt;]);&lt;/span&gt;
        &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;8&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;());&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;

    &lt;span class='nd'&gt;@Test&lt;/span&gt;
    &lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kt'&gt;void&lt;/span&gt; &lt;span class='nf'&gt;sampleList&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='n'&gt;Shipment&lt;/span&gt; &lt;span class='n'&gt;shipment&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;Shipment&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt;

        &lt;span class='c1'&gt;// Set up the expected behaviour in the support services&lt;/span&gt;
        &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;checking&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;Expectations&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
            &lt;span class='o'&gt;{&lt;/span&gt;
                &lt;span class='n'&gt;allowing&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;openSession&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;allowing&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getSessionFactory&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;ignoring&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;

                &lt;span class='c1'&gt;// Set required expectations for test to pass&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getStandardShippingWeight&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
            &lt;span class='o'&gt;}&lt;/span&gt;

        &lt;span class='o'&gt;});&lt;/span&gt;
        &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;updateTotalWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt;&lt;span class='o'&gt;[]&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;});&lt;/span&gt;
        &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;15&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;());&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Let’s look at a few interesting parts of the test code:&lt;/p&gt;

&lt;p&gt;&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nd'&gt;@RunWith&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;JMock&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;class&lt;/span&gt;&lt;span class='o'&gt;)&lt;/span&gt;
&lt;span class='kd'&gt;public&lt;/span&gt; &lt;span class='kd'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;ShipperTestCase&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;This causes JUnit to use the runner from JMock, instead of its built-it one.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;@Before
public void prepareMocks&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
    &lt;span class='nv'&gt;context&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; new JUnit4Mockery&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
        &lt;span class='o'&gt;{&lt;/span&gt;
            setImposteriser&lt;span class='o'&gt;(&lt;/span&gt;ClassImposteriser.INSTANCE&lt;span class='o'&gt;)&lt;/span&gt;;    // Enable mocks of concrete classes
        &lt;span class='o'&gt;}&lt;/span&gt;
    &lt;span class='o'&gt;}&lt;/span&gt;;
    &lt;span class='nv'&gt;catalog&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; context.mock&lt;span class='o'&gt;(&lt;/span&gt;Catalog.class&lt;span class='o'&gt;)&lt;/span&gt;;
    &lt;span class='nv'&gt;config&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; context.mock&lt;span class='o'&gt;(&lt;/span&gt;Config.class&lt;span class='o'&gt;)&lt;/span&gt;;
    &lt;span class='nv'&gt;sessionFactory&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; context.mock&lt;span class='o'&gt;(&lt;/span&gt;SessionFactory.class&lt;span class='o'&gt;)&lt;/span&gt;;
    &lt;span class='nv'&gt;session&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; context.mock&lt;span class='o'&gt;(&lt;/span&gt;Session.class&lt;span class='o'&gt;)&lt;/span&gt;;

    // Create our &lt;span class='nb'&gt;test &lt;/span&gt;object and wire up mock services to it

    &lt;span class='nv'&gt;shipper&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; new Shipper&lt;span class='o'&gt;()&lt;/span&gt;;
    shipper.setCatalog&lt;span class='o'&gt;(&lt;/span&gt;catalog&lt;span class='o'&gt;)&lt;/span&gt;;
    shipper.setConfig&lt;span class='o'&gt;(&lt;/span&gt;config&lt;span class='o'&gt;)&lt;/span&gt;;
    shipper.setSessionFactory&lt;span class='o'&gt;(&lt;/span&gt;sessionFactory&lt;span class='o'&gt;)&lt;/span&gt;;
&lt;span class='o'&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This method runs before each of the test cases. It’s (hopefully) very straightforward – it creates mock objects for each of the external services that our class relies on, and then creates an object to test with those mocks wired to it.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;        &lt;span class='c1'&gt;// Set up the expected behaviour in the support services&lt;/span&gt;
        &lt;span class='n'&gt;context&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;checking&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;Expectations&lt;/span&gt;&lt;span class='o'&gt;()&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt;
            &lt;span class='o'&gt;{&lt;/span&gt;
                &lt;span class='n'&gt;allowing&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;openSession&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;allowing&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getSessionFactory&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;sessionFactory&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;ignoring&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;session&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt;

                &lt;span class='c1'&gt;// Set required expectations for test to pass&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;config&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getStandardShippingWeight&lt;/span&gt;&lt;span class='o'&gt;();&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
                &lt;span class='n'&gt;atLeast&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;of&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;catalog&lt;/span&gt;&lt;span class='o'&gt;).&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;);&lt;/span&gt; &lt;span class='n'&gt;will&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;returnValue&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;4&lt;/span&gt;&lt;span class='o'&gt;));&lt;/span&gt;
            &lt;span class='o'&gt;}&lt;/span&gt;

        &lt;span class='o'&gt;});&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This block sets the behaviour we want from our mocks. Notice that we can specify different behaviour based on different parameters to a method. The ignoring(session) line indicates that all other methods of session should simply return a default value.&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;        &lt;span class='n'&gt;shipper&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;updateTotalWeight&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='k'&gt;new&lt;/span&gt; &lt;span class='n'&gt;String&lt;/span&gt;&lt;span class='o'&gt;[]&lt;/span&gt; &lt;span class='o'&gt;{&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='s'&gt;&amp;quot;c&amp;quot;&lt;/span&gt;&lt;span class='o'&gt;});&lt;/span&gt;
        &lt;span class='n'&gt;assertEquals&lt;/span&gt;&lt;span class='o'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;15&lt;/span&gt;&lt;span class='o'&gt;,&lt;/span&gt; &lt;span class='n'&gt;shipment&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='na'&gt;getWeight&lt;/span&gt;&lt;span class='o'&gt;());&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Finally, we call our test object and verify the results.&lt;/p&gt;

&lt;p&gt;When writing expectations, it’s worth making sure you only set constraints (like the &lt;code&gt;atLeast(1)&lt;/code&gt; line) where a mock method MUST be called or you consider the test as failed. If you make the constraints too tight, then your tests can get brittle and can fail when you change the implementation, even if that change hasn’t broken the functionality. For example, if I’d used &lt;code&gt;exactly(1)&lt;/code&gt; instead of &lt;code&gt;atLeast(1)&lt;/code&gt;, the test would still pass but would be less resilient to implementation changes.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Protecting email addresses on web pages</title>
      <link href="http://hennessynet.com/2008/02/23/protecting-email-addresses-on-web-pages.html" />
      <updated>2008-02-23T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2008/02/23/protecting-email-addresses-on-web-pages</id>
      <content type="html">&lt;p&gt;I was adding my email address to &lt;a href='http://peerassembly.com'&gt;http://peerassembly.com&lt;/a&gt; and used a little javascript snippit to hide the email address from simple (read: stupid) email harvester bots. It’s so short, it hardly warrants a post but maybe someone will find it the next time they’re putting a contact email address on a web page and save themselves a ton of spam!&lt;/p&gt;

&lt;p&gt;If you don’t have it already, add &lt;code&gt;prototype.js&lt;/code&gt; to your page:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;  &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;src=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;javascripts/prototype.js&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt; &lt;span class='na'&gt;charset=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;At the place in your page where you put your email address, replace it with:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nt'&gt;&amp;lt;span&lt;/span&gt; &lt;span class='na'&gt;id=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;email&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;info&lt;span class='nt'&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then, add this to your page:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;  &lt;span class='nt'&gt;&amp;lt;script &lt;/span&gt;&lt;span class='na'&gt;type=&lt;/span&gt;&lt;span class='s'&gt;&amp;quot;text/javascript&amp;quot;&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
  &lt;span class='kd'&gt;var&lt;/span&gt; &lt;span class='nx'&gt;a&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;info&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;&amp;amp;#64;&amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;peerassembly.com&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;;&lt;/span&gt;
  &lt;span class='nx'&gt;$&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;).&lt;/span&gt;&lt;span class='nx'&gt;update&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;&amp;lt;a href=&amp;quot;mailto:&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='nx'&gt;a&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;&amp;quot;&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='nx'&gt;a&lt;/span&gt;&lt;span class='o'&gt;+&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;);&lt;/span&gt;
  &lt;span class='nt'&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That’s it – when the javascript executes, it will insert the email link in your page.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Using capistrano for static sites</title>
      <link href="http://hennessynet.com/2008/02/14/using-capistrano-for-static-sites.html" />
      <updated>2008-02-14T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2008/02/14/using-capistrano-for-static-sites</id>
      <content type="html">&lt;p&gt;I’ve been using capistrano for rails deployment for a while. I thought I’d see how useful it was for managing some static sites – it turned out to be really useful. Here’s how to do it:&lt;/p&gt;

&lt;p&gt;First, create the project in subversion and check it out. I really like the idea of everything being in source control. My normal process for updating a site is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;check out site files&lt;/li&gt;

&lt;li&gt;make changes&lt;/li&gt;

&lt;li&gt;review locally&lt;/li&gt;

&lt;li&gt;check them in&lt;/li&gt;

&lt;li&gt;publish updated site&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Capistrano helps with the last step – to publish your updated site, you simply use:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;cap deploy:update
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;In this example, my subversion repository is at &lt;em&gt;dev.work.com/svn&lt;/em&gt; the site is &lt;em&gt;mysite.com&lt;/em&gt; and the server is located at &lt;em&gt;bighost.com&lt;/em&gt;&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;svn mkdir http://dev.work.com/svn/mysite
&lt;span class='nv'&gt;$ &lt;/span&gt;svn co http://dev.work.com/svn/mysite
&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;cd &lt;/span&gt;mysite
&lt;span class='nv'&gt;$ &lt;/span&gt;mkdir -p config public public/images public/stylesheets public/javascripts
&lt;span class='nv'&gt;$ &lt;/span&gt;capify .
&lt;span class='o'&gt;[&lt;/span&gt;add&lt;span class='o'&gt;]&lt;/span&gt; writing &lt;span class='s1'&gt;&amp;#39;./Capfile&amp;#39;&lt;/span&gt;
&lt;span class='o'&gt;[&lt;/span&gt;add&lt;span class='o'&gt;]&lt;/span&gt; writing &lt;span class='s1'&gt;&amp;#39;./config/deploy.rb&amp;#39;&lt;/span&gt;
&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='k'&gt;done&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt; capified!
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If you already have content for the site, copy it into the &lt;code&gt;./public&lt;/code&gt; directory tree. Otherwise, create the content in place.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;config/deploy.rb&lt;/code&gt; as follows&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:application&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;mysite&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:repository&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;  &lt;span class='s2'&gt;&amp;quot;http://dev.work.com/svn/mysite&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:deploy_via&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:copy&lt;/span&gt;

&lt;span class='n'&gt;set&lt;/span&gt; &lt;span class='ss'&gt;:deploy_to&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;/home/denis/&lt;/span&gt;&lt;span class='si'&gt;#{&lt;/span&gt;&lt;span class='n'&gt;application&lt;/span&gt;&lt;span class='si'&gt;}&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;&lt;/span&gt;

&lt;span class='n'&gt;role&lt;/span&gt; &lt;span class='ss'&gt;:app&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;bighost.com&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;role&lt;/span&gt; &lt;span class='ss'&gt;:web&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;bighost.com&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;role&lt;/span&gt; &lt;span class='ss'&gt;:db&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;  &lt;span class='s2'&gt;&amp;quot;bighost.com&amp;quot;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:primary&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='kp'&gt;true&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Check all of these into subversion and create the server-side directories with:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;svn add *
&lt;span class='nv'&gt;$ &lt;/span&gt;svn ci
&lt;span class='nv'&gt;$ &lt;/span&gt;cap deploy:setup
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note that in this example, the web site is served from &lt;code&gt;/home/denis/mysite/current/public&lt;/code&gt;. Here’s the apache config file (to be installed in &lt;code&gt;/etc/apache2/sites-available&lt;/code&gt;) to make that work:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nt'&gt;&amp;lt;VirtualHost&lt;/span&gt; &lt;span class='err'&gt;*&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
  ServerAdmin denis@hennessynet.com
  ServerName mysite.com
  ServerAlias www. mysite.com
  DocumentRoot /home/denis/mysite/current/public
  &lt;span class='nt'&gt;&amp;lt;Directory&lt;/span&gt; &lt;span class='err'&gt;/home/denis/mysite/current/public&lt;/span&gt;&lt;span class='nt'&gt;&amp;gt;&lt;/span&gt;
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    allow from all
  &lt;span class='nt'&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;

  ErrorLog /var/log/apache2/error. mysite.log
  CustomLog /var/log/apache2/access. mysite.log combined
&lt;span class='nt'&gt;&amp;lt;/VirtualHost&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;</content>
    </entry>
  
    <entry>
      <title>Wil Shipley on Adwords, Indie Mac development and more</title>
      <link href="http://hennessynet.com/2008/02/10/wil-shipley-on-adwords-indie-mac-development-and-more.html" />
      <updated>2008-02-10T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2008/02/10/wil-shipley-on-adwords-indie-mac-development-and-more</id>
      <content type="html">&lt;p&gt;I just watched Wil Shipley’s presentation from C4[1] (&lt;a href='http://www.viddler.com/explore/rentzsch/videos/4/'&gt;video&lt;/a&gt;). The topic is generally about how to best use hype to promote your application (it was in front of an audience of Mac Indie developers). Besides being thoroughly enjoyable to watch, it contained quite a number of interesting tidbits on Google adwords, beta and upgrade policies, …&lt;/p&gt;

&lt;p&gt;Recommended.&lt;/p&gt;</content>
    </entry>
  
    <entry>
      <title>Installing MacPorts and MySQL on Leopard</title>
      <link href="http://hennessynet.com/2008/01/15/installing-macports-and-mysql-on-leopard.html" />
      <updated>2008-01-15T00:00:00+00:00</updated>
      <id>http://hennessynet.com/2008/01/15/installing-macports-and-mysql-on-leopard</id>
      <content type="html">&lt;p&gt;I recently had to re-install a development environment on a Mac Pro so I kept track of the steps as I went. Here’s the simplest way to get MacPorts and MySQL installed on Leopard.&lt;/p&gt;

&lt;p&gt;Install XCode 3.0 from Leopard Install Disk 2 Download MacPorts installer from &lt;a href='http://www.macports.org/install.php'&gt;http://www.macports.org/install.php&lt;/a&gt; and run. Then, add the following to &lt;code&gt;~/.bash_profile&lt;/code&gt; (and then restart your Terminal):&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nb'&gt;export &lt;/span&gt;&lt;span class='nv'&gt;PATH&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;/opt/local/bin:/opt/local/sbin:&lt;span class='nv'&gt;$PATH&lt;/span&gt;
&lt;span class='nb'&gt;export &lt;/span&gt;&lt;span class='nv'&gt;MANPATH&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;/opt/local/share/man:&lt;span class='nv'&gt;$MANPATH&lt;/span&gt;
&lt;span class='nb'&gt;export &lt;/span&gt;&lt;span class='nv'&gt;ARCHFLAGS&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;&lt;span class='s2'&gt;&amp;quot;-arch i386&amp;quot;&lt;/span&gt;
&lt;span class='nb'&gt;bind&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;set completion-ignore-case on&amp;#39;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;That last line is not strictly needed but I like to be able to tab through directory names without caring about the capitalisation. Finally run:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;sudo port -v selfupdate
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;OK, that’s MacPorts out of the way, on to MySQL:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;sudo port install mysql5 +server
&lt;span class='nv'&gt;$ &lt;/span&gt;sudo -u mysql mysql_install_db5
&lt;span class='nv'&gt;$ &lt;/span&gt;&lt;span class='nb'&gt;cd&lt;/span&gt; /opt/local
&lt;span class='nv'&gt;$ &lt;/span&gt;sudo /opt/local/lib/mysql5/bin/mysqld_safe
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Add the following as &lt;code&gt;/opt/local/etc/mysql5/my.cnf&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='o'&gt;[&lt;/span&gt;mysqld_safe&lt;span class='o'&gt;]&lt;/span&gt;
&lt;span class='nv'&gt;socket&lt;/span&gt;&lt;span class='o'&gt;=&lt;/span&gt;/tmp/mysql.sock
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Lastly, try it out (and set it to start on reboot) with:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='nv'&gt;$ &lt;/span&gt;sudo /opt/local/lib/mysql5/bin/mysqld_safe
&lt;span class='nv'&gt;$ &lt;/span&gt;sudo launchctl load -w /Library/LaunchDaemons/org.macports.mysql5.plist
&lt;span class='nv'&gt;$ &lt;/span&gt;sudo ln -s /tmp/mysql.sock /opt/local/var/run/mysql5/mysqld.sock
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;If I’ve missed anything, let me know.&lt;/p&gt;</content>
    </entry>
  
</feed>

