Friday, May 18, 2012

First Forays into TDD

So on a recent project, I've finally gotten into Test Driven Development, and I have to say, I completely enjoyed it.

Without giving much away, the project is system allowing other internal business systems to publish XML content to subscribed users. Those users are then allowed to reply with specific responses based on the message content. The message has system-level content in a header, as well as business-level content, agnostic to the system. It became very obvious very quickly that the the concept of message threads would become important. Enter TDD...

The server-side was built using Enterprise Integration Pattern framework to abstract the boilerplate necessary to route messages back and forth, and down to a data store. So a message transformer was introduced to insert a ThreadID into the message header that correlated with the business system's SystemID and their provided ExternalID and ReferenceID. These latter ids, allow the business systems to track their messages in their own internal way. In this way an ExternalID + SystemID would map to a ThreadID in the system's internal cache. A follow-up message could then be given a new ExternalID pointing to a previous message through the ReferenceID.

Since I knew what the messages should look like coming in and going out, it can be modeled completely as a black-box, which naturally and happily lends itself to TDD (happily because it's the easiest case to get acquainted with TDD).

Given the predetermined inputs and outputs (an exception is a valid output, don't forget), I drew up some basic scenarios, and blew that up into a flowchart (via Gliffy):
From there, I was able to layout my general test cases. Many of them were just standard JUnit cases, like failing when not receiving an expected exception:
@Test
public void testReferenceIdNotCached() throws Exception {
  String inputXml = "<message><header><systemId>TestSystem</systemId><referenceid>ReferenceIdNotCached</referenceid></header><body /></message>";
  try {
    Message<String> output = transformer.transform(new Message<String>(inputXml));
    fail(String.format(
                    "Value of <referenceid> field is not cached and should throw an exception. Output is::[%s].",output.getPayload()));
  } catch (MessageTransformationException e) {
    assert e.getCause() instanceof IllegalArgumentException : "Underlying exception is incorrect.";
  }
}


Some of the tests though check that for the canned message inputted, creates an output message matching an expected format. This is where XmlUnit was a big help. It lets you compare XML strings, giving you the option to either expect an exact string or a similar string with a different ordering of elements. It also lets you go into a more detailed level where you can inspect the differences and potentially discard them, one by one. Below is an example of getting a similar output, since the ordering of the header elements doesn't matter, just that they exist, and the values are the same.

@Test
public void testRefExternalIdPassedWithRefCached() throws Exception {
  String referenceId = "EXT_" + UUID.randomUUID().toString();
  UUID threadId = threadIdCache.getNewAndPut(referenceId, "TestSystemId");

  String inputFormat = "<message><header><systemid>TestSystemId</systemid><referenceid>%s</referenceid></header><body /></message>";
  String inputXml = String.format(inputFormat, "TestSystemId", referenceId);
  Message<String> transformedMessage = transformer.transform(new Message<String>(inputXml));

  String expectedFormat = "<message><header><systemid>%s</systemid><threadid>%s</threadid><referenceid>%s</referenceid></header><body /></message>";
  String expectedXml = String.format(expectedFormat, "TestSystemId", threadId.toString(), referenceId);
  String transformedXml = stripMessageId(transformedMessage.getPayload());

  Diff diff = new Diff(expectedXml, transformedXml);
  assertTrue(diff.toString(), diff.similar());
}


Once these were written, I reused my flowchart to write the code. As a result of this coordinated effort, I immediately saw from an earlier version of the chart that I missed a couple of checks and that the ordering of them was out of whack, but my tests never really needed to change since my inputs and outputs were still the same. And since my tests were already written, I felt slightly compelled and more at ease to add more tests to make sure inputs existed and were in the correct format.

It was a great process, and I look forward to using it again in other parts of my code. The three things that really came across during this: it's very hard to go against habit making sure I write the tests before the code, I can see how difficult it can be to put in place in certain situations (like when refactoring), but you can eventually get tests in place on a large enough section of your code to make the process useful, and lastly, your tests are written!.

Friday, May 11, 2012

gzip and the stdout

As a native Windows user, I still don't consistently think in a CLI mindset. So when I finally came across a shortcut to a common annoyance, it was a forehead slap of "Oh, THAT'S why that's there."

When writing apps hopefully, like me, you roll over your logs and compress them to conserve space. However, also like me, you have to check those files in a production or production-like environment. Which means either logging in as some super-user, or downloading the files, or copying them to some /tmp directory, when you can unzip the files, and read them.

Or you could save yourself a couple of unnecessary steps and just think in CLI using the "-c" flag for gzip, then pipe the output to less, more, or wherever.

bash> gzip -cd rolledover.20120510.log.gz | less

Thursday, May 10, 2012

So You Want To Be a Developer

While one of the reasons I started this blog is to act as a Pensieve of sorts allowing me to retain and review interesting tidbits of code I've developed, there is another reason for it. This blog allows me a forum to expound a bit on the basics of computer programming, software development and engineering... they're not the same! (but more on that later).

Anyways, here's my first entry on that subject, and instead of reexplaining it, I'll just point you to a couple of entertaining videos (which also buys me time on a longer post I have brewing). It the 10,000 ft view about I actually do for a living, and includes some broad recommendations on how you can be better at this job, and level-up your software developer / engineering character.

Without further delay (and in two parts) here is So You Want To Be a Developer Part 1 and Part 2.

Many thanks to the folks at Extra Credits, Stack Exchange and Penny Arcade for putting this together and spreading the word.

Wednesday, May 9, 2012

Why am I doing this?

I'm no self-help enthusiast, but I am a fan of The Nerdist Chris Hardwick, and read his book The Nerdist Way (check out my review here). In it he mentions trying to build a D&D character of yourself, finding areas you want to target and build your strength in. It's geeky, but it is a pretty logical way to do some self-improvement.

I started to think about rateable skills and strengths for a software developer. It would be a fun but daunting task. It's something that has churned around in the back of my head from time to time. I then came across this beauty, which is the "Programmer Competency Matrix". It's not perfect, and it doesn't apply for everyone's career path, or for every job, but it's a great starting point.

BTW, this blog levels me up to log(n) on Knowledgeblogs.

Tuesday, May 8, 2012

Testing SyntaxHighlighter

Getting this thing ready for primetime. Eat it posterous! I've loved the way SyntaxHighter has looked, and wanted a blog that can support it. Posterous fails miserably in that area. So based on my friend Doug's suggestions, I hit up this installation guide for Blogger, and here we are.
public class SyntaxTester {
  // single line comment
  private static final String HEY_YO = "Hey yo!";

  /**
   * Javadoc
   * @param args
   */
  public static void main(String[] args) {
    System.out.println(HEY_YO);
  }
}