Monday, March 19, 2012

Agile programming in extremis -- third refactoring's a charm.

See the previous post (Agile programming in extremis) for the start of the story.

The problem with the code from the previous post is "All those private methods are untestable." To resolve this we refactor again -- ending up with 8 files containing 50 lines of production code and approximately 180 lines of test code, like so:
public class EventListener{
  private EventComparer eventComparer = new EventComparer();
  private DuplicateEventHandler duplicateEventHandler;
  private Event previousEvent = null;
  
  public EventListener(
      DuplicateEventHandler duplicateEventHandler){
    this.duplicateEventHandler = duplicateEventHandler;
  }
  
  public void onEvent(Event currentEvent)
  {
    bool previousEventIsValid = (previousEvent != null);
    if(previousEventIsValid 
         && eventComparer.isSameEvent(
              previousEvent, currentEvent)) {
       duplicateEventHandler.handleSameEvent(currentEvent);
    }
    previousEvent = currentEvent.cloneEvent(); 
  }    
};

public class EventComparer{ 
  private EventLocationComparer eventLocationComparer 
        = new EventLocationComparer();
  private EventTimeComparer eventTimeComparer = 
           new EventTimeComparer();

  public bool sameEvent(
       Event currentEvent, Event previousEvent){
    return 
     eventLocationComparer.isSameLocation(
           currentEvent, prevousEvent) &&
     eventTimeComparer.isSameTime(
           currentEvent, previousEvent);
  }  
};

public class LocationComparer{
  static final int X_LIMIT = 200;
  static final int Y_LIMIT = 200;

  public bool isSameLocation(
        Event currentEvent, Event previousEvent){
    return (isSamePoint(
                currentEvent.getX(), 
                previousEvent.getX(), 
                X_LIMIT) &&
           (isSamePoint(
                currentEvent.getY(), 
                previousEvent.getY(), 
                Y_LIMIT);
  }
  
  public bool isSamePoint(
      int current, int previous, int tolerance){
    return math.abs(current - previous) < tolerance;
  }
};

public class TimeComparer{
  static final long TIME_LIMIT = 500L;

  public bool isSameTime(
       Event currentEvent, Event previousEvent){
    return currentEvent.getTime() - previousEvent.getTime() 
        < TIME_LIMIT;
  }  
};

public class EventListenerTest{
  private EventListener testObject;
  
  private Event event1 = new Event(parameter);

  @Mock
  private DuplicateEventHandler duplicateEventHandler;

  @Before  
  public void setup(){
    testObject = new EventListener(duplicateEventHandler);
    testObject.onEvent(event1);
  }    
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters
       to test boundary conditions);
    testObject.onEvent(event2);
    verify(duplicateEventHandler).handleSameEvent(event2);
    // or depending on the parameters to event2...
    verify(duplicateEventHandler, never()).
       handleSameEvent(event2);
  }
};

public class EventComparerTest{
  private EventComparer testObject;

  private Event event1 = new Event(parameter);

 @Before
  public void setup(){
    testObject = new EventComparer();
  }  
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters 
       to test boundary conditions);

    bool testResult = testObject.sameEvent(test1, test2);
    
    assertTrue(testResult);
  } 
};

public class EventLocationComparerTest{
  private EventLocationComparer testObject;
  
  private Event event1 = new Event(parameter);

 @Before
  public void setup(){
    testObject = new EventLocationComparer();
  }
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters 
       to test boundary conditions);

    bool testResult = testObject.isSameLocation(
          test1, test2);
    
    assertTrue(testResult);
  }
  
  @Test
  public void thisIsAnotherTypicalTest(){
    Random random = new Random();
    int testValue1 = random.next();
    int testLimit = random.next();
    int testValue2 = testValue1 
              plus or minus testLimit plus or minus one.
    
    bool testResult = testObject.isSamePoint(
            testValue1, testValue2, testLimit);
    
    assertTrue(testResult);    
  }
};

public class EventTimeComparerTest{
  private EventTimeComparer testObject;
  
 @Before
  public void setup(){
    testObject = new EventTimeComparer();
  }
  
  @Test
  public void thisIsATypicalTest(){
    Event event2 = new Event(various parameters 
       to test boundary conditions);

    bool testResult = testObject.isSameTime(test1, test2);
    
    assertTrue(testResult);
  }
};
It may be a bit long, but hey, it's testable. (..or not. See the next post.)

No comments: