James Carr | Rants and Musings of an Agile Developer

Archive for November 2006

Been a busy holiday, but finally got a chance tonight to relax and get back into the game of playing with Eiffel. I think it will be fun to try out the bowling game in Eiffel using test driven design, but the Design By Contract features should make it a bit interesting. For now, here’s some testing tools for Eiffel that I found.

No tags

Nov/06

20

Taking Eiffel for a Spin

Lately I’ve been playing around with a multitude of languages, hoping to not only have yet more to add to my list of fluent languages to brag about, but to also gain further insight in other implementations and design from them. For this reason, I decided to play with Eiffel over the weekend.

(more…)

No tags

Nov/06

19

User Unfriendly

Today I was fumbling around my insurance provider’s website hoping to make my bill payment online and get it set up automatically because despite constant calls changing my address on the policy and begging them to send me the bill, they still insist all this time to continue sending the bill to my parent’s address. Go figure.

Anyhow, I could not register for it. For some reason, I kept getting defeated when I tried to enter my social security number. I kept entering my SSN, and it kept getting denied. So I clicked on the “contact us” link at the side in hopes of getting some help.

Nope. The contact form was in flash. I usually avoid flash like the plague (too many suprise screaming sounds or enough flashing animation to induce a seizure) but let in and decided to install it so I could use the contact form. What saddened me, however, is there’s no need to use flash for it at all… all it is is a simple form with some special effects that could easily be reproduced using javascript.

Which is where I am trying to get with this crazy rant. Content should be accessible despite what platform you run or what you have installed… that’s the whole philosophy and purpose of the web. What makes this worse though, is what about blind people? How’s a blind person supposed to use the contact form if Jaws can’t read it? Section 508 violations abound!

No tags

Today brought a great accomplishment for our team (at least, for me personally… it felt great). We had had a small project that acted like an undead monster. At the outset, it seemed simple… our customer just wasn’t satisfied with the acceptance tests we had (in their words, “something smells funny”). The project knocked around the team for a bit… getting done, turned in. Then suddenly coming back like an undead monster searching for fresh blood.

This week, I teamed up with several different team members swapping in and out as we tried to discover why we were getting bad results from our test. As we delved in deeper, we learned so many things. At the beginning of the week, we were getting IOExceptions on some of the results being read from the reports and traced it to a bug on the outbound team’s side. They fixed it, and our other cases passed… but some failed. As we delved deeper, we discovered that some records were triggering a business rule that honestly didn’t belong there. So we removed it.

Then, those records passed, but the last didn’t. It turned out while the ones failing did not did need the business rule, the last case that had been passing through all the grief did! The interesting thing was that it was again something that revealed a small (and somewhat hard to catch) bug in the outbound business logic, so after a little research and discussion with the other team, they decided they would take it up, so we contacted the customer, they submitted a story to the other teams queue, and we were left to wrap up.

So… after a few small modifications, we successfully completed our stories and turned them in. I felt quite a bit of relief… this project had been bugging us forever, and this week not only did I learn so many more things about how our system depends on the other team’s system, it really brought to the surface how much our work ties into their work and made apparent what had previously felt invisible. In essence, we became a customer to the other team. ;)

It’s interesting how good acceptance tests can do that.

No tags

Nov/06

12

Custom Content Fitnesse Plugin

Ever found yourself wanting to add new javascript files to fitnesse for added functionality? Ever wanted to replace the default javascript files with your own, as well as add/or replace css files too?

Sadly, you’ve probably discovered that you can’t, since the html is generated on page request, and the elements in the head are added during the generation process. Further, fitnesse.js is replaced by a copy in the jar each time fitnesse starts. Don’t you wish it could be easier?

If so, then this plugin is for you. It allows you to add your own javascript and css files, with some handy features as well. You can configure explicitly what files you want to include in the plugins.properties file, or you can leave these configuration variables blank and the plugin will scan the /files/javascript and /files/css directories and include all of the files it finds in the head.

Using the plugin is easy… just download it, put it in your classpath, and add this to your plugins.properties file (again… if you don’t have this file, create it and stick it in the directory you run fitnesse from).


HtmlPageFactory=org.jamescarr.fitnesse.html.CustomHtmlPageFactory

# Custom Html Page Configuration
fitnesse_root=/home/jamescarr/fitnesse/FitNesseRoot

As you can see above, I also specify where the FitNesseRoot directory is… as this may be different according to your own configuration. You must have this set! Otherwise, the plugin just flat out won’t work (and you’ll probably get some exception stack traces dumped to the console).

By default, it will include any files it finds directly under css and javascript directories, ignoring subdirectories. To specify exactly what files to use, use something like the following:


javascript_files=dynamicTableMaker/all.js;fitnesse.js
css_files=fitnesse_cold_theme.css;fitnesse_print.css

These are relative to FitNesseRoot/files/javascript/ and FitNesseRoot/files/css/ respectively, using a semicolon to seperate specific files to use. This will only include the files that are specified rather than all of them found in directories, and can make it easier to handle subdirectories.

Well, let me know if anyone finds this useful… I’ve been using it for my dynamic table editor plugin that I’ll (hopefully) be releasing soon.

No tags

Nov/06

12

Huh? Where’s My Blog?

Something amusing I discovered this morning. I was a bit bored, so I decided to run my name through various search engines and see if I’ll show up one way or another.

MSN Live Search, Yahoo, and Altavista all return a link to my blog as the first result for the search term “James Carr.”

AskJeeves has me on page three of the results.

And as of this writing, my blog shows up all the way on the 18th page of the search results! Oddly enough, sites that link to my blog show up sooner, so people can find it “from a distance”.

Hopefully my ranking can rise above all the blog posts about the 60s soul singer eventually. ;)

No tags

Nov/06

11

Fitnesse Date Plugin

A long time ago at work I ran into a small problem during some of our tests with fitnesse. We had a few fitnesse tests that tested time based rules, stuff like a 30 day rule that basically goes something like “if the file was received longer than 60 days ago, do not process it.” This was slightly problematic though as we had to update tests from time to time if we were going to hard code dates in our tests, so I decided to take it upon myself to create a wiki plugin to generate the date time dynamically.

Anyhow, I posted it on the fitnesse mailing list on yahoogroups, but I have neglected to take the time to write up instructions on it. So, without furth ado, here it is… the fitnesse sysdate plugin how to (or you can download it now).

Installation

Installation is rather simple… simply place the SysdatePlugin.jar plugin somewhere in your class path and add it to your plugins.properties file (if you don’t already have one, create it in the same directory you run fitnesse from).

Below is what you need to add to your plugins.properties file:

WikiWidgets=fitnesse.wikitext.widgets.SystemDateWikiWidget

Start fitnesse and you’re ready to go! You should see the following at the console once fitnesse starts up.

Custom wiki widgets loaded:
                fitnesse.wikitext.widgets.SystemDateWikiWidget

Usage

Using the plugin is quite simple. To start using it right away, simply type:

!sysdate
 

This will print the current date in MMddYYYY format. If you want to use a different format, you have the option of passing the format in as a parameter… basically it will accept any format string that SimpleDateFormat takes, so refer to the api documentation for SimpleDateFormat for further information.

For example, if I wanted to print the date for today as something like 11/11/2006 21:15:23, I’d use the following:

!sysdate(MM/dd/yyyy hh:mm:ss)

Of course, it’s not usefull without features to generate a date in the future, or in the past. For example, say I want to have a test that alsways tests a boundry condition, say against a date that is always EXACTLY 30 days in the past.

The following are examples of various formats to use. Simply adding a +number or -number to the sysdate will make n days in the past or future.

!sysdate(MM/dd/yyyy hh:mm:ss)-30
!sysdate-30
!sysdate+30
!sysdate(MM/dd/yyyy hh:mm:ss)+5
 

That’s about it. If there are any features anyone like to have, please feel free to let me know. You can download the plugin here.

The Source

Here is the accompanying unit test:

package fitnesse.wikitext.widgets;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import fitnesse.wiki.*;
import fitnesse.wikitext.WidgetBuilder;

public class SystemDateWikiWidgetTest extends WidgetTest
{
    private WikiPage root;
    private PageCrawler crawler;
    private WikiPage page;
    private WidgetRoot widgetRoot;

    public void setUp() throws Exception
    {
        root = InMemoryPage.makeRoot("root");
        crawler = root.getPageCrawler();
        page = crawler.addPage(root, PathParser.parse("MyPage"));
        widgetRoot = new WidgetRoot("", page);
    }

    public void testMatches() throws Exception
    {
        assertMatches("!sysdate");
        assertMatches("!sysdate(mmmddyyyy)");
        assertMatches("!sysdate(MM-dd-yy)");
        assertMatches("!sysdate(MM-dd-yy)-1");
        assertMatches("!sysdate(MM-dd-yy)-22");
        assertMatches("!sysdate(MM-dd-yy)-300");
        assertMatches("!sysdate-30");
    }

    protected String getRegexp()
    {
        return SystemDateWikiWidget.REGEXP;
    }

    /**
     * Allow adding of sysdate special variable
     *
     * @author jamescarr
     * @throws Exception
     */
    public void testSysdateVariable() throws Exception{
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!sysdate\n");
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate");
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat format = new SimpleDateFormat("MMddyyyy");
        System.out.println(format.format(date));
        assertEquals(format.format(date), w.render());
    }

    public void testSysdateVariableWithFormat() throws Exception{
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!sysdate(MMM-dd-yyyy)\n");
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate(MMM-dd-yyyy)");
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat format = new SimpleDateFormat("MMM-dd-yyyy");
        System.out.println(format.format(date));
        assertEquals(format.format(date), w.render());
    }
    public void testSysdateVariableWithBadFormat() throws Exception{
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!define var {!sysdate(shithead)}\n");
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate(shithead)");

        // default
        SimpleDateFormat format = new SimpleDateFormat("MMddyyyy");
        Date date = new Date(System.currentTimeMillis());
        assertEquals( format.format(date), w.render());
    }

    public void testMultipleSysDateCalls() throws Exception{
        String dates = "!sysdate(MMMddyyyy)\n!sysdate\n!sysdate(MM-dd-yyyy)\n!sysdate(yyyy-MMM-dd)\n!sysdate(MM/dd/yyyy)";
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

        // expect only the first date
        SimpleDateFormat format = new SimpleDateFormat("MMMddyyyy");
        Date date = new Date(System.currentTimeMillis());

        assertEquals(format.format(date), w.render());
    }
    public void testSubtractZeroDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)-0";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());

       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }
    public void testSubtractTwentyDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)-20";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());
       cal.setTime(date);
       cal.add(Calendar.DATE, -20);
       date = cal.getTime();
       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }

    public void testAddTwentyDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)+20";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());
       cal.setTime(date);
       cal.add(Calendar.DATE, 20);
       date = cal.getTime();
       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }

    public void testAddThreeHundredDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)+300";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());
       cal.setTime(date);
       cal.add(Calendar.DATE, 300);
       date = cal.getTime();
       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }
}

And here is the source for the date widget class:

package fitnesse.wikitext.widgets;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import fitnesse.wikitext.WikiWidget;

public class SystemDateWikiWidget extends WikiWidget {
   private static final String SUBTRACT = "-";
   private static final String ADD = "+";
   private static final String DATE_FORMAT_REGEX = "\\(\\w+.*\\)";
   public static final String REGEXP = "!sysdate(\\(\\w+.*?\\))?(-\\d+|\\+\\d+)?";
   public static final Pattern pattern = Pattern.compile(REGEXP);

   private static final Pattern dateMethodPattern = Pattern.compile(DATE_FORMAT_REGEX);
   private static final Pattern subtractionPattern = Pattern.compile("(-\\d+|\\+\\d+)$");
   private String originalText = "";

   private String token = null;

   public SystemDateWikiWidget(ParentWidget parent, String text) {
      super(parent);
      originalText = text;
      Matcher match = pattern.matcher(text);
      if (match.find()) {
         token = match.group(0);
      }
   }

   private String getCurrentDate(String token) {

      SimpleDateFormat formatter = getDateFormatter(token);
      Date date = getTheDate(token);
      return formatter.format(date);
   }

   private Date getTheDate(String token) {
      Matcher match = subtractionPattern.matcher(token);
      Date date = new Date(System.currentTimeMillis());
      int days = 0;

      if (match.find()) {
         String result = match.group(0);
         if (0 == result.indexOf(SUBTRACT)) {
            days = getIntegerValue(result, SUBTRACT);
         }
         else if (0 == result.indexOf(ADD)) {
            days = getIntegerValue(result, ADD);
         }
         date = getDateDaysFromDate(date, days);
      }
      return date;
   }

   private Date getDateDaysFromDate(Date date, int days) {
      Calendar cal = Calendar.getInstance();
      cal.setTime(date);
      cal.add(Calendar.DATE, days);
      return cal.getTime();
   }

   private SimpleDateFormat getDateFormatter(String token) {
      SimpleDateFormat formatter = new SimpleDateFormat("MMddyyyy");
      Matcher match = dateMethodPattern.matcher(token);
      if (match.find()) {
         String format = match.group(0);
         format = removeParenthesis(format);
         try {
            formatter = new SimpleDateFormat(format);
         }
         catch (IllegalArgumentException e) {

         }

      }

      return formatter;
   }

   private int getIntegerValue(String result, String modifier) {
      result = result.replaceAll("\\" + modifier, "");
      return (SUBTRACT.equals(modifier)) ? -Integer.parseInt(result) : Integer.parseInt(result);
   }

   private String removeParenthesis(String format) {
      return format.replaceAll("\\(", "").replaceAll("\\)", "");
   }

   public String render() throws Exception {
      return (token != null) ? getCurrentDate(token) : originalText;
   }
}

No tags

Nov/06

11

Fitnesse Commandline Fixture

Last month I spent quite a bit of time whipping up and perfecting my very own CommandLineFixture that allowed me to have a FIT table that interacted with the commandline. This was quite handy as the system under test was using a language there is no fitnesse implementation for, so I whipped up a commandline interface to the program and used my custom CommandLineFixture to interact with it.

Then today I discovered that good old Bob Martin had already made something similar. The interesting thing is his is basically the same. Guess I should have searched first. ;)

No tags

Nov/06

11

Sneak King?

Today I stumbled across a rather amusing commercial. Apparently Burger King is soon going to be selling an XBOX 360 game titled “Sneak King.” The object of the game is to sneak around and deliver “delicous food” from Burger King to unsuspecting people before they pass out from hunger.

I can’t help but say the game is so wrong on so many levels… the last thing I need on my mind is some guy in that scary ass Burger King costume hiding in my trash can!

See for yourself:

No tags

As I mentioned earlier, I had attended the day long DesignFest session at OOPSLA my first day there. Since the whole experience picqued my interest, I decided to go ahead and attend a second session on Tuesday afternoon in an effort to perhaps inject myself more, and give it another shot.

I spotted Eric Evans sitting at one of the tables, so I decided to join them in hopes to get to see Eric tackle whatever design problem that was given, but unforunately he was only a moderator so he couldn’t participate much. :(

At first, things started a little shaky as they had at the previous DesignFest, although (thankfully) we all agreed on certain ground rules at the start, such as not being concearned about performance and such. There was quite a bit of debate on certain designs at first, and Eric even commented a bit that he felt like it was “maddening” because he couldn’t jump in and start working on the design with us. ;)

However, as time passed, we tended to find lots of common ground in our design, and suddenly everything began to mesh together in the design and it began to rapidly surface. We began quickly sketching out various sequence diagrams to show the object interactions that completed different user stories, and very interesting designs began to emerge.

The session wasn’t as long as the first one I attended, only being half a day (I believe the duration was possibly 3 hours or so). The interesting aspect was that despite the short amount of time, we actually accomplished quite a bit more than the team I was on did the first day.

Not to say anything bad of the team members of the previous team I was on, it really just boiled down to team cohesiveness. Although we were a little disjointed at first (as to be expected with a group that doesn’t know each other), as soon as we all broke the ice a bit, things began to run a bit more smoothly.

Another insight I gained from this is just about every group has “The Leader”, someone who tends to dominate the discussion at first and kind of drive the team. However, although we did have a “leader” at first, the role kind of melted away with many of us taking turns with the role, and sometimes even seeing it disappear entirely. The result was we were able to collaborate quicker and easier.

Definately a great experience.

No tags

Older posts >>

Theme Design by devolux.nh2.me