逸言

对Log进行mock

| Comments

在我们进行的一个项目中,需要测试是否按照要求正确写入到日志中。例如,我们定义了这样的类和方法:

public class ServiceClient {
     public <S, D> D sendRequest(S request, final Class<D> responseType) {
          Source source = jaxbUtil.marshall(request);
          return sendRequest(source, responseType);
     }

     private <D> D sendRequest(Source source, final Class<D> responseType) {
          return webServiceTemplate.sendSourceAndReceive(source, SourceExtractor<D>() {
               @Override
               public D extractData(Source response) throws IOException, TransformerException {
                      if (LOG.isDebugEnabled()) {
                           LOG.debug(String.format("Received response: %s", XmlSourceUtil.toXml(response)));
                      }
                      return jaxbUtil.unmarshall(response, responseType);
               }
     });
}

现在,我们要测试在Debug被enable的情况下,是否真正写了日志。我们不可能在单元测试中去访问日志文件,然后判断日志是否写入。一方面,这影响了单元测试的速度,另一方面也会造成对日志文件的依赖。我们的做法是在测试时,获得该日志,然后将该日志的所有Appenders去掉,然后加入属于内存中的Appender,即WriterAppender,然后获得该Appender写入的日志内容,对这个内容进行断言。我们定义了一个Fixture:

public class Log4jFixture {
     @BeforeClass
     public static void clearLog4JAllApenders() {
          Logger.getRootLogger().removeAllApenders();
          Logger.getRootLogger().addAppender(new NullAppender());
          setLoggingLevel(Level.DEBUG);
     }

     private static void setLoggingLevel(Level level) {
          Logger.getLogger("com.agiledon").setLevel(level);
     }

     public static StringWriter attachInMemoryLogAppender() {
          StringWriter buffer = new StringWriter();
          Logger.getRootLogger().addAppender(new WriterAppender(new SimpleLayout(), buffer));
          return buffer;
     }
}

若要测试Log的测试用例,则需要继承Log4jFixture。BeforeClass方法保证了每个继承该类的测试用例,在运行测试用例之前都会事先清除Log的appender。然后,再需要记录日志时,例如调用前面ServiceClient的sendRequest()方法前,先调用attachInMemoryLogAppender()方法,最后得到的StringWriter就会包含日志的内容。例如:

public class ServiceClientTest extends Log4jFixture {
  private JaxbUtil jaxbUtil;
  private WebServiceTemplate webServiceTemplate;
  private ServiceClient serviceClient;
  private MockObjectFactory mockObjectFactory;
  
  @Before
  public void setUp() {
      jaxbUtil = mockObjectFactory.createStrictMock(JaxbUtil.class);
      webServiceTemplate = mockObjectFactory.createStrictMock(WebServiceTemplate.class);
      serviceClient = new ServiceClient();
      serviceClient.setJaxbUtil(jaxbUtil);
      serviceClient.setWebServiceTemplate(webServiceTemplate);
  }
  
  @Test
  public void should_log_response_when_debug_enabled() throws IOException {
      setLoggingLevel(Level.INFO);
      
      Capture<SourceExtractor> sourceExtractorCapture = new Capture<SourceExtractor>();
      expect(webServiceTemplate.sendSourceAndReceive(anyObject(Source.class), capture(sourceExtractorCapture))).andReturn(new ReturnXml());
      expect(jaxbUtil.marshall(anyObject())).andReturn(new StringSource("<root/>"));
      expect(jaxbUtil.unmarshall(anyObject(Source.class), anyObject(Class.class))).andReturn(new Object());
      
      mockObjectFactory.replay();
      StringWriter stringWriter = attachInMemoryLogAppender();
      serviceClient.sendRequest(new Request(), ReturnXml.class);
      
      SourceExtractor actualSourceExtractor = sourceExtractorCapture.getValue();
      actualSourceExtractor.extractData(new StringSource("<root/>"));
      
      assertThat(stringWriter.toString(), both(containsString("Received response:")).and(containsString("<root/>")));
      
  }
}

在测试中,因为要测试的ServiceClient类,调用了私有方法sendRequest(),该方法的第二个参数事实上是一个实现了SourceExtractor的匿名类。我们用了EasyMock提供的Capture来负责捕获。

Comments