Wednesday, 28 June 2017

Tell, don't ask

More than twelve years ago Tim Joyce passed on some programming wisdom:

With programs tell don't ask, vice versa for people.

This was a bit abstract for me at the time but last night it came back to me as what is wrong with the code I am currently working on. We store our application configuration in a table in the system's target database and whenever some configuration is needed it is looked up in the database. There was no problem with this approach when the code was written because JUnit had not been invented and testing was not the main part of our discipline. However to write a test we would need a database present, which is an obstacle to fast, distinct, unit tests and has been a blocker to writing tests.

Noncompliant Code Example

public class Example { 
  private String path;
  public void logPath() {
    try {
      path = CachedSystemParameter.getInstance().
                 getParameterValue("PATH");
    } catch (SystemParameterException e) {
      logger.error("[BUSINESS] Error while retrieving system parameter PATH", e);
    }
    logger.info("Path: " + path);
  }
}

Compliant Code Example

By adding sftpPath to the class constructor we can test the business logic without the need for a database fixture.
public class Example { 

  private String path;

  public Example() { 
    this(CachedSystemParameter.getInstance().
                 getParameterValue("PATH"));
  }

  public Example(String path) { 
    this.path = path;
  } 

  public void logPath() {
    logger.info("Path: " + path);
  }
}

Thursday, 4 May 2017

Testing java slf4j over log4j logging in JUnit using SLF4J Test

Testing logging on failure paths has two problems:

  • It is hard to get the log message text
  • The logger outputs to the test log
The first leads to compromises eg verifying only that a message was logged, the second makes you, the programmer, think an error has occurred when the tests in fact passed.

Code to test


public class Sut { 
    public String perform() {
        getLog().debug("In perform");
        return "Hello world";
    }
}

My clunky PowerMock Solution

My approach was problematic as it required the use of PowerMock which is as powerful as nitroglycerin.

Test Code


@RunWith(PowerMockRunner.class)
@PrepareForTest({LoggerFactory.class})
public class SutTest {

    @Test
    public void testPerform() {
        mockStatic(LoggerFactory.class);
        Logger mockLog = mock(Logger.class);
        when(LoggerFactory.getLogger(any(Class.class))).thenReturn(mockLog);

        assertEquals("Hello world", new Sut().perform());
        verify(mockLog, times(1)).debug(startsWith("In perform"));
    }
}

Elegant SLF4j Test Solution

The slf4j-test project by RobElliot266 provides a logger which stores messages and so can be asserted against.

POM Setup

Add the following to your dependencies


        <dependency>
            <groupId>uk.org.lidalia</groupId>
            <artifactId>slf4j-test</artifactId>
            <version>1.1.0</version>
            <scope>test</scope>
        </dependency>

To ensure that this logger is used during tests only and that it takes precedence over the production logger in the test class path ensure the test logger is the first logger mentioned in the dependencies block and has a test scope.

As an additional measure you can explicitly exclude the production logger from the test class path:


        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.18.1</version>
            <configuration>
                <classpathDependencyExcludes>
                    <classpathDependencyExcludes>org.slf4j:slf4j-jdk14</classpathDependencyExcludes>
                </classpathDependencyExcludes>
            </configuration>
        </plugin>

Test Code


public class SutTest {
  @Test
  public void testPerform() {
    assertEquals("Hello world", new Sut().perform());
    assertEquals("Testing", logger.getLoggingEvents().get(0).getMessage());
  }
}

Much thanks to RobElliot266 for an neat solution to a problem that has been bugging me for a while.

Saturday, 18 March 2017

A stack chart of any CSV url

Stack chart.