-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
1,120 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
# SLF4JTesting | ||
|
||
SLF4TTesting provides a "Testing Double" of SLF4J LoggerFactory to facilitate log testing but also as a utility | ||
for use in unit and integration tests to conveniently configure logging on a test by test basis. | ||
|
||
SLF4TTesting has been designed to: | ||
|
||
- support unit and integration testing | ||
- support concurrent test execution such as [TestNG parallel suites](http://testng.org/doc/documentation-main.html#parallel-suites) or [Scalatest parallel test execution](http://doc.scalatest.org/2.0/index.html#org.scalatest.ParallelTestExecution) | ||
- promote dependency injection of the logging implementation | ||
- support testing of multi-threaded components | ||
- support testing of assemblies | ||
- provide a simple console logging facility | ||
- allow selective suppression of individual console logging messages (eg to hide any expected errors) | ||
- support integration with mocking libraries (eg Mockito) | ||
|
||
SLT4FTesting differs from some other solutions because it: | ||
|
||
* does not pollute the classpath with any StaticLoggerBinder stuff | ||
* does not compete with other SLF4J implementations resident on the classpath | ||
* does not rely on cluttering the classpath with special logback.xml/logback-test.xml files | ||
* does not use statics | ||
* does not rely on singletons anywhere | ||
* does not need thread-locals etc | ||
|
||
## Usage | ||
|
||
### Setting up | ||
|
||
Include the jar as a test dependency. | ||
|
||
### Basic Example | ||
|
||
Here we use constructor injection using the SLF4J logger factory interface. | ||
|
||
```java | ||
import org.slf4j.ILoggerFactory; | ||
import org.slf4j.Logger; | ||
|
||
public class Example1 { | ||
|
||
private final Logger logger; | ||
|
||
public Example1(ILoggerFactory lf) { | ||
this.logger = lf.getLogger(Example1.class.getName()); | ||
} | ||
|
||
public void aMethodThatLogs() { | ||
logger.info("Hello World!"); | ||
} | ||
} | ||
``` | ||
|
||
The `.getName()` is a tiny bit of boilerplate that's only necessary because SLF4J's' | ||
(ILoggerFactory)[http://www.slf4j.org/api/org/slf4j/ILoggerFactory.html] interface does not provide a by-class convenience | ||
method like (LoggerFactory)[http://www.slf4j.org/api/org/slf4j/LoggerFactory.html] does. | ||
|
||
Constructor injection is not that arduous, even less so if you use a dependecy injection framework, but it provides the seam that allows us to | ||
inject out testing double. | ||
|
||
```java | ||
import org.junit.Test; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
public class Example1UnitTest { | ||
|
||
@Test | ||
public void testAMethodThatLogs() throws Exception { | ||
TestLoggerFactory loggerFactory = new TestLoggerFactory(); | ||
|
||
Example1 sut = new Example1(loggerFactory); | ||
sut.aMethodThatLogs(); | ||
|
||
TestLogger logger = loggerFactory.getLogger(Example1.class.getName()); | ||
assertTrue(logger.contains(".*Hello.*")); | ||
} | ||
} | ||
``` | ||
|
||
|
||
## Motivation | ||
|
||
### Improved construction patterns and testability | ||
|
||
I strongly believe in dependency injection (DI) so anything that looks like a static or singleton offends me, | ||
statics and singletons are an impediment to testable code. | ||
|
||
We can't conveniently substitute statics for during testing and in the case of SLF4J we end up resorting to | ||
nasties like classpath manipulation to get over this. | ||
|
||
Anything global or static leaks information between tests and typically requires before and after setup | ||
and tear down which is more test boilerplate. | ||
This pattern means that running tests classes or class methods concurrently leads to interference as all threads interact with the | ||
shared mutable global. | ||
|
||
These problems can be avoided if we do not permit this share state between tests and instead use POJO's and DI; this library attempts | ||
to make it easy to use a POJO/DI pattern with SLF4J and write tests for that code. | ||
|
||
### Console logging during tests | ||
|
||
Typically we don't want logging going to the console in tests. A possible exception to this is that it's useful | ||
if any unexpected errors are logged to the console. Sometimes however we are performing negative tests and we expect | ||
certain error messages but we do not want to see these on the console. | ||
|
||
SLF4JTesting supports optional console logging but also a regex based scheme to suppress certain expected log lines. | ||
This helps us keep our CI console clean without losing the benefits of logging of unexpected conditions that would help | ||
us diagnose failing tests more easily. | ||
|
||
### Multithreaded code | ||
|
||
Sometimes we have an integration test where the test subject performs logging from another thread than the foreground test thread. | ||
SLF4JTesting collects all logging occurring in the test subject regardless of thread. | ||
|
||
### Assembly integration testing | ||
|
||
Sometimes we want to test an assembly where the assembly constructs objects internally. | ||
If each parent object is injected with the logger factory and any objects the parent creates are also injected with the | ||
logger factory then this pattern allows the entire logging implementation to be substituted in tests. | ||
|
||
```java | ||
import org.slf4j.ILoggerFactory; | ||
import org.slf4j.Logger; | ||
|
||
public class AssemblyExample { | ||
|
||
private final ChildObject child; | ||
|
||
public AssemblyExample(ILoggerFactory lf) { | ||
child = new ChildObject(lf); | ||
} | ||
} | ||
|
||
class ChildObject { | ||
private final Logger logger; | ||
private final GrandchildObject grandchild; | ||
|
||
ChildObject(ILoggerFactory lf) { | ||
logger = lf.getLogger(AssemblyExample.class.getName()); | ||
grandchild = new GrandchildObject(lf); | ||
} | ||
|
||
public void aMethodThatLogs() { | ||
logger.info("Hello World!"); | ||
} | ||
} | ||
|
||
class GrandchildObject { | ||
private final Logger logger; | ||
|
||
GrandchildObject (ILoggerFactory lf) { | ||
logger = lf.getLogger(AssemblyExample.class.getName()); | ||
} | ||
|
||
public void aMethodThatLogs() { | ||
logger.info("Bye World!"); | ||
} | ||
} | ||
``` | ||
|
||
### Making assertions | ||
|
||
It is possible to make some assertions about what was logged by using methods provided by TestLogger`.` | ||
|
||
``` | ||
TestLogger logger = loggerFactory.getLogger(Example1.class.getName()); | ||
assertTrue(logger.contains(".*Hello.*")); | ||
``` | ||
|
||
### Mocking | ||
|
||
You can make further assertions by using a mocking framework. | ||
|
||
SLF4JLogging allows you to hook a mock (eg Mockito or other) up to the logging implementation so that | ||
you can use the power of your chosen mocking framework directly to test the logging. | ||
|
||
SLF4J logging does not impose any particular mocking framework. | ||
|
||
```java | ||
public class MockingExampleUnitTest { | ||
@Test | ||
public void testAMethodThatLogsWithAMock() throws Exception { | ||
Logger mockLogger = Mockito.mock(Logger.class); | ||
|
||
Settings cfg = new Settings().delegate(BasicExample.class.getName(), mockLogger); | ||
TestLoggerFactory loggerFactory = new TestLoggerFactory(cfg); | ||
|
||
BasicExample sut = new BasicExample(loggerFactory); | ||
sut.aMethodThatLogs(); | ||
|
||
Mockito.verify(mockLogger).info("Hello World!"); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
group 'portingle' | ||
version '0.0-SNAPSHOT' | ||
|
||
apply plugin: 'java' | ||
|
||
sourceCompatibility = 1.7 | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
dependencies { | ||
compile 'org.slf4j:slf4j-api:1.7.5' | ||
|
||
testCompile 'junit:junit:4.11' | ||
testCompile 'org.mockito:mockito-all:1.10.19' | ||
|
||
|
||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#Wed Dec 02 23:12:13 GMT 2015 | ||
distributionBase=GRADLE_USER_HOME | ||
distributionPath=wrapper/dists | ||
zipStoreBase=GRADLE_USER_HOME | ||
zipStorePath=wrapper/dists | ||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
#!/usr/bin/env bash | ||
|
||
############################################################################## | ||
## | ||
## Gradle start up script for UN*X | ||
## | ||
############################################################################## | ||
|
||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
DEFAULT_JVM_OPTS="" | ||
|
||
APP_NAME="Gradle" | ||
APP_BASE_NAME=`basename "$0"` | ||
|
||
# Use the maximum available, or set MAX_FD != -1 to use that value. | ||
MAX_FD="maximum" | ||
|
||
warn ( ) { | ||
echo "$*" | ||
} | ||
|
||
die ( ) { | ||
echo | ||
echo "$*" | ||
echo | ||
exit 1 | ||
} | ||
|
||
# OS specific support (must be 'true' or 'false'). | ||
cygwin=false | ||
msys=false | ||
darwin=false | ||
case "`uname`" in | ||
CYGWIN* ) | ||
cygwin=true | ||
;; | ||
Darwin* ) | ||
darwin=true | ||
;; | ||
MINGW* ) | ||
msys=true | ||
;; | ||
esac | ||
|
||
# Attempt to set APP_HOME | ||
# Resolve links: $0 may be a link | ||
PRG="$0" | ||
# Need this for relative symlinks. | ||
while [ -h "$PRG" ] ; do | ||
ls=`ls -ld "$PRG"` | ||
link=`expr "$ls" : '.*-> \(.*\)$'` | ||
if expr "$link" : '/.*' > /dev/null; then | ||
PRG="$link" | ||
else | ||
PRG=`dirname "$PRG"`"/$link" | ||
fi | ||
done | ||
SAVED="`pwd`" | ||
cd "`dirname \"$PRG\"`/" >/dev/null | ||
APP_HOME="`pwd -P`" | ||
cd "$SAVED" >/dev/null | ||
|
||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||
|
||
# Determine the Java command to use to start the JVM. | ||
if [ -n "$JAVA_HOME" ] ; then | ||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||
# IBM's JDK on AIX uses strange locations for the executables | ||
JAVACMD="$JAVA_HOME/jre/sh/java" | ||
else | ||
JAVACMD="$JAVA_HOME/bin/java" | ||
fi | ||
if [ ! -x "$JAVACMD" ] ; then | ||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||
Please set the JAVA_HOME variable in your environment to match the | ||
location of your Java installation." | ||
fi | ||
else | ||
JAVACMD="java" | ||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
Please set the JAVA_HOME variable in your environment to match the | ||
location of your Java installation." | ||
fi | ||
|
||
# Increase the maximum file descriptors if we can. | ||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then | ||
MAX_FD_LIMIT=`ulimit -H -n` | ||
if [ $? -eq 0 ] ; then | ||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||
MAX_FD="$MAX_FD_LIMIT" | ||
fi | ||
ulimit -n $MAX_FD | ||
if [ $? -ne 0 ] ; then | ||
warn "Could not set maximum file descriptor limit: $MAX_FD" | ||
fi | ||
else | ||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||
fi | ||
fi | ||
|
||
# For Darwin, add options to specify how the application appears in the dock | ||
if $darwin; then | ||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:code=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||
fi | ||
|
||
# For Cygwin, switch paths to Windows format before running java | ||
if $cygwin ; then | ||
APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||
JAVACMD=`cygpath --unix "$JAVACMD"` | ||
|
||
# We build the pattern for arguments to be converted via cygpath | ||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||
SEP="" | ||
for dir in $ROOTDIRSRAW ; do | ||
ROOTDIRS="$ROOTDIRS$SEP$dir" | ||
SEP="|" | ||
done | ||
OURCYGPATTERN="(^($ROOTDIRS))" | ||
# Add a user-defined pattern to the cygpath arguments | ||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||
fi | ||
# Now convert the arguments - kludge to limit ourselves to /bin/sh | ||
i=0 | ||
for arg in "$@" ; do | ||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | ||
|
||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | ||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||
else | ||
eval `echo args$i`="\"$arg\"" | ||
fi | ||
i=$((i+1)) | ||
done | ||
case $i in | ||
(0) set -- ;; | ||
(1) set -- "$args0" ;; | ||
(2) set -- "$args0" "$args1" ;; | ||
(3) set -- "$args0" "$args1" "$args2" ;; | ||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||
esac | ||
fi | ||
|
||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules | ||
function splitJvmOpts() { | ||
JVM_OPTS=("$@") | ||
} | ||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS | ||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" | ||
|
||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" |
Oops, something went wrong.