tags:

views:

55

answers:

4

I'm using JUnit4 and Hibernate3 in my project. Hibernate depends on Slf4j and thus my project includes this library as well. Now I'd like to use Slf4j in unit tests in order to log supplementary testing information. Could you please provide a short example of how my unit test should look like in order to log just one line of text? Preferably without code duplication in multiple tests.

+2  A: 

Why do you want to log things in your unit test? Unit tests should be pass/fail and should use the testing framework to indicate that. You don't want to be reading through output to see if the test passed or failed. And if it's failing, running it in your IDE/debugger is the best way to fix it.

dty
Because sometimes its useful to know WHY a test failed, or perhaps to print performance metrics in case you want to casually keep an eye on these.
BjornS
If you want to do performance testing, do it properly and capture and graph the numbers. Casually eyeballing some numbers from a UT run is NOT the way to measure performance. And as to why a test failed, you should write your assertions so the text tells you what went wrong. At work, our product has tens of thousands of unit tests; where do you think we'd be if we had to read output from those?
dty
I didn't say performance testing, I simply said to casually keep track of it as an example of why you might want logging in your tests. As for your work, yes we have tens of thousands of tests as well and you'd be surprised how quickly you can find the information you want related to a specific test. Besides, you won't need to read them unless you really need to, and when you really need to, its damn handy to already have the logs there.
BjornS
I'm sorry, I just don't get it. You are either interested in performance or not. If you are, spotting some numbers in the middle of a log file is not the way to keep track of what's going on. As for other "valuable" logging, the system under test is presumably doing its own logging too, so seeing the logging done by the test among any other logging must be tricky. I come back to what I originally said - unit tests should be red/green, pass/fail. And if a test fails, the failure message should tell you why.
dty
@dty Logging during testing is important WHILE you're creating the test. Once the test is done, the log messages are obsolete, but they don't harm anyone. You will be interested to get back to them when the test fails again, say, in 1 year. Of course, in an ideal world only assertions would be enough. But we need logging very very often.
Vincenzo
In all my (considerable!) years, I've never felt the need to add logging to a unit test. To me, this smells of the unit test being too complex. But anyway, I'm not sure how your question is different from "how do I use slf4j". I'm not sure how using it from within a unit test is relevant to the question. What's different about using it from a unit test vs using it from production code?
dty
@dty I don't know, that's why I'm asking :) Btw I up-voted your answer since I consider it relevant to my question and it will definitely affect my future attitude to "logging in testing". Thank you.
Vincenzo
Hehehe! Good. Making people think and challenge assumptions is just part of the service. :-)
dty
A: 

It seems to be a bad smell to me.

You should always avoid manual inspection or verification for unit test.

Unit test should be automated, human intervention should only be needed if your build tools tell you that certain test failed, and the failure reason should be provided using all those verification methods (e.g. assertEquals)

Adrian Shum
+1  A: 

We use log4j as our output logger;

private static Logger log = LoggerFactory.getLogger(MyClassHere.class);

slf4j should find and use log4j without issue if you configure it correctly. To make life easy, I would use this Eclipse pattern, since you will be writing this code a fair bit:

private static Logger log = LoggerFactory.getLogger(${enclosing_type}.class);
${:import(org.slf4j.Logger,org.slf4j.LoggerFactory)}

for tests I would recommend you don't rise above INFO level and keep most things to DEBUG. If you really want to trap lots of errors intelligently then I would recommend looking into PositronLogger which is a log file appender which will silently pick up on everything down to TRACE but only dump it to file if it captures ERROR; sort of like time travelling :)

http://github.com/andreaja/PositronLogger

BjornS
@BjornS I need to do this `getLogger()` in every unit test inside some `@Before`-annotated method, right?
Vincenzo
No, just the private static reference, then you can simply call log.debug("some stuff"); wherever you like. Edit: I should clarify; the LoggerFactory.getLogger() is part of the static reference, you do not need to call it in your @Before, simply enter it as a field in your test class and it should work. A brief example: class MyTests { private static Logger log = LoggerFactory.getLogger(MyTests.class); @Test public void magic() { log.info("..."); } }
BjornS
+1  A: 

I also like to use slf4j in my JUnit tests for my DAO class. It does help when you are creating a new test or modifying an old one. I usually leave my old logging output at debug level, and make my new logging statements at info level while I'm still actively working on the code in that method. One of my JUnit classes would look something like this:

package com.example.mydao;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// other imports not shown...

public class TestMyDAO extends TestCase {

    private static final Logger logger = 
        LoggerFactory.getLogger(TestMyDAO.class);


    public void testA() {
        logger.debug("Logging from testA() method");
    }

    public void testB() {
        logger.debug("Logging from testB() method");
    }

    public void testThatIAmWorkingOn() {
        logger.info("Logging from my new test method at INFO level");
    }

}

I'm using log4j as the actual logging provider, so my log4j.xml configuration file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
    <appender name="consoleAppender" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%d %-5p  [%c{1}] %m %n" />
        </layout>
    </appender>

    <logger name="com.example.mydao" additivity="false">
        <level value="INFO" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="org.hibernate" additivity="false">
        <level value="WARN" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="org.hibernate.connection.DriverManagerConnectionProvider" additivity="false">
        <level value="INFO" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="org.hibernate.connection.C3P0ConnectionProvider" additivity="false">
        <level value="INFO" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="com.mchange" additivity="false">
        <level value="WARN" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="com.mchange.v2.resourcepool.BasicResourcePool" additivity="false">
        <level value="INFO" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource" additivity="false">
        <level value="INFO" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <logger name="com.mchange.v2.c3p0.C3P0Registry" additivity="false">
        <level value="INFO" />
        <appender-ref ref="consoleAppender"/>
    </logger>

    <root>
        <priority value ="WARN" />
        <appender-ref ref="consoleAppender"/>
    </root>

</log4j:configuration>

This gives me the info-level output from my JUnit class, as well as some useful stuff from Hibernate runtime and other libraries used with Hibernate. Adjust to your own taste.

Finally, I need to make sure that all of the following libraries are in my classpath when I execute the JUnit tests:

  • slf4j-api-1.6.0.jar
  • slf4j-log4j12-1.6.0.jar
  • log4j-1.2.16.jar
  • log4j.xml (my configuration file, shown above)
  • Some version of the JUnit runtime JAR
  • All the JARs normally present when running your application in production

This is what I do when using log4j. If you use a different logging implementation, then adjust accordingly. It doesn't matter if you're using a different version of slf4j, as long as the "API" and implementation JARs are the same version (mine are both 1.6.0).

Jim Tough