Legacy pictures

July 18 2010

someone once stated that legacy code is simply “code that works” (I guess was Michael Feathers, but could not find any reference online). Feathers himself proposed characterization tests as a vise to put legacy code into, before starting doing any change.

this week had the pleasure to add support to our current application for customer’s internal search engine indexing, something already developed by some colleagues of mine, for a similar web application. so, I spent some time reading product documentation and reviewing existing code, which was clearly composed of custom domain logic and generic integration logic with given product. I also played a bit with code, in a sandbox, to understand how pieces could be splitted.

then, I reverted every change, and started writing an initial characterization test.

main application entry point was a “workflow” process, expected to be executed by the application container when a few events had occurred. visible behaviour consisted of text files, with specific content, stored via FTP. so, I first “reproduced” current behaviour starting a local FTP server (bundled with my MacBook, and available from System Preferences). after having seen text files written, I could write an automatic test (well, automatic but not yet reproducible on every workstation).

@Before
public void setUp() throws Exception {
  ...

  ftpFolder = createTmpFolderAt(FTP_HOME_FOLDER);

  properties.put("ftp.url", FTP_HOST);
  properties.put("ftp.port", FTP_PORT);
  properties.put("ftp.user", FTP_USER);
  properties.put("ftp.password", FTP_PASSWORD);
  properties.put("ftp.directory", ftpFolder.getName());
  properties.put("base.url", "http://www.mysite.com");
  
  Hashtable<String, String> properties = new Hashtable<String, String>();
  activate(facade, new InMemoryComponentContext(properties));
}

@Test
public void shouldNotifyNewsCreation() throws Exception {
  String lavoro = "/its/a/news/lavoro";
  ...
  
  String[] arguments = new String[] { "creazione" };
  process.execute(workItem, workflowSession, arguments);

  String expectedContent = "VdkVgwKey: http://www.mysite.com/its/a/news/lavoro.xml\n<<EOD>>";
  assertEquals(expectedContent, contentAt(ftpResource("/lavoro.bif")));
}

helper methods simply read resource content, stored at a given URL:

private String ftpResource(String resourcePath) {
  return
    "ftp://" + FTP_USER + ":" + FTP_PASSWORD +
    "@" + FTP_HOST + ":" + FTP_PORT + "/" + 
    ftpFolder.getName() + resourcePath;
}

private String contentAt(String urlAsString) throws Exception {
  return ContentUtils.readUrl(new URL(urlAsString));
}

then, was the turn for turning my local FTP server off, and using one for testing purpose. I have to admit, I tought it would have been easier, I’ve done this many times for HTTP, mail and JMS servers, but getting an FTP server started and stopped by a JUnit test turned to be painful! I played with MockFtpServer (hung waiting FTP data on port 0, no way to set any other port), then Apache FtpServer (had no luck with user grants on filesystem) and HermesFTP (gosh! it requires Spring for running!).

then found a lightweight and simple Java FTP server: AXL FTP Server (details for adding a Maven dependency are available here). its entry point it’s a Main method: i just managed to start a server thread on test setup and stop it on test tear-down (actually, on suite setup and tear-down).

// see src/test/resources/ftp/ftp.cfg
private static final String FTP_HOST = "localhost";
private static final String FTP_PORT = "2121";
private static final String FTP_USER = "foo";
private static final String FTP_PASSWORD = "bar";
private static final String FTP_HOME_FOLDER = "/tmp";

private static Thread serverThread; 
...

@BeforeClass
public static void startServer() throws Exception {
  serverThread = new Thread(new Runnable() {
    public void run() {
      ftpd.main(new String[] { "src/test/resources/ftp" });
    }
  });

  serverThread.start();
}

@AfterClass
public static void stopServer() {
  if (serverThread != null) {
    serverThread.stop();
  }
}

it was a matter of a few configuration files, which I put under Maven test resources folder (ftp.cfg for server config, and dir.cfg for users and filesystem grants).

this gave me confidence, really, adding a few more test cases. I could then start restructuring and refactoring code: extracting a brand new project, then removing duplicated logic, extracting classes and interfaces with clear names. in this process, as long as I was changing code, my understanding of the whole notification process improved.

in the end, I could easily move FTP integration test to the new project test suite, while unit-testing original code for workflow process with test-doubles (in-memory fake objects and mock objects). more unit tests were added, along the way, also.

well, it was really not legacy code, not yet! it was lack of automatic tests. this gave me the opportunity to apply Feathers’s process: take a picture of current behaviour, before starting refactoring code.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: