Feed on
Posts
Comments

The dates have just appeared for this years ICFP contest:

July 11, 2008 — July 14, 2008

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.

Here are some past contests:

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.

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.

Mocking objects in java is actually quite hard to do (much harder than Ruby or Smalltalk) but JMock helps a lot. JMock (http://www.jmock.org/) 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.

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):

package com.sample;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.LockMode;
import org.springframework.orm.hibernate3.SessionFactoryUtils;

public class Shipper {
    Config config;
    Catalog catalog;
    SessionFactory sessionFactory;

    public void setConfig(Config config) {
        this.config = config;
    }

    public void setCatalog(Catalog catalog) {
        this.catalog = catalog;
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public void updateTotalWeight(Shipment shipment, String[] ids) {
        Session session = SessionFactoryUtils.getSession(sessionFactory, true);
        session.refresh(shipment, LockMode.UPGRADE_NOWAIT);

        int weight = config.getStandardShippingWeight();
        if (ids != null) {
            for (String id : ids) {
                weight += catalog.getWeight(id);
            }
        }

        shipment.setWeight(weight);
    }
}

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:

package com.sample;

import static org.junit.Assert.assertEquals;
import org.junit.runner.RunWith;
import org.junit.Before;
import org.junit.Test;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.Mockery;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.hibernate.SessionFactory;
import org.hibernate.Session;

@RunWith(JMock.class)
public class ShipperTestCase {
    Mockery context;
    Shipper shipper;
    Catalog catalog;
    Config config;
    SessionFactory sessionFactory;
    Session session;

    @Before
    public void prepareMocks() {
        context = new JUnit4Mockery() {
            {
                // Enable mocks of concrete classes
                setImposteriser(ClassImposteriser.INSTANCE);
            }
        };
        catalog = context.mock(Catalog.class);
        config = context.mock(Config.class);
        sessionFactory = context.mock(SessionFactory.class);
        session = context.mock(Session.class);

        // Create our test object and wire up mock services to it

        shipper = new Shipper();
        shipper.setCatalog(catalog);
        shipper.setConfig(config);
        shipper.setSessionFactory(sessionFactory);
    }

    @Test
    public void emptyOrNullList() {
        Shipment shipment = new Shipment();

        // Set up the expected behaviour in the support services
        context.checking(new Expectations() {
            {
                allowing(sessionFactory).openSession(); will(returnValue(session));
                allowing(session).getSessionFactory(); will(returnValue(sessionFactory));
                ignoring(session);

                // Set required expectations for test to pass
                atLeast(1).of(config).getStandardShippingWeight(); will(returnValue(8));
            }

        });
        shipper.updateTotalWeight(shipment, null);
        assertEquals(8, shipment.getWeight());
        shipment.setWeight(0);
        shipper.updateTotalWeight(shipment, new String[0]);
        assertEquals(8, shipment.getWeight());
    }

    @Test
    public void sampleList() {
        Shipment shipment = new Shipment();

        // Set up the expected behaviour in the support services
        context.checking(new Expectations() {
            {
                allowing(sessionFactory).openSession(); will(returnValue(session));
                allowing(session).getSessionFactory(); will(returnValue(sessionFactory));
                ignoring(session);

                // Set required expectations for test to pass
                atLeast(1).of(config).getStandardShippingWeight(); will(returnValue(4));
                atLeast(1).of(catalog).getWeight("a"); will(returnValue(1));
                atLeast(1).of(catalog).getWeight("b"); will(returnValue(2));
                atLeast(1).of(catalog).getWeight("c"); will(returnValue(4));
            }

        });
        shipper.updateTotalWeight(shipment, new String[] { "a", "b", "c"});
        assertEquals(15, shipment.getWeight());
    }
}

Let’s look at a few interesting parts of the test code:

@RunWith(JMock.class)
public class ShipperTestCase {

This causes JUnit to use the runner from JMock, instead of its built-it one.

    @Before
    public void prepareMocks() {
        context = new JUnit4Mockery() {
            {
                setImposteriser(ClassImposteriser.INSTANCE);    // Enable mocks of concrete classes
            }
        };
        catalog = context.mock(Catalog.class);
        config = context.mock(Config.class);
        sessionFactory = context.mock(SessionFactory.class);
        session = context.mock(Session.class);

        // Create our test object and wire up mock services to it

        shipper = new Shipper();
        shipper.setCatalog(catalog);
        shipper.setConfig(config);
        shipper.setSessionFactory(sessionFactory);
    }

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.

        // Set up the expected behaviour in the support services
        context.checking(new Expectations() {
            {
                allowing(sessionFactory).openSession(); will(returnValue(session));
                allowing(session).getSessionFactory(); will(returnValue(sessionFactory));
                ignoring(session);

                // Set required expectations for test to pass
                atLeast(1).of(config).getStandardShippingWeight(); will(returnValue(4));
                atLeast(1).of(catalog).getWeight("a"); will(returnValue(1));
                atLeast(1).of(catalog).getWeight("b"); will(returnValue(2));
                atLeast(1).of(catalog).getWeight("c"); will(returnValue(4));
            }

        });

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.

        shipper.updateTotalWeight(shipment, new String[] { "a", "b", "c"});
        assertEquals(15, shipment.getWeight());

Finally, we call our test object and verify the results.

When writing expectations, it’s worth making sure you only set constraints (like the atLeast(1) 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 exactly(1) instead of atLeast(1), the test would still pass but would be less resilient to implementation changes.

I was adding my email address to peerassembly.com 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!

If you don’t have it already, add prototype.js to your page:

  <script src="javascripts/prototype.js" type="text/javascript" charset="utf-8"></script>

At the place in your page where you put your email address, replace it with:

<span id="email">info</span>

Then, add this to your page:

  <script type="text/javascript">
  var a = "info" + "&#64;" + "peerassembly.com";
  $('email').update('<a href="mailto:'+a+'">'+a+'</a>');
  </script>

That’s it - when the javascript executes, it will insert the email link in your page.

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:

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:

  • check out site files
  • make changes
  • review locally
  • check them in
  • publish updated site

Capistrano helps with the last step - to publish your updated site, you simply use:

$ cap deploy:update

In this example, my subversion repository is at dev.work.com/svn the site is mysite.com and the server is located at bighost.com

$ svn mkdir http://dev.work.com/svn/mysite
$ svn co http://dev.work.com/svn/mysite
$ cd mysite
$ mkdir -p config public public/images public/stylesheets public/javascripts
$ capify .
[add] writing `./Capfile'
[add] writing `./config/deploy.rb'
[done] capified!

If you already have content for the site, copy it into the ./public directory tree. Otherwise, create the content in place.

Edit config/deploy.rb as follows

set :application, "mysite"
set :repository,  "http://dev.work.com/svn/mysite"
set :deploy_via, :copy

set :deploy_to, "/home/denis/#{application}"

role :app, "bighost.com"
role :web, "bighost.com"
role :db,  "bighost.com", :primary => true

Check all of these into subversion and create the server-side directories with:

$ svn add *
$ svn ci
$ cap deploy:setup

Note that in this example, the web site is served from /home/denis/mysite/current/public. Here’s the apache config file (to be installed in /etc/apache2/sites-available) to make that work:

<VirtualHost *>
        ServerAdmin denis@hennessynet.com
        ServerName mysite.com
	    ServerAlias www. mysite.com
        DocumentRoot /home/denis/mysite/current/public
        <Directory /home/denis/mysite/current/public>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride All
                Order allow,deny
                allow from all
        </Directory>

        ErrorLog /var/log/apache2/error. mysite.log
        CustomLog /var/log/apache2/access. mysite.log combined
</VirtualHost>

I just watched Wil Shipley’s presentation from C4[1] (video). 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, …

Recommended.

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.

  1. Install XCode 3.0 from Leopard Install Disk 2
  2. Download MacPorts installer from http://www.macports.org/install.php and run.

Then, add the following to ~/.bash_profile (and then restart your Terminal):

export PATH=/opt/local/bin:/opt/local/sbin:$PATH
export MANPATH=/opt/local/share/man:$MANPATH
export ARCHFLAGS="-arch i386"
bind 'set completion-ignore-case on'

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:

$ sudo port -v selfupdate

OK, that’s MacPorts out of the way, on to MySQL:

$ sudo port install mysql5 +server
$ sudo -u mysql mysql_install_db5
$ cd /opt/local
$ sudo /opt/local/lib/mysql5/bin/mysqld_safe

Add the following as /opt/local/etc/mysql5/my.cnf:

[mysqld_safe]
socket=/tmp/mysql.sock

Lastly, try it out (and set it to start on reboot) with:

$ sudo /opt/local/lib/mysql5/bin/mysqld_safe
$ sudo launchctl load -w /Library/LaunchDaemons/org.macports.mysql5.plist
$ sudo ln -s /tmp/mysql.sock /opt/local/var/run/mysql5/mysqld.sock

If I’ve missed anything, let me know.