在我们进行的一个项目中,需要测试是否按照要求正确写入到日志中。例如,我们定义了这样的类和方法:
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来负责捕获。